diff --git a/arch/Kconfig b/arch/Kconfig index 15d8fc0653bb4..b597236699066 100644 --- a/arch/Kconfig +++ b/arch/Kconfig @@ -113,6 +113,7 @@ config RISCV select ARCH_SUPPORTS_ROM_START if !SOC_SERIES_ESP32C3 select ARCH_HAS_CODE_DATA_RELOCATION select ARCH_HAS_THREAD_LOCAL_STORAGE + select ARCH_HAS_STACKWALK select IRQ_OFFLOAD_NESTED if IRQ_OFFLOAD select USE_SWITCH_SUPPORTED select USE_SWITCH @@ -409,6 +410,14 @@ config FRAME_POINTER Select Y here to gain precise stack traces at the expense of slightly increased size and decreased speed. +config ARCH_STACKWALK_MAX_FRAMES + int "Max depth for stack walk function" + default 8 + depends on ARCH_HAS_STACKWALK + help + Depending on implementation, this can place a hard limit on the depths of the stack + for the stack walk function to examine. + menu "Interrupt Configuration" config ISR_TABLES_LOCAL_DECLARATION_SUPPORTED @@ -654,6 +663,11 @@ config ARCH_HAS_EXTRA_EXCEPTION_INFO config ARCH_HAS_GDBSTUB bool +config ARCH_HAS_STACKWALK + bool + help + This is selected when the architecture implemented the arch_stack_walk() API. + config ARCH_HAS_COHERENCE bool help diff --git a/arch/riscv/core/fatal.c b/arch/riscv/core/fatal.c index d6dd4bc38869a..779d23905cab0 100644 --- a/arch/riscv/core/fatal.c +++ b/arch/riscv/core/fatal.c @@ -4,7 +4,6 @@ * SPDX-License-Identifier: Apache-2.0 */ -#include #include #include #include @@ -30,7 +29,7 @@ static const struct z_exc_handle exceptions[] = { #endif /* Stack trace function */ -void z_riscv_unwind_stack(const struct arch_esf *esf); +void z_riscv_unwind_stack(const struct arch_esf *esf, const _callee_saved_t *csf); uintptr_t z_riscv_get_sp_before_exc(const struct arch_esf *esf) { @@ -80,14 +79,7 @@ FUNC_NORETURN void z_riscv_fatal_error_csf(unsigned int reason, const struct arc #endif /* CONFIG_RISCV_ISA_RV32E */ LOG_ERR(" sp: " PR_REG, z_riscv_get_sp_before_exc(esf)); LOG_ERR(" ra: " PR_REG, esf->ra); -#ifndef CONFIG_SYMTAB LOG_ERR(" mepc: " PR_REG, esf->mepc); -#else - uint32_t offset = 0; - const char *name = symtab_find_symbol_name(esf->mepc, &offset); - - LOG_ERR(" mepc: " PR_REG " [%s+0x%x]", esf->mepc, name, offset); -#endif LOG_ERR("mstatus: " PR_REG, esf->mstatus); LOG_ERR(""); } @@ -107,8 +99,8 @@ FUNC_NORETURN void z_riscv_fatal_error_csf(unsigned int reason, const struct arc LOG_ERR(""); } - if (IS_ENABLED(CONFIG_EXCEPTION_STACK_TRACE) && (esf != NULL)) { - z_riscv_unwind_stack(esf); + if (IS_ENABLED(CONFIG_EXCEPTION_STACK_TRACE)) { + z_riscv_unwind_stack(esf, csf); } #endif /* CONFIG_EXCEPTION_DEBUG */ diff --git a/arch/riscv/core/stacktrace.c b/arch/riscv/core/stacktrace.c index cda6748c36371..7a684a9bae9e7 100644 --- a/arch/riscv/core/stacktrace.c +++ b/arch/riscv/core/stacktrace.c @@ -14,68 +14,95 @@ LOG_MODULE_DECLARE(os, CONFIG_KERNEL_LOG_LEVEL); uintptr_t z_riscv_get_sp_before_exc(const struct arch_esf *esf); -#if __riscv_xlen == 32 - #define PR_REG "%08" PRIxPTR -#elif __riscv_xlen == 64 - #define PR_REG "%016" PRIxPTR -#endif - -#define MAX_STACK_FRAMES CONFIG_EXCEPTION_STACK_TRACE_MAX_FRAMES +#define MAX_STACK_FRAMES \ + MAX(CONFIG_EXCEPTION_STACK_TRACE_MAX_FRAMES, CONFIG_ARCH_STACKWALK_MAX_FRAMES) struct stackframe { uintptr_t fp; uintptr_t ra; }; -#ifdef CONFIG_FRAME_POINTER -#define SFP_FMT "fp: " -#else -#define SFP_FMT "sp: " -#endif +typedef bool (*stack_verify_fn)(uintptr_t, const struct k_thread *const, const struct arch_esf *); -#ifdef CONFIG_EXCEPTION_STACK_TRACE_SYMTAB -#define LOG_STACK_TRACE(idx, sfp, ra, name, offset) \ - LOG_ERR(" %2d: " SFP_FMT PR_REG " ra: " PR_REG " [%s+0x%x]", idx, sfp, ra, name, \ - offset) -#else -#define LOG_STACK_TRACE(idx, sfp, ra, name, offset) \ - LOG_ERR(" %2d: " SFP_FMT PR_REG " ra: " PR_REG, idx, sfp, ra) -#endif +static inline bool in_irq_stack_bound(uintptr_t addr, uint8_t cpu_id) +{ + uintptr_t start, end; -static bool in_stack_bound(uintptr_t addr, const struct arch_esf *esf) + start = (uintptr_t)K_KERNEL_STACK_BUFFER(z_interrupt_stacks[cpu_id]); + end = start + CONFIG_ISR_STACK_SIZE; + + return (addr >= start) && (addr < end); +} + +static inline bool in_kernel_thread_stack_bound(uintptr_t addr, const struct k_thread *const thread) +{ + uintptr_t start, end; + + start = thread->stack_info.start; + end = Z_STACK_PTR_ALIGN(thread->stack_info.start + thread->stack_info.size); + + return (addr >= start) && (addr < end); +} + +#ifdef CONFIG_USERSPACE +static inline bool in_user_thread_stack_bound(uintptr_t addr, const struct k_thread *const thread) { -#ifdef CONFIG_THREAD_STACK_INFO uintptr_t start, end; + /* See: zephyr/include/zephyr/arch/riscv/arch.h */ + if (IS_ENABLED(CONFIG_PMP_POWER_OF_TWO_ALIGNMENT)) { + start = thread->arch.priv_stack_start - CONFIG_PRIVILEGED_STACK_SIZE; + end = thread->arch.priv_stack_start; + } else { + start = thread->stack_info.start - CONFIG_PRIVILEGED_STACK_SIZE; + end = thread->stack_info.start; + } + + return (addr >= start) && (addr < end); +} +#endif /* CONFIG_USERSPACE */ + +static bool in_fatal_stack_bound(uintptr_t addr, const struct k_thread *const thread, + const struct arch_esf *esf) +{ + ARG_UNUSED(thread); + + if (!IS_ALIGNED(addr, sizeof(uintptr_t))) { + return false; + } + if (_current == NULL || arch_is_in_isr()) { /* We were servicing an interrupt */ uint8_t cpu_id = IS_ENABLED(CONFIG_SMP) ? arch_curr_cpu()->id : 0U; - start = (uintptr_t)K_KERNEL_STACK_BUFFER(z_interrupt_stacks[cpu_id]); - end = start + CONFIG_ISR_STACK_SIZE; + return in_irq_stack_bound(addr, cpu_id); + } #ifdef CONFIG_USERSPACE - } else if (((esf->mstatus & MSTATUS_MPP) == PRV_U) && - ((_current->base.user_options & K_USER) != 0)) { - /* See: zephyr/include/zephyr/arch/riscv/arch.h */ - if (IS_ENABLED(CONFIG_PMP_POWER_OF_TWO_ALIGNMENT)) { - start = _current->arch.priv_stack_start - CONFIG_PRIVILEGED_STACK_SIZE; - end = _current->arch.priv_stack_start; - } else { - start = _current->stack_info.start - CONFIG_PRIVILEGED_STACK_SIZE; - end = _current->stack_info.start; - } -#endif /* CONFIG_USERSPACE */ - } else { - start = _current->stack_info.start; - end = Z_STACK_PTR_ALIGN(_current->stack_info.start + _current->stack_info.size); + if ((esf != NULL) && ((esf->mstatus & MSTATUS_MPP) == PRV_U) && + ((_current->base.user_options & K_USER) != 0)) { + return in_user_thread_stack_bound(addr, _current); } +#endif /* CONFIG_USERSPACE */ - return (addr >= start) && (addr < end); -#else - ARG_UNUSED(addr); + return in_kernel_thread_stack_bound(addr, _current); +} + +static bool in_stack_bound(uintptr_t addr, const struct k_thread *const thread, + const struct arch_esf *esf) +{ ARG_UNUSED(esf); - return true; -#endif /* CONFIG_THREAD_STACK_INFO */ + + if (!IS_ALIGNED(addr, sizeof(uintptr_t))) { + return false; + } + +#ifdef CONFIG_USERSPACE + if ((thread->base.user_options & K_USER) != 0) { + return in_user_thread_stack_bound(addr, thread); + } +#endif /* CONFIG_USERSPACE */ + + return in_kernel_thread_stack_bound(addr, thread); } static inline bool in_text_region(uintptr_t addr) @@ -86,61 +113,131 @@ static inline bool in_text_region(uintptr_t addr) } #ifdef CONFIG_FRAME_POINTER -void z_riscv_unwind_stack(const struct arch_esf *esf) +static void walk_stackframe(stack_trace_callback_fn cb, void *cookie, const struct k_thread *thread, + const struct arch_esf *esf, stack_verify_fn vrfy, + const _callee_saved_t *csf) { - uintptr_t fp = esf->s0; + uintptr_t fp, last_fp = 0; uintptr_t ra; struct stackframe *frame; - LOG_ERR("call trace:"); + if (esf != NULL) { + /* Unwind the provided exception stack frame */ + fp = esf->s0; + ra = esf->mepc; + } else if ((csf == NULL) || (csf == &_current->callee_saved)) { + /* Unwind current thread (default case when nothing is provided ) */ + fp = (uintptr_t)__builtin_frame_address(0); + ra = (uintptr_t)walk_stackframe; + thread = _current; + } else { + /* Unwind the provided thread */ + fp = csf->s0; + ra = csf->ra; + } - for (int i = 0; (i < MAX_STACK_FRAMES) && (fp != 0U) && in_stack_bound(fp, esf);) { - frame = (struct stackframe *)fp - 1; - ra = frame->ra; + for (int i = 0; (i < MAX_STACK_FRAMES) && vrfy(fp, thread, esf) && (fp > last_fp);) { if (in_text_region(ra)) { -#ifdef CONFIG_EXCEPTION_STACK_TRACE_SYMTAB - uint32_t offset = 0; - const char *name = symtab_find_symbol_name(ra, &offset); -#endif - LOG_STACK_TRACE(i, fp, ra, name, offset); + if (!cb(cookie, ra)) { + break; + } /* * Increment the iterator only if `ra` is within the text region to get the * most out of it */ i++; } + last_fp = fp; + /* Unwind to the previous frame */ + frame = (struct stackframe *)fp - 1; + ra = frame->ra; fp = frame->fp; } - - LOG_ERR(""); } -#else /* !CONFIG_FRAME_POINTER */ -void z_riscv_unwind_stack(const struct arch_esf *esf) +#else /* !CONFIG_FRAME_POINTER */ +register uintptr_t current_stack_pointer __asm__("sp"); +static void walk_stackframe(stack_trace_callback_fn cb, void *cookie, const struct k_thread *thread, + const struct arch_esf *esf, stack_verify_fn vrfy, + const _callee_saved_t *csf) { - uintptr_t sp = z_riscv_get_sp_before_exc(esf); + uintptr_t sp; uintptr_t ra; - uintptr_t *ksp = (uintptr_t *)sp; + uintptr_t *ksp, last_ksp = 0; + + if (esf != NULL) { + /* Unwind the provided exception stack frame */ + sp = z_riscv_get_sp_before_exc(esf); + ra = esf->mepc; + } else if ((csf == NULL) || (csf == &_current->callee_saved)) { + /* Unwind current thread (default case when nothing is provided ) */ + sp = current_stack_pointer; + ra = (uintptr_t)walk_stackframe; + thread = _current; + } else { + /* Unwind the provided thread */ + sp = csf->sp; + ra = csf->ra; - LOG_ERR("call trace:"); + } - for (int i = 0; (i < MAX_STACK_FRAMES) && ((uintptr_t)ksp != 0U) && - in_stack_bound((uintptr_t)ksp, esf); - ksp++) { - ra = *ksp; + ksp = (uintptr_t *)sp; + for (int i = 0; (i < MAX_STACK_FRAMES) && vrfy((uintptr_t)ksp, thread, esf) && + ((uintptr_t)ksp > last_ksp);) { if (in_text_region(ra)) { -#ifdef CONFIG_EXCEPTION_STACK_TRACE_SYMTAB - uint32_t offset = 0; - const char *name = symtab_find_symbol_name(ra, &offset); -#endif - LOG_STACK_TRACE(i, (uintptr_t)ksp, ra, name, offset); + if (!cb(cookie, ra)) { + break; + } /* * Increment the iterator only if `ra` is within the text region to get the * most out of it */ i++; } + last_ksp = (uintptr_t)ksp; + /* Unwind to the previous frame */ + ra = ((struct arch_esf *)ksp++)->ra; } +} +#endif /* CONFIG_FRAME_POINTER */ + +void arch_stack_walk(stack_trace_callback_fn callback_fn, void *cookie, + const struct k_thread *thread, const struct arch_esf *esf) +{ + walk_stackframe(callback_fn, cookie, thread, esf, in_stack_bound, + thread != NULL ? &thread->callee_saved : NULL); +} + +#if __riscv_xlen == 32 +#define PR_REG "%08" PRIxPTR +#elif __riscv_xlen == 64 +#define PR_REG "%016" PRIxPTR +#endif + +#ifdef CONFIG_EXCEPTION_STACK_TRACE_SYMTAB +#define LOG_STACK_TRACE(idx, ra, name, offset) \ + LOG_ERR(" %2d: ra: " PR_REG " [%s+0x%x]", idx, ra, name, offset) +#else +#define LOG_STACK_TRACE(idx, ra, name, offset) LOG_ERR(" %2d: ra: " PR_REG, idx, ra) +#endif /* CONFIG_EXCEPTION_STACK_TRACE_SYMTAB */ +static bool print_trace_address(void *arg, unsigned long ra) +{ + int *i = arg; +#ifdef CONFIG_EXCEPTION_STACK_TRACE_SYMTAB + uint32_t offset = 0; + const char *name = symtab_find_symbol_name(ra, &offset); +#endif + + LOG_STACK_TRACE((*i)++, ra, name, offset); + + return true; +} + +void z_riscv_unwind_stack(const struct arch_esf *esf, const _callee_saved_t *csf) +{ + int i = 0; + + LOG_ERR("call trace:"); + walk_stackframe(print_trace_address, &i, NULL, esf, in_fatal_stack_bound, csf); LOG_ERR(""); } -#endif /* CONFIG_FRAME_POINTER */ diff --git a/include/zephyr/arch/arch_interface.h b/include/zephyr/arch/arch_interface.h index d7c33e511ce50..ae51b4a375999 100644 --- a/include/zephyr/arch/arch_interface.h +++ b/include/zephyr/arch/arch_interface.h @@ -1251,6 +1251,33 @@ bool arch_pcie_msi_vector_connect(msi_vector_t *vector, */ void arch_spin_relax(void); +/** + * stack_trace_callback_fn - Callback for @ref arch_stack_walk + * @param cookie Caller supplied pointer handed back by @ref arch_stack_walk + * @param addr The stack entry address to consume + * + * @return True, if the entry was consumed or skipped. False, if there is no space left to store + */ +typedef bool (*stack_trace_callback_fn)(void *cookie, unsigned long addr); + +/** + * @brief Architecture-specific function to walk the stack + * + * @param callback_fn Callback which is invoked by the architecture code for each entry. + * @param cookie Caller supplied pointer which is handed back to @a callback_fn + * @param thread Pointer to a k_thread struct, can be NULL + * @param esf Pointer to an arch_esf struct, can be NULL + * + * ============ ======= ============================================ + * thread esf + * ============ ======= ============================================ + * thread NULL Stack trace from thread (can be _current) + * thread esf Stack trace starting on esf + * ============ ======= ============================================ + */ +void arch_stack_walk(stack_trace_callback_fn callback_fn, void *cookie, + const struct k_thread *thread, const struct arch_esf *esf); + #ifdef __cplusplus } #endif /* __cplusplus */ diff --git a/subsys/debug/Kconfig b/subsys/debug/Kconfig index d8922ce99eab7..eb4313d2374d6 100644 --- a/subsys/debug/Kconfig +++ b/subsys/debug/Kconfig @@ -382,6 +382,7 @@ config EXCEPTION_STACK_TRACE config EXCEPTION_STACK_TRACE_MAX_FRAMES int "Configures the depth of stack trace" + default ARCH_STACKWALK_MAX_FRAMES if ARCH_HAS_STACKWALK default 8 depends on EXCEPTION_STACK_TRACE help diff --git a/subsys/shell/modules/kernel_service.c b/subsys/shell/modules/kernel_service.c index 992a83f65b51c..246120f923420 100644 --- a/subsys/shell/modules/kernel_service.c +++ b/subsys/shell/modules/kernel_service.c @@ -24,6 +24,7 @@ #if defined(CONFIG_LOG_RUNTIME_FILTERING) #include #endif +#include #if defined(CONFIG_THREAD_MAX_NAME_LEN) #define THREAD_MAX_NAM_LEN CONFIG_THREAD_MAX_NAME_LEN @@ -203,6 +204,66 @@ static int cmd_kernel_threads(const struct shell *sh, return 0; } +#if defined(CONFIG_ARCH_HAS_STACKWALK) + +static bool print_trace_address(void *arg, unsigned long ra) +{ + const struct shell *sh = arg; +#ifdef CONFIG_SYMTAB + uint32_t offset = 0; + const char *name = symtab_find_symbol_name(ra, &offset); + + shell_print(sh, "ra: %p [%s+0x%x]", (void *)ra, name, offset); +#else + shell_print(sh, "ra: %p", (void *)ra); +#endif + + return true; +} + +struct unwind_entry { + const struct k_thread *const thread; + bool valid; +}; + +static void is_valid_thread(const struct k_thread *cthread, void *user_data) +{ + struct unwind_entry *entry = user_data; + + if (cthread == entry->thread) { + entry->valid = true; + } +} + +static int cmd_kernel_unwind(const struct shell *sh, size_t argc, char **argv) +{ + struct k_thread *thread; + + if (argc == 1) { + thread = _current; + } else { + thread = UINT_TO_POINTER(strtoll(argv[1], NULL, 16)); + struct unwind_entry entry = { + .thread = thread, + .valid = false, + }; + + k_thread_foreach_unlocked(is_valid_thread, &entry); + + if (!entry.valid) { + shell_error(sh, "Invalid thread id %p", (void *)thread); + return -EINVAL; + } + } + shell_print(sh, "Unwinding %p %s", (void *)thread, thread->name); + + arch_stack_walk(print_trace_address, (void *)sh, thread, NULL); + + return 0; +} + +#endif /* CONFIG_ARCH_HAS_STACKWALK */ + static void shell_stack_dump(const struct k_thread *thread, void *user_data) { const struct shell *sh = (const struct shell *)user_data; @@ -397,6 +458,9 @@ SHELL_STATIC_SUBCMD_SET_CREATE(sub_kernel, defined(CONFIG_THREAD_MONITOR) SHELL_CMD(stacks, NULL, "List threads stack usage.", cmd_kernel_stacks), SHELL_CMD(threads, NULL, "List kernel threads.", cmd_kernel_threads), +#if defined(CONFIG_ARCH_HAS_STACKWALK) + SHELL_CMD_ARG(unwind, NULL, "Unwind a thread.", cmd_kernel_unwind, 1, 1), +#endif /* CONFIG_ARCH_HAS_STACKWALK */ #endif #if defined(CONFIG_SYS_HEAP_RUNTIME_STATS) && (K_HEAP_MEM_POOL_SIZE > 0) SHELL_CMD(heap, NULL, "System heap usage statistics.", cmd_kernel_heap), diff --git a/tests/arch/common/stack_unwind/testcase.yaml b/tests/arch/common/stack_unwind/testcase.yaml index 1699adeae5d37..b6fbd0f7ed8bb 100644 --- a/tests/arch/common/stack_unwind/testcase.yaml +++ b/tests/arch/common/stack_unwind/testcase.yaml @@ -15,8 +15,8 @@ tests: type: multi_line regex: - "E: call trace:" - - "E: 0: fp: \\w+ ra: \\w+" - - "E: 1: fp: \\w+ ra: \\w+" + - "E: 0: ra: \\w+" + - "E: 1: ra: \\w+" arch.common.stack_unwind.riscv_sp: arch_allow: riscv integration_platforms: @@ -26,8 +26,8 @@ tests: type: multi_line regex: - "E: call trace:" - - "E: 0: sp: \\w+ ra: \\w+" - - "E: 1: sp: \\w+ ra: \\w+" + - "E: 0: ra: \\w+" + - "E: 1: ra: \\w+" arch.common.stack_unwind.x86: arch_allow: x86 extra_configs: diff --git a/tests/kernel/smp/src/main.c b/tests/kernel/smp/src/main.c index 7f556793e670c..de17be4f1db04 100644 --- a/tests/kernel/smp/src/main.c +++ b/tests/kernel/smp/src/main.c @@ -802,7 +802,7 @@ ZTEST(smp, test_fatal_on_smp) K_PRIO_PREEMPT(2), 0, K_NO_WAIT); /* hold cpu and wait for thread trigger exception and being terminated */ - k_busy_wait(2 * DELAY_US); + k_busy_wait(5 * DELAY_US); /* Verify that child thread is no longer running. We can't simply use k_thread_join here * as we don't want to introduce reschedule point here. diff --git a/tests/kernel/smp/testcase.yaml b/tests/kernel/smp/testcase.yaml index db509d300e32f..6138c7c262763 100644 --- a/tests/kernel/smp/testcase.yaml +++ b/tests/kernel/smp/testcase.yaml @@ -1,3 +1,5 @@ +common: + timeout: 120 tests: kernel.multiprocessing.smp: tags: