Skip to content

Commit

Permalink
arch: riscv: implement frame-pointer based stack unwinding
Browse files Browse the repository at this point in the history
Influenced heavily by:
https://lists.denx.de/pipermail/u-boot/2023-May/518216.html

the implementation in x86, Linux kernel and others in Meta.

`CONFIG_RISCV_EXCEPTION_STACK_TRACE` can be enabled by
configuring the following Kconfigs:

```prj.conf
CONFIG_DEBUG_INFO=y
CONFIG_EXCEPTION_STACK_TRACE=y
CONFIG_OVERRIDE_FRAME_POINTER_DEFAULT=y
CONFIG_OMIT_FRAME_POINTER=n
```

qemu_riscv64 output when using an illegal instruction,
timestamp is removed to fit into the column limit:

```log
*** Booting Zephyr OS build zephyr-v3.5.0-3687-gdd23d5b175cf ***
<err> os:
<err> os:  mcause: 2, Illegal instruction
<err> os:   mtval: 7777777
<err> os:      a0: 000000000000000c    t0: 0000000000000000
<err> os:      a1: 0000000010000000    t1: 000000008000f4d0
<err> os:      a2: 000000000000000a    t2: 0000000000000000
<err> os:      a3: 0000000080010e00    t3: 0000000000000000
<err> os:      a4: 0000000000000001    t4: 0000000000000000
<err> os:      a5: 000000008000c3d3    t5: 0000000000000000
<err> os:      a6: 000000008000cd00    t6: 0000000000000000
<err> os:      a7: 0000000080010e28
<err> os:      ra: 0000000080000472
<err> os:    mepc: 0000000080000472
<err> os: mstatus: 0000000a00001880
<err> os:
<err> os: Call Trace begin:
<err> os:       0: fp: 0000000080010e40   ra: 00000000800004a6
<err> os:       1: fp: 0000000080010e60   ra: 00000000800004ca
<err> os:       2: fp: 0000000080010e70   ra: 00000000800054e2
<err> os:       3: fp: 0000000080010e80   ra: 000000008000136a
<err> os: Call Trace end
<err> os: >>> ZEPHYR FATAL ERROR 0: CPU exception on CPU 0
<err> os: Current thread: 0x8000eb60 (unknown)
<err> os: Halting system
```

Signed-off-by: Yong Cong Sin <ycsin@meta.com>
  • Loading branch information
ycsin committed Mar 14, 2024
1 parent c9ef323 commit 7a5c717
Show file tree
Hide file tree
Showing 6 changed files with 188 additions and 0 deletions.
18 changes: 18 additions & 0 deletions arch/riscv/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,24 @@ config RISCV_ALWAYS_SWITCH_THROUGH_ECALL
and most people should say n here to minimize context switching
overhead.

config RISCV_HAS_FRAME_POINTER
bool
default y
depends on OVERRIDE_FRAME_POINTER_DEFAULT && !OMIT_FRAME_POINTER
help
Hidden option to simplify access to OVERRIDE_FRAME_POINTER_DEFAULT
and OMIT_FRAME_POINTER. It is automatically enabled when the frame
pointer unwinding is enabled.

config RISCV_EXCEPTION_STACK_TRACE
bool
default y
depends on RISCV_HAS_FRAME_POINTER
depends on EXCEPTION_STACK_TRACE
imply THREAD_STACK_INFO
help
Internal config to enable runtime stack traces on fatal exceptions.

menu "RISCV Processor Options"

