Skip to content

Commit 9e7d51e

Browse files
committed
arch: riscv: stacktrace: implement arch_stack_walk()
Created the `arch_stack_walk()` function out from the original `z_riscv_unwind_stack()`, it's been updated to support unwinding any thread. Updated the stack_unwind test case accordingly. Increased the delay in `test_fatal_on_smp`, to wait for the the fatal thread to be terminated, as stacktrace can take a bit more time. Doubled the kernel/smp testcase timeout from 60 (default) to 120s, as some of the tests can take a little bit more than 60s to finish. Signed-off-by: Yong Cong Sin <[email protected]>
1 parent d5c883e commit 9e7d51e

File tree

6 files changed

+179
-79
lines changed

6 files changed

+179
-79
lines changed

arch/Kconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ config RISCV
113113
select ARCH_SUPPORTS_ROM_START if !SOC_SERIES_ESP32C3
114114
select ARCH_HAS_CODE_DATA_RELOCATION
115115
select ARCH_HAS_THREAD_LOCAL_STORAGE
116+
select ARCH_HAS_STACKWALK
116117
select IRQ_OFFLOAD_NESTED if IRQ_OFFLOAD
117118
select USE_SWITCH_SUPPORTED
118119
select USE_SWITCH

arch/riscv/core/fatal.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ static const struct z_exc_handle exceptions[] = {
3030
#endif
3131

3232
/* Stack trace function */
33-
void z_riscv_unwind_stack(const struct arch_esf *esf);
33+
void z_riscv_unwind_stack(const struct arch_esf *esf, const _callee_saved_t *csf);
3434

3535
uintptr_t z_riscv_get_sp_before_exc(const struct arch_esf *esf)
3636
{
@@ -107,8 +107,8 @@ FUNC_NORETURN void z_riscv_fatal_error_csf(unsigned int reason, const struct arc
107107
LOG_ERR("");
108108
}
109109

110-
if (IS_ENABLED(CONFIG_EXCEPTION_STACK_TRACE) && (esf != NULL)) {
111-
z_riscv_unwind_stack(esf);
110+
if (IS_ENABLED(CONFIG_EXCEPTION_STACK_TRACE)) {
111+
z_riscv_unwind_stack(esf, csf);
112112
}
113113

114114
#endif /* CONFIG_EXCEPTION_DEBUG */

arch/riscv/core/stacktrace.c

Lines changed: 168 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -14,68 +14,95 @@ LOG_MODULE_DECLARE(os, CONFIG_KERNEL_LOG_LEVEL);
1414

1515
uintptr_t z_riscv_get_sp_before_exc(const struct arch_esf *esf);
1616

17-
#if __riscv_xlen == 32
18-
#define PR_REG "%08" PRIxPTR
19-
#elif __riscv_xlen == 64
20-
#define PR_REG "%016" PRIxPTR
21-
#endif
22-
23-
#define MAX_STACK_FRAMES CONFIG_EXCEPTION_STACK_TRACE_MAX_FRAMES
17+
#define MAX_STACK_FRAMES \
18+
MAX(CONFIG_EXCEPTION_STACK_TRACE_MAX_FRAMES, CONFIG_ARCH_STACKWALK_MAX_FRAMES)
2419

2520
struct stackframe {
2621
uintptr_t fp;
2722
uintptr_t ra;
2823
};
2924

30-
#ifdef CONFIG_FRAME_POINTER
31-
#define SFP_FMT "fp: "
32-
#else
33-
#define SFP_FMT "sp: "
34-
#endif
25+
typedef bool (*stack_verify_fn)(uintptr_t, const struct k_thread *const, const struct arch_esf *);
3526

36-
#ifdef CONFIG_EXCEPTION_STACK_TRACE_SYMTAB
37-
#define LOG_STACK_TRACE(idx, sfp, ra, name, offset) \
38-
LOG_ERR(" %2d: " SFP_FMT PR_REG " ra: " PR_REG " [%s+0x%x]", idx, sfp, ra, name, \
39-
offset)
40-
#else
41-
#define LOG_STACK_TRACE(idx, sfp, ra, name, offset) \
42-
LOG_ERR(" %2d: " SFP_FMT PR_REG " ra: " PR_REG, idx, sfp, ra)
43-
#endif
27+
static inline bool in_irq_stack_bound(uintptr_t addr, uint8_t cpu_id)
28+
{
29+
uintptr_t start, end;
4430

45-
static bool in_stack_bound(uintptr_t addr, const struct arch_esf *esf)
31+
start = (uintptr_t)K_KERNEL_STACK_BUFFER(z_interrupt_stacks[cpu_id]);
32+
end = start + CONFIG_ISR_STACK_SIZE;
33+
34+
return (addr >= start) && (addr < end);
35+
}
36+
37+
static inline bool in_kernel_thread_stack_bound(uintptr_t addr, const struct k_thread *const thread)
38+
{
39+
uintptr_t start, end;
40+
41+
start = thread->stack_info.start;
42+
end = Z_STACK_PTR_ALIGN(thread->stack_info.start + thread->stack_info.size);
43+
44+
return (addr >= start) && (addr < end);
45+
}
46+
47+
#ifdef CONFIG_USERSPACE
48+
static inline bool in_user_thread_stack_bound(uintptr_t addr, const struct k_thread *const thread)
4649
{
47-
#ifdef CONFIG_THREAD_STACK_INFO
4850
uintptr_t start, end;
4951

52+
/* See: zephyr/include/zephyr/arch/riscv/arch.h */
53+
if (IS_ENABLED(CONFIG_PMP_POWER_OF_TWO_ALIGNMENT)) {
54+
start = thread->arch.priv_stack_start - CONFIG_PRIVILEGED_STACK_SIZE;
55+
end = thread->arch.priv_stack_start;
56+
} else {
57+
start = thread->stack_info.start - CONFIG_PRIVILEGED_STACK_SIZE;
58+
end = thread->stack_info.start;
59+
}
60+
61+
return (addr >= start) && (addr < end);
62+
}
63+
#endif /* CONFIG_USERSPACE */
64+
65+
static bool in_fatal_stack_bound(uintptr_t addr, const struct k_thread *const thread,
66+
const struct arch_esf *esf)
67+
{
68+
ARG_UNUSED(thread);
69+
70+
if (!IS_ALIGNED(addr, sizeof(uintptr_t))) {
71+
return false;
72+
}
73+
5074
if (_current == NULL || arch_is_in_isr()) {
5175
/* We were servicing an interrupt */
5276
uint8_t cpu_id = IS_ENABLED(CONFIG_SMP) ? arch_curr_cpu()->id : 0U;
5377

54-
start = (uintptr_t)K_KERNEL_STACK_BUFFER(z_interrupt_stacks[cpu_id]);
55-
end = start + CONFIG_ISR_STACK_SIZE;
78+
return in_irq_stack_bound(addr, cpu_id);
79+
}
5680
#ifdef CONFIG_USERSPACE
57-
} else if (((esf->mstatus & MSTATUS_MPP) == PRV_U) &&
58-
((_current->base.user_options & K_USER) != 0)) {
59-
/* See: zephyr/include/zephyr/arch/riscv/arch.h */
60-
if (IS_ENABLED(CONFIG_PMP_POWER_OF_TWO_ALIGNMENT)) {
61-
start = _current->arch.priv_stack_start - CONFIG_PRIVILEGED_STACK_SIZE;
62-
end = _current->arch.priv_stack_start;
63-
} else {
64-
start = _current->stack_info.start - CONFIG_PRIVILEGED_STACK_SIZE;
65-
end = _current->stack_info.start;
66-
}
67-
#endif /* CONFIG_USERSPACE */
68-
} else {
69-
start = _current->stack_info.start;
70-
end = Z_STACK_PTR_ALIGN(_current->stack_info.start + _current->stack_info.size);
81+
if ((esf != NULL) && ((esf->mstatus & MSTATUS_MPP) == PRV_U) &&
82+
((_current->base.user_options & K_USER) != 0)) {
83+
return in_user_thread_stack_bound(addr, _current);
7184
}
85+
#endif /* CONFIG_USERSPACE */
7286

73-
return (addr >= start) && (addr < end);
74-
#else
75-
ARG_UNUSED(addr);
87+
return in_kernel_thread_stack_bound(addr, _current);
88+
}
89+
90+
static bool in_stack_bound(uintptr_t addr, const struct k_thread *const thread,
91+
const struct arch_esf *esf)
92+
{
7693
ARG_UNUSED(esf);
77-
return true;
78-
#endif /* CONFIG_THREAD_STACK_INFO */
94+
95+
if (!IS_ALIGNED(addr, sizeof(uintptr_t))) {
96+
return false;
97+
}
98+
99+
#ifdef CONFIG_USERSPACE
100+
if ((thread->base.user_options & K_USER) != 0) {
101+
return in_user_thread_stack_bound(addr, thread);
102+
}
103+
#endif /* CONFIG_USERSPACE */
104+
105+
return in_kernel_thread_stack_bound(addr, thread);
79106
}
80107

81108
static inline bool in_text_region(uintptr_t addr)
@@ -86,61 +113,131 @@ static inline bool in_text_region(uintptr_t addr)
86113
}
87114

88115
#ifdef CONFIG_FRAME_POINTER
89-
void z_riscv_unwind_stack(const struct arch_esf *esf)
116+
static void walk_stackframe(stack_trace_callback_fn cb, void *cookie, const struct k_thread *thread,
117+
const struct arch_esf *esf, stack_verify_fn vrfy,
118+
const _callee_saved_t *csf)
90119
{
91-
uintptr_t fp = esf->s0;
120+
uintptr_t fp, last_fp = 0;
92121
uintptr_t ra;
93122
struct stackframe *frame;
94123

95-
LOG_ERR("call trace:");
124+
if (esf != NULL) {
125+
/* Unwind the provided exception stack frame */
126+
fp = esf->s0;
127+
ra = esf->mepc;
128+
} else if ((csf == NULL) || (csf == &_current->callee_saved)) {
129+
/* Unwind current thread (default case when nothing is provided ) */
130+
fp = (uintptr_t)__builtin_frame_address(0);
131+
ra = (uintptr_t)walk_stackframe;
132+
thread = _current;
133+
} else {
134+
/* Unwind the provided thread */
135+
fp = csf->s0;
136+
ra = csf->ra;
137+
}
96138

97-
for (int i = 0; (i < MAX_STACK_FRAMES) && (fp != 0U) && in_stack_bound(fp, esf);) {
98-
frame = (struct stackframe *)fp - 1;
99-
ra = frame->ra;
139+
for (int i = 0; (i < MAX_STACK_FRAMES) && vrfy(fp, thread, esf) && (fp > last_fp);) {
100140
if (in_text_region(ra)) {
101-
#ifdef CONFIG_EXCEPTION_STACK_TRACE_SYMTAB
102-
uint32_t offset = 0;
103-
const char *name = symtab_find_symbol_name(ra, &offset);
104-
#endif
105-
LOG_STACK_TRACE(i, fp, ra, name, offset);
141+
if (!cb(cookie, ra)) {
142+
break;
143+
}
106144
/*
107145
* Increment the iterator only if `ra` is within the text region to get the
108146
* most out of it
109147
*/
110148
i++;
111149
}
150+
last_fp = fp;
151+
/* Unwind to the previous frame */
152+
frame = (struct stackframe *)fp - 1;
153+
ra = frame->ra;
112154
fp = frame->fp;
113155
}
114-
115-
LOG_ERR("");
116156
}
117-
#else /* !CONFIG_FRAME_POINTER */
118-
void z_riscv_unwind_stack(const struct arch_esf *esf)
157+
#else /* !CONFIG_FRAME_POINTER */
158+
register uintptr_t current_stack_pointer __asm__("sp");
159+
static void walk_stackframe(stack_trace_callback_fn cb, void *cookie, const struct k_thread *thread,
160+
const struct arch_esf *esf, stack_verify_fn vrfy,
161+
const _callee_saved_t *csf)
119162
{
120-
uintptr_t sp = z_riscv_get_sp_before_exc(esf);
163+
uintptr_t sp;
121164
uintptr_t ra;
122-
uintptr_t *ksp = (uintptr_t *)sp;
165+
uintptr_t *ksp, last_ksp = 0;
166+
167+
if (esf != NULL) {
168+
/* Unwind the provided exception stack frame */
169+
sp = z_riscv_get_sp_before_exc(esf);
170+
ra = esf->mepc;
171+
} else if ((csf == NULL) || (csf == &_current->callee_saved)) {
172+
/* Unwind current thread (default case when nothing is provided ) */
173+
sp = current_stack_pointer;
174+
ra = (uintptr_t)walk_stackframe;
175+
thread = _current;
176+
} else {
177+
/* Unwind the provided thread */
178+
sp = csf->sp;
179+
ra = csf->ra;
123180

124-
LOG_ERR("call trace:");
181+
}
125182

126-
for (int i = 0; (i < MAX_STACK_FRAMES) && ((uintptr_t)ksp != 0U) &&
127-
in_stack_bound((uintptr_t)ksp, esf);
128-
ksp++) {
129-
ra = *ksp;
183+
ksp = (uintptr_t *)sp;
184+
for (int i = 0; (i < MAX_STACK_FRAMES) && vrfy((uintptr_t)ksp, thread, esf) &&
185+
((uintptr_t)ksp > last_ksp);) {
130186
if (in_text_region(ra)) {
131-
#ifdef CONFIG_EXCEPTION_STACK_TRACE_SYMTAB
132-
uint32_t offset = 0;
133-
const char *name = symtab_find_symbol_name(ra, &offset);
134-
#endif
135-
LOG_STACK_TRACE(i, (uintptr_t)ksp, ra, name, offset);
187+
if (!cb(cookie, ra)) {
188+
break;
189+
}
136190
/*
137191
* Increment the iterator only if `ra` is within the text region to get the
138192
* most out of it
139193
*/
140194
i++;
141195
}
196+
last_ksp = (uintptr_t)ksp;
197+
/* Unwind to the previous frame */
198+
ra = ((struct arch_esf *)ksp++)->ra;
142199
}
200+
}
201+
#endif /* CONFIG_FRAME_POINTER */
202+
203+
void arch_stack_walk(stack_trace_callback_fn callback_fn, void *cookie,
204+
const struct k_thread *thread, const struct arch_esf *esf)
205+
{
206+
walk_stackframe(callback_fn, cookie, thread, esf, in_stack_bound,
207+
thread != NULL ? &thread->callee_saved : NULL);
208+
}
209+
210+
#if __riscv_xlen == 32
211+
#define PR_REG "%08" PRIxPTR
212+
#elif __riscv_xlen == 64
213+
#define PR_REG "%016" PRIxPTR
214+
#endif
215+
216+
#ifdef CONFIG_EXCEPTION_STACK_TRACE_SYMTAB
217+
#define LOG_STACK_TRACE(idx, ra, name, offset) \
218+
LOG_ERR(" %2d: ra: " PR_REG " [%s+0x%x]", idx, ra, name, offset)
219+
#else
220+
#define LOG_STACK_TRACE(idx, ra, name, offset) LOG_ERR(" %2d: ra: " PR_REG, idx, ra)
221+
#endif /* CONFIG_EXCEPTION_STACK_TRACE_SYMTAB */
143222

223+
static bool print_trace_address(void *arg, unsigned long ra)
224+
{
225+
int *i = arg;
226+
#ifdef CONFIG_EXCEPTION_STACK_TRACE_SYMTAB
227+
uint32_t offset = 0;
228+
const char *name = symtab_find_symbol_name(ra, &offset);
229+
#endif
230+
231+
LOG_STACK_TRACE((*i)++, ra, name, offset);
232+
233+
return true;
234+
}
235+
236+
void z_riscv_unwind_stack(const struct arch_esf *esf, const _callee_saved_t *csf)
237+
{
238+
int i = 0;
239+
240+
LOG_ERR("call trace:");
241+
walk_stackframe(print_trace_address, &i, NULL, esf, in_fatal_stack_bound, csf);
144242
LOG_ERR("");
145243
}
146-
#endif /* CONFIG_FRAME_POINTER */

tests/arch/common/stack_unwind/testcase.yaml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ tests:
1515
type: multi_line
1616
regex:
1717
- "E: call trace:"
18-
- "E: 0: fp: \\w+ ra: \\w+"
19-
- "E: 1: fp: \\w+ ra: \\w+"
18+
- "E: 0: ra: \\w+"
19+
- "E: 1: ra: \\w+"
2020
arch.common.stack_unwind.riscv_sp:
2121
arch_allow: riscv
2222
integration_platforms:
@@ -26,8 +26,8 @@ tests:
2626
type: multi_line
2727
regex:
2828
- "E: call trace:"
29-
- "E: 0: sp: \\w+ ra: \\w+"
30-
- "E: 1: sp: \\w+ ra: \\w+"
29+
- "E: 0: ra: \\w+"
30+
- "E: 1: ra: \\w+"
3131
arch.common.stack_unwind.x86:
3232
arch_allow: x86
3333
extra_configs:

tests/kernel/smp/src/main.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -802,7 +802,7 @@ ZTEST(smp, test_fatal_on_smp)
802802
K_PRIO_PREEMPT(2), 0, K_NO_WAIT);
803803

804804
/* hold cpu and wait for thread trigger exception and being terminated */
805-
k_busy_wait(2 * DELAY_US);
805+
k_busy_wait(5 * DELAY_US);
806806

807807
/* Verify that child thread is no longer running. We can't simply use k_thread_join here
808808
* as we don't want to introduce reschedule point here.

tests/kernel/smp/testcase.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
common:
2+
timeout: 120
13
tests:
24
kernel.multiprocessing.smp:
35
tags:

0 commit comments

Comments
 (0)