Vela-Backtrace 使用指南

一、 概述

在日常开发中,查看指定线程的栈信息是常见需求,尤其在处理死锁、高CPU使用率、忙循环、系统崩溃或内存调试等场景时。通常,开发者可借助JLink等调试工具,通过gdb和断点(bp)实现这些功能,但设备封包发布后,外围调试功能关闭,这些方法在真实设备上往往失效。为解决这一限制,openvela支持在运行环境中直接查看特定线程的栈信息。当程序崩溃或线程阻塞时,backtrace(回溯/堆栈跟踪)能清晰展示从当前执行点到初始调用者的完整调用链,是诊断死锁、崩溃等问题的核心工具。

二、 配置说明

1. 通用配置说明

openvela 支持 ARM、RISC-V 和 Xtensa 三种架构的通用配置,具体如下:

# 开启 backtrace 功能,默认不显示函数名称
CONFIG_SCHED_BACKTRACE=y
CONFIG_SYSTEM_DUMPSTACK=y

# 如果需要符号名支持,启用以下选项;  
# 若 Flash 空间不足,可以通过 addr2line 手动解析地址到符号  
CONFIG_ALLSYMS=y

# 启用架构支持  
CONFIG_ARCH_HAVE_BACKTRACE=y

其中,对于ARM 平台,由于代码资源限制及 Thumb-2 特性的要求,提供以下三种 backtrace 实现方式供选择:

# 基于帧指针(frame pointer)的回溯实现
# 原理:依赖编译器生成的固定栈帧结构(如 x86 的 EBP 链)
# 优势:实现简单,无额外内存开销
# 限制:需编译器强制 -fno-omit-frame-pointer,性能损失约 3-5%
CONFIG_UNWINDER_FRAME_POINTER

# 基于栈指针(stack pointer)的回溯实现 
# 原理:暴力扫描栈内存识别返回地址模式
# 优势:不依赖编译器选项,支持无帧指针优化(-O2)
# 风险:可能误判数据为代码地址(错误率约 0.7%)
CONFIG_UNWINDER_STACK_POINTER

# 基于 ARM 架构的 .exidx 段的回溯实现
# 原理:解析 ELF 中 .exidx 段的展开指令(ARM EABI 标准)
# 优势:100% 精确回溯,支持异常处理栈展开
# 依赖:需链接器生成 .exidx 段(-funwind-tables 编译选项)
CONFIG_UNWINDER_ARM

目前,openvela 支持 ARM、RISC-V 和 Xtensa 三种体系结构的 backtrace 功能,各架构的配置和实现如下。

2. ARM

  • ARM Cortex-A/R

在 ARM Cortex-A 和 Cortex-R 系列中,backtrace 实现支持以下两种方式,选择其中一种即可:

# 方式 1:基于 fp 寄存器的回溯实现  
CONFIG_UNWINDER_FRAME_POINTER

# 方式 2:基于 .exidx 段的回溯实现 
CONFIG_UNWINDER_ARM
  - ARM Cortex-M

在 ARM Cortex-M 系列中,backtrace 有以下三种实现方式,根据项目需求选择其一:

# 方式 1:基于 fp 的回溯实现(需注意编译器限制)  
CONFIG_UNWINDER_FRAME_POINTER=y  

# 方式 2:基于 sp 的回溯实现,适用于小资源设备  
CONFIG_UNWINDER_STACK_POINTER=y  

# 方式 3:基于 .exidx 段的回溯实现  
CONFIG_UNWINDER_ARM=y
  • CONFIG_UNWINDER_FRAME_POINTER:GCC 编译器上存在相关限制
  • CONFIG_UNWINDER_STACK_POINTER:通过解析 bl 和 blx 指令来获取程序计数器(PC)地址,特别适用于资源有限的嵌入式设备,但由于指令解析的启发式方法,存在一定的误判概率。如果项目代码被分散到多个区域,需要通过以下 API 配置多个代码区域:
void up_backtrace_init_code_regions(FAR void **regions)
  • CONFIG_UNWINDER_ARM:该栈回溯机制在编译链接阶段生成异常处理索引表(.exidx段),实现精确的指令级栈展开,但会导致最终代码体积增加约5%-8%。

3. RISC-V

在 RISC-V 架构中,backtrace 基于帧指针(frame pointer)实现,只需启用以下配置:

CONFIG_FRAME_POINTER=y
CONFIG_SCHED_BACKTRACE=y

4. Xtensa

在 Xtensa 架构中,backtrace 默认支持栈回溯,不需要额外启用 -fno-omit-frame-pointer 选项。

三. 具体案例及心得

系统因为某些原因 crash, 除开类似 core dump 这类核心转储工具外,log 本身也能提供很多直观的信息给用户。我们必须优先分析dump日志以捕获第一现场的关键信息,例如内存状态、寄存器值、调用堆栈和异常类型等核心数据,这些信息能精确还原崩溃瞬间的系统环境,帮助快速定位根本原因,避免后续调试中的信息丢失或误判。下面以一份研发手表crash日志为例(资源已提供下载),讲述如何对 crash log 进行解析。

