arm-none-eabi-addr2line概述:
arm-none-eabi-addr2line
是 GNU Binutils 软件包的一部分,专门用于 ARM Cortex-M 等嵌入式系统的调试工具。它的主要功能是将程序中的地址转换为源代码中的文件名和行号,这对于调试和诊断程序中的错误特别有帮助。
是 ARM 嵌入式开发中重要的调试工具,尤其在程序崩溃或未知问题发生时,能够帮助开发者快速定位问题所在。通过将地址转换为可读的文件和行号信息,它极大地加快了调试过程。
此工具可以将一个内存地址或程序计数器值(PC值)映射回生成该机器指令的源代码文件及行号。它对于从调试信息中恢复源代码位置很有用,特别是在定位内存地址或分析错误时。通常情况下,这种地址信息来自于内存转储(例如硬件故障或程序崩溃时的堆栈回溯)。
arm-none-eabi-addr2line –help:
Usage: arm-none-eabi-addr2line.exe [option(s)] [addr(s)]
Convert addresses into line number/file name pairs.
If no addresses are specified on the command line, they will be read from stdin
The options are:
@<file> Read options from <file>
-a --addresses Show addresses
-b --target=<bfdname> Set the binary file format
-e --exe=<executable> Set the input file name (default is a.out)
-i --inlines Unwind inlined functions
-j --section=<name> Read section-relative offsets instead of addresses
-p --pretty-print Make the output easier to read for humans
-s --basenames Strip directory names
-f --functions Show function names
-C --demangle[=style] Demangle function names
-R --recurse-limit Enable a limit on recursion whilst demangling. [Default]
-r --no-recurse-limit Disable a limit on recursion whilst demangling
-h --help Display this information
-v --version Display the program's version
arm-none-eabi-addr2line.exe: supported targets: elf32-littlearm elf32-littlearm-fdpic elf32-bigarm elf32-bigarm-fdpic elf32-little elf32-big srec symbolsrec verilog tekhex binary ihex plugin
Report bugs to <http://www.sourceware.org/bugzilla/>
翻译为中文:
用法: arm-none-eabi-addr2line.exe [选项] [地址]
将地址转换为行号/文件名对。
如果命令行中未指定地址,将从标准输入中读取地址。
可用选项如下:
@<文件> 从<文件>中读取选项
-a --addresses 显示地址
-b --target=<bfdname> 设置二进制文件格式
-e --exe=<可执行文件> 设置输入文件名(默认为 a.out)
-i --inlines 展开内联函数
-j --section=<名称> 读取相对于段的偏移量而不是地址
-p --pretty-print 使输出更易于人类阅读
-s --basenames 去掉目录名
-f --functions 显示函数名称
-C --demangle[=风格] 解码函数名称
-R --recurse-limit 启用解码时递归限制(默认)
-r --no-recurse-limit 禁用解码时的递归限制
-h --help 显示此信息
-v --version 显示程序版本
arm-none-eabi-addr2line.exe: 支持的目标格式:elf32-littlearm elf32-littlearm-fdpic elf32-bigarm elf32-bigarm-fdpic elf32-little elf32-big srec symbolsrec verilog tekhex binary ihex plugin
报告错误到 <http://www.sourceware.org/bugzilla/>
应用场景:
- 嵌入式系统调试:当嵌入式系统崩溃时,开发者可以从错误日志中获取崩溃时的内存地址(例如硬件异常或断言失败时)。通过
arm-none-eabi-addr2line
工具,可以轻松定位问题出现在源代码的具体位置。 - 堆栈回溯分析:在执行堆栈回溯时,可以将所有函数调用的地址转换成相应的代码行,帮助开发者快速找到潜在的问题。
使用技巧:
- 带符号表的编译:要使用
arm-none-eabi-addr2line
工具,程序需要在编译时保留调试符号(通过编译选项-g
)。 - 无符号表的处理:如果 ELF 文件没有调试符号,
arm-none-eabi-addr2line
将无法提供详细的源代码行号信息,而只能给出模块的偏移地址。 - 结合 GDB 使用:在调试会话中,
arm-none-eabi-addr2line
通常和 GDB 一起使用,用于快速查找源代码中的错误。
分析死机或HardFault异常原因:
当程序死机时,可以通过 PC 和 LR 的结合,分析程序死机的原因。 应该优先读取 PC(程序计数器) 指针来排查错误,但 LR(链接寄存器) 指针同样有重要参考价值。
-
PC(程序计数器):指向当前正在执行的指令地址,是排查错误的主要线索。通过读取 PC,可以确定程序崩溃时正在执行的具体代码位置,有助于分析出问题所在(例如非法指令或访问非法地址)。
-
LR(链接寄存器):通常保存调用函数的返回地址。如果程序因为函数调用错误导致崩溃,LR 可以帮助你确定导致问题的调用链。当程序执行
BL
(Branch with Link)指令时,LR 会保存调用函数的返回地址。在某些情况下,LR 比 PC 更有助于追溯崩溃前的执行路径,尤其是当 PC 指向了异常处理程序时。
HardFault和死机等错误查找使用示例:
程序arm-none-eabi-addr2line.exe
编译后的文件:nrf52832_xxaa.axf
使用nrfjprog --readregs获取PC:0x0002C2E2 LR:0x00029147
执行指令:
arm-none-eabi-addr2line -e .\nrf52832_xxaa.axf -a -f 0x0002C2E2 0x00029147
得到结果 可能出现问题地方:
0x0002c2e2
wakeup_button_cfg
../clib/../cmprslib/zerorunl2.c:?
0x00029147
nrfx_coredep_delay_us
nRF5_SDK_17.1.0_ddde560\examples\ble_peripheral\ble_app_uart\pca10040\s132\arm5_no_packs/..\..\..\..\..\..\modules\nrfx\/soc/nrfx_coredep.h:173
使用nrfjprog –readregs获取到寄存器信息示例如下:
获取到的寄存器信息:
R0: 0x0000C6BE
R1: 0x0002C2E1
R2: 0x00000000
R3: 0x20002E00
R4: 0x0002C2E0
R5: 0x0000001A
R6: 0x0000FA00
R7: 0x00000000
R8: 0x00000000
R9: 0x00000000
R10: 0x00000000
R11: 0x00000000
R12: 0x0000000C
SP: 0x20005DD0
LR: 0x00029147
PC: 0x0002C2E2
xPSR: 0x21000000
MSP: 0x20005DD0
PSP: 0x00000000
RAZ: 0x00000000
CFBP: 0x04000000
APSR: 0x20000000
EPSR: 0x01000000
IPSR: 0x00000000
通用寄存器 (R0 - R12)
- R0 - R12: 这是 ARM Cortex-M 处理器的12个通用寄存器。它们用于存储处理中的临时数据或函数参数。寄存器可以在函数调用中传递参数,也可以用于保存局部变量。
- R0: 0x0000C6BE:存储了一个中间结果或局部变量。
- R1: 0x0002C2E1:可能是另一个函数参数或计算结果。
- R2 - R12: 这些寄存器可能在不同的时刻存储不同的数据,具体内容和意义依赖于当前正在执行的程序。
堆栈指针 (SP) 和 链接寄存器 (LR)
- SP (Stack Pointer, 堆栈指针):
SP
指向当前堆栈顶的位置,用于保存局部变量、返回地址等。- SP: 0x20005DD0: 当前堆栈指针指向内存地址
0x20005DD0
。
- SP: 0x20005DD0: 当前堆栈指针指向内存地址
- LR (Link Register, 链接寄存器):
LR
保存函数调用返回地址。执行完函数时,处理器会跳回到这个地址。- LR: 0x00029147: 当前的函数返回地址为
0x00029147
,指向下一个指令。
- LR: 0x00029147: 当前的函数返回地址为
程序计数器 (PC)
- PC (Program Counter, 程序计数器):
PC
保存了当前执行指令的地址。- PC: 0x0002C2E2: 当前正在执行的指令位于地址
0x0002C2E2
。
- PC: 0x0002C2E2: 当前正在执行的指令位于地址
程序状态寄存器 (xPSR)
xPSR
是 Cortex-M 处理器中的一个综合状态寄存器,它由以下三个部分组成:
- APSR (Application Program Status Register): 保存程序的状态信息,如条件码。
- APSR: 0x20000000:
N
位设置,表示上一次运算的结果为负值。
- APSR: 0x20000000:
- EPSR (Execution Program Status Register): 保存指令执行状态。
- EPSR: 0x01000000: Thumb状态位
T
为1,说明处理器正以Thumb指令集执行程序。
- EPSR: 0x01000000: Thumb状态位
- IPSR (Interrupt Program Status Register): 保存中断号信息。
- IPSR: 0x00000000: 当前没有中断正在执行。
其他状态寄存器
- MSP (Main Stack Pointer): 这是主堆栈指针,用于系统和异常处理模式。
- MSP: 0x20005DD0: 主堆栈指针和堆栈指针
SP
指向同一个地址,表示当前正处于主模式。
- MSP: 0x20005DD0: 主堆栈指针和堆栈指针
- PSP (Process Stack Pointer): 这是进程堆栈指针,用于用户模式下的进程。
- PSP: 0x00000000: 当前未使用进程堆栈指针,可能是在系统模式下执行。
- CFBP (Control, Faultmask, Basepri, and Primask): 控制寄存器,用于管理异常优先级和屏蔽。
- CFBP: 0x04000000: 表示
Basepri
设置为0x04
,用于异常优先级控制。
- CFBP: 0x04000000: 表示
其他值
- RAZ: 保留值,通常为0。
- RAZ: 0x00000000: 保持为 0。
重点介绍:
1. 堆栈指针 (SP)
- SP (Stack Pointer) 是一个指向当前堆栈顶部的寄存器。它在函数调用和返回过程中更新,以便跟踪局部变量和函数参数的存储。
- 在 Cortex-M 处理器中,SP 的具体值会根据当前执行的上下文变化。通常,SP 会在进入一个函数时向下移动(降低),在退出函数时向上移动(增加)。
2. 主堆栈指针 (MSP)
- MSP (Main Stack Pointer) 是用于主堆栈的指针,通常在处理器复位后初始化并用于所有的特权级(如处理器进入异常模式时)。
- MSP 用于系统的默认堆栈,通常由系统调用、ISR(中断服务例程)和上下文切换等情况使用。
- 当处理器处于 异常模式(如中断、故障等)时,MSP 是默认使用的堆栈指针。
3. 进程堆栈指针 (PSP)
- PSP (Process Stack Pointer) 是用于用户进程堆栈的指针,通常用于在操作系统环境下的用户任务。
- 当处理器切换到用户模式(例如,执行用户任务)时,PSP 是被使用的堆栈指针。
- 这使得用户程序的执行可以与系统级别的处理分开,增强了安全性和稳定性。
4. SP、MSP 和 PSP 的使用场景
- 系统启动:当 Cortex-M 处理器启动时,MSP 通常被初始化为堆栈的顶部,并且在启动时由系统使用。
- 异常处理:在处理中断或异常时,系统会使用 MSP。异常处理程序可以直接使用 MSP 堆栈进行局部变量和参数的管理。
- 任务切换:如果使用实时操作系统(RTOS),在进行任务切换时,系统会保存当前任务的 PSP,并恢复下一个任务的 PSP,从而使每个任务有独立的堆栈空间。
- 堆栈溢出:当应用程序超出分配给堆栈的内存时,可能导致堆栈溢出,从而影响当前函数或调用链,甚至导致 HardFault。通过检查 SP 的位置(如接近预定义的堆栈边界),可以检测堆栈溢出问题。
5. SP、MSP 和 PSP 寄存器的状态
在你提供的寄存器信息中:
- SP 为
0x20005DD0
,表示当前堆栈指针的位置,可能表示当前正在使用的堆栈。 - MSP 也为
0x20005DD0
,表明在崩溃发生时,MSP 和 SP 可能指向相同的内存地址,这意味着可能使用了主堆栈进行操作。 - PSP 为
0x00000000
,表示当前未在使用进程堆栈。
hardfault_handler_keil.c 函数解释
需要打开宏
#ifndef HARDFAULT_HANDLER_ENABLED
#define HARDFAULT_HANDLER_ENABLED 1
#endif
需要在keil中此处(Preprocessor Symbols)加入宏
DEBUG
DEBUG_NRF NRF
需要加入文件:nRF5_SDK_17.1.0_ddde560\components\libraries\hardfault\hardfault_implementation.c
需要加入文件:nRF5_SDK_17.1.0_ddde560\components\libraries\hardfault\nrf52\handler\hardfault_handler_keil.c
主处理逻辑:
- 使用 `ldr` 指令加载 `HardFault_c_handler` 地址,并根据链接寄存器(LR)的状态判断使用的堆栈(PSP 或 MSP)。
- 如果使用 PSP,直接调用 C 语言的处理函数。
- 如果使用 MSP,检查栈指针是否在有效范围内;如果不在有效范围内,移动栈指针到合适的位置。
- 最后跳转到处理函数继续执行。
这段代码的核心目的是在发生硬Fault时,通过汇编级别的处理程序将控制权转移到 C 语言的处理函数,确保系统能够正确处理错误。
__ASM void HardFault_Handler(void) // 定义一个汇编函数,用于处理硬Fault异常
{
PRESERVE8 // 指示编译器保持 8 字节对齐,确保堆栈的正确性
EXTERN HardFault_c_handler // 声明外部的 C 函数,用于处理硬Fault
EXTERN |STACK$$Base| // 声明栈的基地址
EXTERN |STACK$$Limit| // 声明栈的限制地址
ldr r3, =HardFault_c_handler // 将处理函数的地址加载到寄存器 r3 中
tst lr, #4 // 测试链接寄存器(LR)第 2 位,判断使用的堆栈类型
/* PSP is quite simple and does not require additional handler */
itt ne // 条件执行指令,如果测试结果为不等,则执行后面的指令
mrsne r0, psp // 如果使用 PSP,将其值读取到寄存器 r0 中
/* Jump to the handler, do not store LR - returning from handler just exits exception */
bxne r3 // 跳转到 C 处理函数,不保存 LR
/* Processing MSP requires stack checking */
mrs r0, msp // 读取当前的 MSP 值到寄存器 r0
ldr r1, =|STACK$$Limit| // 将栈限制加载到寄存器 r1
ldr r2, =|STACK$$Base| // 将栈基地址加载到寄存器 r2
/* MSP is in the range of the stack area */
cmp r0, r1 // 比较当前 MSP 和栈限制
bhi HardFault_MoveSP // 如果 MSP 大于栈限制,跳转到移动栈指针处理
cmp r0, r2 // 比较当前 MSP 和栈基地址
bhi HardFault_Handler_Continue // 如果 MSP 大于栈基地址,继续处理
HardFault_MoveSP // 标签,表示进入栈指针移动处理
mov sp, r1 // 将栈指针(SP)移动到栈限制
mov r0, #0 // 将 r0 清零,为后续处理做准备
HardFault_Handler_Continue // 标签,表示继续处理硬Fault
bx r3 // 跳转到 C 处理函数
ALIGN // 对齐指令,确保后续代码的对齐
} // 结束 HardFault_Handler 函数定义
新手必知
海量第三方学习资源.
超全常用工具与文档.
本站常用资源下载.
常见问题搜索.
QQ群: 542294007.
文章引用自:元仓库 OLIB.cn.