config INCLUDE_RESET_VECTOR
Expand Down
76 changes: 76 additions & 0 deletions arch/riscv/core/fatal.c
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,79 @@ static const struct z_exc_handle exceptions[] = {
#define NO_REG " "
#endif

#ifdef CONFIG_RISCV_EXCEPTION_STACK_TRACE
#define MAX_STACK_FRAMES 8

struct stackframe {
uintptr_t fp;
uintptr_t ra;
};

static bool in_stack_bound(uintptr_t addr)
{
#ifdef CONFIG_THREAD_STACK_INFO
uintptr_t start, end;

if (_current == NULL || arch_is_in_isr()) {
/* We were servicing an interrupt */
int cpu_id;

#ifdef CONFIG_SMP
cpu_id = arch_curr_cpu()->id;
#else
cpu_id = 0;
#endif

start = (uintptr_t)Z_THREAD_STACK_BUFFER(z_interrupt_stacks[cpu_id]);
end = start + CONFIG_ISR_STACK_SIZE;
#ifdef CONFIG_USERSPACE
/* TODO: handle user threads */
#endif
} else {
start = _current->stack_info.start;
end = Z_STACK_PTR_ALIGN(_current->stack_info.start + _current->stack_info.size);
}

return (addr >= start) && (addr < end);
#else
ARG_UNUSED(addr);
return true;
#endif /* CONFIG_THREAD_STACK_INFO */
}

static inline bool in_text_region(uintptr_t addr)
{
extern uintptr_t __text_region_start, __text_region_end;

return (addr >= (uintptr_t)&__text_region_start) && (addr < (uintptr_t)&__text_region_end);
}

static void unwind_stack(const z_arch_esf_t *esf)
{
uintptr_t fp = esf->s0;
uintptr_t ra;
struct stackframe *frame;

LOG_ERR("Call trace begin:");

for (int i = 0; (i < MAX_STACK_FRAMES) && (fp != 0U) && in_stack_bound((uintptr_t)fp);) {
frame = (struct stackframe *)fp - 1;
ra = frame->ra;
if (in_text_region(ra)) {
LOG_ERR(" %2d: fp: " PR_REG " ra: " PR_REG, i, (uintptr_t)fp, ra);
/*
* Increment the iterator only if `ra` is within the text region to get the
* most out of it
*/
i++;
}
fp = frame->fp;
}

LOG_ERR("Call trace end");
}
#endif /* CONFIG_RISCV_EXCEPTION_STACK_TRACE */

FUNC_NORETURN void z_riscv_fatal_error(unsigned int reason,
const z_arch_esf_t *esf)
{
Expand All @@ -54,6 +127,9 @@ FUNC_NORETURN void z_riscv_fatal_error(unsigned int reason,
LOG_ERR(" mepc: " PR_REG, esf->mepc);
LOG_ERR("mstatus: " PR_REG, esf->mstatus);
LOG_ERR("");
#ifdef CONFIG_RISCV_EXCEPTION_STACK_TRACE
unwind_stack(esf);
#endif /* CONFIG_RISCV_EXCEPTION_STACK_TRACE */
}
#endif /* CONFIG_EXCEPTION_DEBUG */
z_fatal_error(reason, esf);
Expand Down
9 changes: 9 additions & 0 deletions tests/arch/riscv/stack_unwind/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Copyright (c) 2024 Meta
# SPDX-License-Identifier: Apache-2.0

cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(riscv_stack_unwind)

FILE(GLOB app_sources src/*.c)
target_sources(app PRIVATE ${app_sources})
11 changes: 11 additions & 0 deletions tests/arch/riscv/stack_unwind/prj.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
CONFIG_TEST=y

CONFIG_LOG=y
CONFIG_LOG_BUFFER_SIZE=2048

CONFIG_EXCEPTION_STACK_TRACE=y
CONFIG_OVERRIDE_FRAME_POINTER_DEFAULT=y
CONFIG_OMIT_FRAME_POINTER=n
CONFIG_DEBUG=y
CONFIG_DEBUG_INFO=y
CONFIG_DEBUG_OPTIMIZATIONS=y
64 changes: 64 additions & 0 deletions tests/arch/riscv/stack_unwind/src/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* Copyright (c) 2024 Meta
*
* SPDX-License-Identifier: Apache-2.0
*/

/*
* This generates the following output on qemu_riscv64:
*
* *** Booting Zephyr OS build v3.6.0-677-g999a2edd1f1e ***
* Hello World! qemu_riscv64
* func1
* err
* E:
* E: mcause: 2, Illegal instruction
* E: mtval: 77777777
* E: a0: 0000000000000000 t0: 0000000000000000
* E: a1: 0000000010000000 t1: 0000000000000000
* E: a2: 000000000000000a t2: 0000000000000000
* E: a3: 000000008000c210 t3: 0000000000000000
* E: a4: 000000008000c210 t4: 0000000000000000
* E: a5: 000000008000c210 t5: 0000000000000000
* E: a6: 0000000000000000 t6: 0000000000000000
* E: a7: 0000000000000000
* E: ra: 0000000080000446
* E: mepc: 0000000080000446
* E: mstatus: 0000000a00021880
* E:
* E: Call trace begin:
* E: 0: fp: 000000008000eb40 ra: 000000008000047a
* E: 1: fp: 000000008000eb60 ra: 000000008000049e
* E: 2: fp: 000000008000eb70 ra: 00000000800023b0
* E: 3: fp: 000000008000eb80 ra: 000000008000082e
* E: Call trace end
* E: >>> ZEPHYR FATAL ERROR 0: CPU exception on CPU 0
* E: Current thread: 0x8000c210 (main)
* E: Halting system
*/

#include <stdio.h>
#include <stdbool.h>

static void err(void)
{
printf("%s\n", __func__);
__asm__ volatile (".word 0x77777777");
}

static void func1(bool a)
{
printf("%s\n", __func__);
if (a) {
err();
}
}

int main(void)
{
printf("Hello World! %s\n", CONFIG_BOARD);

func1(true);

return 0;
}
10 changes: 10 additions & 0 deletions tests/arch/riscv/stack_unwind/testcase.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
tests:
arch.riscv.stack_unwind:
arch_allow: riscv
harness: console
harness_config:
type: multi_line
regex:
- ".* Call trace begin:"
- ".* 0: fp: \\w+ ra: \\w+"
- ".* 1: fp: \\w+ ra: \\w+"

0 comments on commit 7a5c717

Please sign in to comment.