要先找到是哪个 core 引起的crash,即看第一现场:

[2025-07-01 13:51:33.176] [12/31 00:00:24] [ 0] [cp] dump_assert_info: Assertion failed panic: at file: /home/work/ssd1/workspace/Vela-o62-rel-5.0-Build/vendor/bes/boards/best1600_ep/common/src/bes_global_shmem.h:100 task: Idle_Task process: Kernel 0x1404c619
[2025-07-01 13:51:33.179] [12/31 00:00:24] [ 0] [cp] up_dump_register: R0: 20533150 R1: 00000064 R2: 00000037  R3: 00000007
[2025-07-01 13:51:33.180] [12/31 00:00:24] [ 0] [cp] up_dump_register: R4: 20532d88 R5: 34210e0c R6: 34210820  R7: 20540ab8
[2025-07-01 13:51:33.180] [12/31 00:00:24] [ 0] [cp] up_dump_register: R8: 00000000 R9: 00000224 R10: 20532e50 R11: 00000064
[2025-07-01 13:51:33.182] [12/31 00:00:24] [ 0] [cp] up_dump_register: R12: 00000000 SP: 20540aa0 LR: 1404cf2f  PC: 1404cf2f
[2025-07-01 13:51:33.183] [12/31 00:00:24] [ 0] [cp] up_dump_register: xPSR: 68030037 BASEPRI: 00000000 CONTROL: 00000004
[2025-07-01 13:51:33.184] [12/31 00:00:24] [ 0] [cp] up_dump_register: EXC_RETURN: 00000000
[2025-07-01 13:51:33.184] [12/31 00:00:24] [ 0] [cp] dump_stackinfo: IRQ Stack:
[2025-07-01 13:51:33.186] [12/31 00:00:24] [ 0] [cp] dump_stackinfo:   base: 0x20540400
[2025-07-01 13:51:33.186] [12/31 00:00:24] [ 0] [cp] dump_stackinfo:   size: 00002048
[2025-07-01 13:51:33.187] [12/31 00:00:24] [ 0] [cp] dump_stackinfo:     sp: 0x20540aa0
[2025-07-01 13:51:33.187] [12/31 00:00:24] [ 0] [cp] stack_dump: 0x20540a80: 20540aa0 20540400 20540ab8 205d03d0 000007f0 20532e50 205d0bc0 1404cebd
[2025-07-01 13:51:33.189] [12/31 00:00:24] [ 0] [cp] stack_dump: 0x20540aa0: 34210e0c 00000064 20532e2c 34214f84 1404c619 00000000 20540b08 00000000
[2025-07-01 13:51:33.190] [12/31 00:00:24] [ 0] [cp] stack_dump: 0x20540ac0: 20540c00 00000006 20532d88 20533150 34210820 34210e0c 00000064 7474754e
[2025-07-01 13:51:33.191] [12/31 00:00:24] [ 0] [cp] stack_dump: 0x20540ae0: 00000058 2050c864 2050c880 14007a67 61695848 20696d6f 63746157 34532068
[2025-07-01 13:51:33.193] [12/31 00:00:24] [ 0] [cp] stack_dump: 0x20540b00: 49536520 3044204d 14004436 140076cb 302e3000 0000302e 14017aad 0132b3a0
[2025-07-01 13:51:33.194] [12/31 00:00:24] [ 0] [cp] stack_dump: 0x20540b20: 20540b28 6534779f 64653264 39323365 6c754a20 20312020 35323032 3a313120

可以从此处看到cp 被动 crash ,物理串口强制输出,关键字样: bes_global_shmem.h line:
100。不是第一crash现场,继续分析在sensor输出了

[2025-07-01 13:51:35.379] [12/31 00:00:24] [ 0] [sen] Panic remote cpu ap:
[2025-07-01 13:51:35.379] [12/31 00:00:24] [ 0] [sen] Panic remote cpu cp:

可知案发现场是sensor核中某些错误使得ap、cpb被动crash。根据sensor打印的back trace信息

[sen] sched_dumpstack: backtrace| 0: 0x00613680 0x00618af2 0x006136b2 0x00617fd6 0x00618262 0x0063c98c 0x006014de 0x00631390
[sen] sched_dumpstack: backtrace| 0: 0x00633ea0 0x00603982 0x006381ce 0x00634454 0x006174cc 0x006036fe 0x00633e0c 0x0063bade

利用./prebuilts/gcc/linux/arm/bin/arm-none-eabi-addr2line -ife
~/vela_ap.elf [backtrace]解析出错误的堆栈信息

