Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions include/zephyr/debug/thread_analyzer.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ struct thread_analyzer_info {
#endif
#endif

#ifdef CONFIG_THREAD_ANALYZER_STACK_SAFETY
uint32_t stack_safety;
#endif

#ifdef CONFIG_THREAD_ANALYZER_PRIV_STACK_USAGE
/** Total size of privileged stack */
size_t priv_stack_size;
Expand All @@ -49,6 +53,44 @@ struct thread_analyzer_info {
#endif
};

/** Stack safety issue codes */

/* No stack safety issues detected */
#define THREAD_ANALYZE_STACK_SAFETY_NO_ISSUES 0

/* Unused stack space is below the defined threshold */
#define THREAD_ANALYZE_STACK_SAFETY_THRESHOLD_EXCEEDED 1

/* No unused stack space is left */
#define THREAD_ANALYZE_STACK_SAFETY_AT_LIMIT 2

/* Stack overflow detected */
#define THREAD_ANALYZE_STACK_SAFETY_OVERFLOW 3

/** @brief Thread analyzer stack safety callback function
*
* Stack safety callback function.
*
* @param thread Pointer to the thread being analyzed.
* @param unused_space Amount of unused stack space.
* @param stack_issue Pointer to variable to store stack safety issue code
*/
typedef void (*thread_analyzer_stack_safety_handler)(struct k_thread *thread,
size_t unused_space,
uint32_t *stack_issue);

/** @brief Change the thread analyzer stack safety callback function
*
* This function changes the thread analyzer's stack safety handler. This
* allows an application to customize behavior when a thread's unused stack
* drops below its configured threshold.
*
* @param handler Function pointer to the new handler (NULL for default)
*/
void thread_analyzer_stack_safety_handler_set(thread_analyzer_stack_safety_handler handler);



/** @brief Thread analyzer stack size callback function
*
* Callback function with thread analysis information.
Expand Down
102 changes: 102 additions & 0 deletions include/zephyr/kernel.h
Original file line number Diff line number Diff line change
Expand Up @@ -532,6 +532,108 @@ static inline void k_thread_heap_assign(struct k_thread *thread,
*/
__syscall int k_thread_stack_space_get(const struct k_thread *thread,
size_t *unused_ptr);

/**
* @brief Set the unused stack threshold for a thread as a percentage
*
* This function sets the unused stack safety usage threshold for a thread as a
* percentage of the specified thread's total stack size. When performing a
* runtime stack safety usage check, if the thread's unused stack is detected
* to be below this threshold, then a runtime stack safety usage hook will be
* invoked. Setting this threshold to 0% disables the hook.
*
* @param thread Thread on which to set the threshold
* @param pct Percentage of total stack size to use as threshold
*
* @retval 0 on success
* @retval -EINVAL if @p pct exceeds 99%
*/
__syscall int k_thread_runtime_stack_unused_threshold_pct_set(struct k_thread *thread,
uint32_t pct);

/**
* @brief Set the unused stack threshold for a thread as a number of bytes
*
* This function sets the unused stack safety usage threshold for a thread as a
* number of bytes. When performing a runtime stack safety usage check, if the
* thread's unused stack is detected to be below this threshold, then a runtime
* stack safety usage hook will be invoked. Setting this threshold to 0 bytes
* disables the hook.
*
* @param thread Thread on which to set the threshold
* @param threshold Number of bytes to use as threshold
*
* @retval 0 on success
* @retval -EINVAL if @p threshold exceeds stack size
*/
__syscall int k_thread_runtime_stack_unused_threshold_set(struct k_thread *thread,
size_t threshold);

/**
* @brief Get the unused stack usage threshold (in bytes)
*
* This function retrieves the unused stack usage threshold for a thread as a
* number of bytes. A value of 0 bytes indicates thread does not have an
* unused stack usage threshold and that the runtime stack safety usage hook is
* disabled for this thread.
*
* @param thread Thread from which to retrieve the threshold
*
* @retval Unused stack threshold (in bytes)
*/
__syscall size_t k_thread_runtime_stack_unused_threshold_get(struct k_thread *thread);

/**
* @brief Thread stack safety handler type
*
* This type defines the prototype for a custom thread stack safety handler.
* The handler is invoked when a thread's unused stack space is detected to
* have crossed below its configured threshold.
*
* @param thread Thread whose stack has crossed the safety threshold
* @param unused_space Amount of unused stack space remaining
* @param arg Pointer to user defined argument passed to the handler
*/
typedef void (*k_thread_stack_safety_handler_t)(const struct k_thread *thread,
size_t unused_space, void *arg);

/**
* @brief Run the full stack safety check on a thread
*
* This function scans the specified thread's stack to determine how much of it
* remains unused. If the unused stack space is found to be less than the
* thread's configured threshold then the specified handler is executed.
*
* @param thread Thread whose stack to check
* @param unused_ptr Amount of unused stack space remaining
* @param handler Custom handler to invoke if threshold crossed
* @param arg Argument to pass to handler
*
* @return 0 on success, -ENOTSUP if forbidden by hardware policy
*/
int k_thread_runtime_stack_safety_full_check(const struct k_thread *thread,
size_t *unused_ptr,
k_thread_stack_safety_handler_t handler,
void *arg);

/**
* @brief Run the an abbreviated stack safety check on a thread
*
* This function scans the specified thread's stack for evidence that it has
* crossed its configured threshold of unused stack space. If this evidence is
* found, the specified handler is executed.
*
* @param thread Thread whose stack to check
* @param unused_ptr Amount of unused stack space remaining
* @param handler Custom handler to invoke if threshold crossed
* @param arg Argument to pass to handler
*
* @return 0 on success, -ENOTSUP if forbidden by hardware policy
*/
int k_thread_runtime_stack_safety_threshold_check(const struct k_thread *thread,
size_t *unused_ptr,
k_thread_stack_safety_handler_t handler,
void *arg);
#endif

#if (K_HEAP_MEM_POOL_SIZE > 0)
Expand Down
11 changes: 11 additions & 0 deletions include/zephyr/kernel/thread.h
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,13 @@ struct _thread_base {
typedef struct _thread_base _thread_base_t;

#if defined(CONFIG_THREAD_STACK_INFO)

#if defined(CONFIG_THREAD_RUNTIME_STACK_SAFETY)
struct _thread_stack_usage {
size_t unused_threshold; /* Threshold below which to trigger hook */
};
#endif

/* Contains the stack information of a thread */
struct _thread_stack_info {
/* Stack start - Represents the start address of the thread-writable
Expand Down Expand Up @@ -171,6 +178,10 @@ struct _thread_stack_info {
size_t sz;
} mapped;
#endif /* CONFIG_THREAD_STACK_MEM_MAPPED */

#if defined(CONFIG_THREAD_RUNTIME_STACK_SAFETY)
struct _thread_stack_usage usage;
#endif
};

typedef struct _thread_stack_info _thread_stack_info_t;
Expand Down
21 changes: 21 additions & 0 deletions kernel/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -570,6 +570,27 @@ config SCHED_THREAD_USAGE_AUTO_ENABLE

endif # THREAD_RUNTIME_STATS

menuconfig THREAD_RUNTIME_STACK_SAFETY
bool "Thread runtime stack safety support"
default n
help
This option enables support for the thread runtime stack safety check routines.
These routines are used to detect a thread's unused stack space and invoke a
user-defined handler if it was found to have dropped below a configured threshold.

if THREAD_RUNTIME_STACK_SAFETY
config THREAD_RUNTIME_STACK_SAFETY_DEFAULT_UNUSED_THRESHOLD_PCT
int "Runtime Stack Safety unused stack usage percentage threshold"
default 0
range 0 99
help
This option specifies a thread's default runtime stack safety
threshold as a percentage of the stack size. When the runtime stack
safety routines detect that a thread's unused stack percentage has fallen
_below_ this threshold, a user-defined handler is invoked.
Setting this to 0 disables the check by default for all threads.
endif

endmenu

rsource "Kconfig.obj_core"
Expand Down
139 changes: 139 additions & 0 deletions kernel/thread.c
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,12 @@ static char *setup_thread_stack(struct k_thread *new_thread,
new_thread->stack_info.start = (uintptr_t)stack_buf_start;
new_thread->stack_info.size = stack_buf_size;
new_thread->stack_info.delta = delta;

#ifdef CONFIG_THREAD_RUNTIME_STACK_SAFETY
new_thread->stack_info.usage.unused_threshold =
(CONFIG_THREAD_RUNTIME_STACK_SAFETY_DEFAULT_UNUSED_THRESHOLD_PCT *
stack_buf_size) / 100;
#endif
#endif /* CONFIG_THREAD_STACK_INFO */
stack_ptr -= delta;

Expand Down Expand Up @@ -716,6 +722,68 @@ char *z_setup_new_thread(struct k_thread *new_thread,
return stack_ptr;
}

#ifdef CONFIG_THREAD_RUNTIME_STACK_SAFETY
int z_impl_k_thread_runtime_stack_safety_unused_threshold_pct_set(struct k_thread *thread,
uint32_t pct)
{
size_t unused_threshold;

if (pct > 99) {
return -EINVAL; /* 100% unused stack and up is invalid */
}

unused_threshold = (thread->stack_info.size * pct) / 100;

thread->stack_info.usage.unused_threshold = unused_threshold;

return 0;
}

int z_impl_k_thread_runtime_stack_safety_unused_threshold_set(struct k_thread *thread,
size_t threshold)
{
if (threshold > thread->stack_info.size) {
return -EINVAL;
}

thread->stack_info.usage.unused_threshold = threshold;

return 0;
}

size_t z_impl_k_thread_runtime_stack_safety_unused_threshold_get(struct k_thread *thread)
{
return thread->stack_info.usage.unused_threshold;
}

#ifdef CONFIG_USERSPACE
int z_vrfy_k_thread_runtime_stack_safety_unused_threshold_pct_set(struct k_thread *thread,
uint32_t pct)
{
K_OOPS(K_SYSCALL_OBJ(thread, K_OBJ_THREAD));

return z_impl_k_thread_runtime_stack_safety_unused_threshold_pct_set(thread, pct);
}
#include <zephyr/syscalls/k_thread_runtime_stack_safety_unused_threshold_pct_set_mrsh.c>

int z_vrfy_k_thread_runtime_stack_safety_unused_threshold_set(struct k_thread *thread,
size_t threshold)
{
K_OOPS(K_SYSCALL_OBJ(thread, K_OBJ_THREAD));

return z_impl_k_thread_runtime_stack_safety_unused_threshold_set(thread, threshold);
}
#include <zephyr/syscalls/k_thread_runtime_stack_safety_unused_threshold_set_mrsh.c>

size_t z_vrfy_k_thread_runtime_stack_safety_unused_threshold_get(struct k_thread *thread)
{
K_OOPS(K_SYSCALL_OBJ(thread, K_OBJ_THREAD));

return z_impl_k_thread_runtime_stack_safety_unused_threshold_get(thread);
}
#include <zephyr/syscalls/k_thread_runtime_stack_safety_unused_threshold_get_mrsh.c>
#endif /* CONFIG_USERSPACE */
#endif /* CONFIG_THREAD_RUNTIME_STACK_SAFETY */

k_tid_t z_impl_k_thread_create(struct k_thread *new_thread,
k_thread_stack_t *stack,
Expand Down Expand Up @@ -922,6 +990,77 @@ int z_stack_space_get(const uint8_t *stack_start, size_t size, size_t *unused_pt
return 0;
}

#ifdef CONFIG_THREAD_RUNTIME_STACK_SAFETY
int k_thread_runtime_stack_safety_full_check(const struct k_thread *thread,
size_t *unused_ptr,
k_thread_stack_safety_handler_t handler,
void *arg)
{
int rv;
size_t unused_space;

__ASSERT_NO_MSG(thread != NULL);

rv = z_stack_space_get((const uint8_t *)thread->stack_info.start,
thread->stack_info.size, &unused_space);

if (rv != 0) {
return rv;
}

if (unused_ptr != NULL) {
*unused_ptr = unused_space;
}

if ((unused_space < thread->stack_info.usage.unused_threshold) &&
(handler != NULL)) {
handler(thread, unused_space, arg);
}

return 0;
}

int k_thread_runtime_stack_safety_threshold_check(const struct k_thread *thread,
size_t *unused_ptr,
k_thread_stack_safety_handler_t handler,
void *arg)
{
int rv;
size_t unused_space;

__ASSERT_NO_MSG(thread != NULL);

rv = z_stack_space_get((const uint8_t *)thread->stack_info.start,
thread->stack_info.usage.unused_threshold,
&unused_space);

if (rv != 0) {
return rv;
}

if (unused_ptr != NULL) {
*unused_ptr = unused_space;
}

if ((unused_space < thread->stack_info.usage.unused_threshold) &&
(handler != NULL)) {
handler(thread, unused_space, arg);
}

return 0;
}

#ifdef CONFIG_USERSPACE
int z_vrfy_k_thread_runtime_stack_safety_unused_threshold_get(struct k_thread *thread)
{
K_OOPS(K_SYSCALL_OBJ(thread, K_OBJ_THREAD));

return z_impl_k_thread_runtime_stack_safety_unused_threshold_set(thread);
}
#include <zephyr/syscalls/k_thread_runtime_stack_safety_unused_threshold_get_mrsh.c>
#endif /* CONFIG_USERSPACE */
#endif /* CONFIG_THREAD_RUNTIME_STACK_SAFETY */

int z_impl_k_thread_stack_space_get(const struct k_thread *thread,
size_t *unused_ptr)
{
Expand Down
10 changes: 10 additions & 0 deletions subsys/debug/thread_analyzer/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,16 @@ config THREAD_ANALYZER_RUN_UNLOCKED
For the limitation of such configuration see the k_thread_foreach
documentation.

config THREAD_ANALYZER_STACK_SAFETY
bool "Thread analysis includes thread runtime stack safety check"
default n
select THREAD_RUNTIME_STACK_SAFETY
help
If enabled, the stack usage analysis is enhanced by calling a customizable handler
when the unused stack space of a thread falls below its configured threshold. This
customized handler may take actions such as logging warnings, suspending or even
aborting threads.

config THREAD_ANALYZER_AUTO
bool "Run periodic thread analysis in a thread"
help
Expand Down
Loading