syslog
??:?
dump_task
/home/work/ssd1/workspace/Vela-o62-rel-5.0-Build/nuttx/sched/misc/assert.c:397 (discriminator 12)
sched_backtrace
/home/work/ssd1/workspace/Vela-o62-rel-5.0-Build/nuttx/sched/sched/sched_backtrace.c:106
sched_dumpstack
/home/work/ssd1/workspace/Vela-o62-rel-5.0-Build/nuttx/libs/libc/sched/sched_dumpstack.c:71
nxsched_foreach
/home/work/ssd1/workspace/Vela-o62-rel-5.0-Build/nuttx/sched/sched/sched_foreach.c:75
dump_tasks
/home/work/ssd1/workspace/Vela-o62-rel-5.0-Build/nuttx/sched/misc/assert.c:546
dump_fatal_info
/home/work/ssd1/workspace/Vela-o62-rel-5.0-Build/nuttx/sched/misc/assert.c:752
_assert
/home/work/ssd1/workspace/Vela-o62-rel-5.0-Build/nuttx/sched/misc/assert.c:907
__aeabi_uldivmod
??:?
bes_oneshot_start
/home/work/ssd1/workspace/Vela-o62-rel-5.0-Build/vendor/bes/chips/bes/bes_oneshot.c:239
bes_timer_start
/home/work/ssd1/workspace/Vela-o62-rel-5.0-Build/vendor/bes/chips/bes/bes_tickless.c:339
bes_start
/home/work/ssd1/workspace/Vela-o62-rel-5.0-Build/vendor/bes/chips/bes/bes_tickless_alarm.c:155
oneshot_tick_start
/home/work/ssd1/workspace/Vela-o62-rel-5.0-Build/nuttx/include/nuttx/timers/oneshot.h:364
up_alarm_tick_start.isra.0
/home/work/ssd1/workspace/Vela-o62-rel-5.0-Build/nuttx/drivers/timers/arch_alarm.c:354
__assert
/home/work/ssd1/workspace/Vela-o62-rel-5.0-Build/nuttx/libs/libc/assert/lib_assert.c:38
bes_rptun_check_cpu
/home/work/ssd1/workspace/Vela-o62-rel-5.0-Build/vendor/bes/boards/best1600_ep/common/src/bes_global_shmem.h:100
bes_mbtx_rx_handler
/home/work/ssd1/workspace/Vela-o62-rel-5.0-Build/vendor/bes/chips/bes/up_rptun_m552sens.c:112
hal_rmt_ipc_rx_irq_handler
/home/work/ssd1/workspace/Vela-o62-rel-5.0-Build/vendor/bes/framework/services/platform/hal/hal_rmt_ipc.c:108
up_irq_handler
/home/work/ssd1/workspace/Vela-o62-rel-5.0-Build/vendor/bes/framework/services/rtos/nuttx/nuttx_os.c:75
irq_dispatch
/home/work/ssd1/workspace/Vela-o62-rel-5.0-Build/nuttx/sched/irq/irq_dispatch.c:144
arm_doirq
/home/work/ssd1/workspace/Vela-o62-rel-5.0-Build/nuttx/arch/arm/src/armv8-m/arm_doirq.c:103
exception_common
/home/work/ssd1/workspace/Vela-o62-rel-5.0-Build/nuttx/arch/arm/src/armv8-m/arm_exception.S:225
_start
/home/work/ssd1/workspace/Vela-o62-rel-5.0-Build/vendor/bes/framework/services/tests/sensor_hub/core/sensor_hub_main.c:368

定位到是/home/work/ssd1/workspace/Vela-o62-rel-5.0-Build/vendor/bes/chips/bes/up_rptun_m552sens.c:112中,回到相关代码中看,分析出原来是up_rptun_m552sens.c文件调用bes_rptun_check_cpu函数时,该函数进入到PANIC()中。

在这里插入图片描述

通过Back trace回溯堆栈的方式我们可以列出当前函数调用关系。但如何熟练地使用调试工具非常重要。不管是GDB、WinDbg还是Visual Studio,这些工具都能生成清晰的调用栈。调用栈能够让我们快速看到程序崩溃时正在执行的函数,明白程序运行到哪一步出了问题。在复杂的程序中,调用栈清楚地展示模块之间的关系,帮助我们缩小问题范围。
但实际上调用栈只显示崩溃的函数名和地址。对程序员来说会查看源代码、日志和变量状态能更准确地找到问题。比如,检查函数的参数、变量或指针是否异常,像空指针或越界访问,就能很快发现常见错误。完整的调试信息也很重要。如果没有符号表,只能看到一堆内存地址,分析就无从下手。
理解程序的运行逻辑也是关键所在。回溯不仅告诉我程序在哪崩了,还能提示问题出现的原因。通过调用栈,可以找到问题可能的起点,检查代码逻辑、边界条件或多线程冲突。比如,栈溢出可能因为递归太深,空指针可能是对象没初始化。
对于程序员而言,每次定位崩溃都是一次学习,记录问题的原因和解决方法,积累经验。在遇到复杂问题时,结合日志、断点和回溯反复验证,就可以一步步找到答案。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值