Rust in Everything
基于 probe-rs-debug 0.32.0

调试信息

03 ARM 调试架构讲到核停下来、寄存器读得出来为止。到这一步,调试器手里只有一串数:PC 是 0x080008dc,SP 是 0x200bff80,LR 是 0x0800087b。把它们变成「停在 hf_inner 的第 81 行,它是从 HardFault 里调用的,HardFault 打断了 svc_inner……」,靠的是固件 ELF 里的 DWARF 调试信息,这是 probe-rs-debug 这个 crate 的工作。它是调试主线的后半段。

三章:

  1. DebugInfo 与 DWARF:DebugInfo 从 ELF 里读哪些节(只有 .debug_frame,不读 .eh_frame);每个编译单元一个 UnitInfo,按地址找函数 DIE 和内联函数;地址怎么查到源码行;变量的类型和位置表达式怎么求值。
  2. 栈展开:CFI 与异常帧:unwind 的三段循环,CFA 和寄存器规则,Thumb 下 PC 为什么取 (LR - 2) & !1;遇到 EXC_RETURN 时 ExceptionInterface 怎么从栈上取回被打断的寄存器,Armv6/7-M 与 Armv8-M 各算多大的异常帧;三个 coredump 夹具的展开结果,以及 coredump 缺内存时展开停在哪一帧。
  3. 变量与单步:VariableCache 这棵树和延迟展开的规则,Rust 类型的特殊处理;SteppingMode 怎么用行表和一个借来的硬件断点实现 step over / into / out。
flowchart TB
  regs["寄存器:PC、SP、LR、xPSR(03 部分)"] --> di["DebugInfo:DWARF 各节、UnitInfo"]
  di --> unwind["unwind:CFI 一帧帧往回推"]
  unwind --> exc["ExceptionInterface:跨过异常帧"]
  exc --> unwind
  unwind --> frames["StackFrame 列表"]
  frames --> vars["VariableCache:按需展开变量"]
  di --> step["SteppingMode:行表加硬件断点"]
  frames --> dap["09 DAP server:VS Code 的 stackTrace、variables"]
  vars --> dap
  step --> dap

只认 MemoryInterface

probe-rs-debug 不依赖任何具体的探针或目标。它的入口函数都只要一个 &mut impl MemoryInterface(读内存)和一组寄存器值。活的目标上,这两样来自 Core;事后分析时,来自一个 CoreDump。CoreDump 实现了 MemoryInterface(会话与目标描述),所以同一套展开代码既能接一块正在调试的板子,也能接一个文件。仓库的测试就是这么做的:tests/debug-unwind-tests/ 下放了一批从真实芯片上抓的 coredump 和对应的 ELF,展开结果用 insta 存成快照,改了展开逻辑就会看到快照的差异。

本卷没有开发板,这一部分的实验全部建立在这些夹具上:自己的实验程序依赖 probe-rs-debug =0.32.0,把夹具文件读进来,调公开的 DebugInfo::unwind、VariableCache::recurse_deferred_variables、DebugInfo::get_source_location。单步需要能动的核,用本卷 03 部分用过的软件模拟目标代替(它不执行指令,只模拟调试寄存器),结论都注明「模拟目标」。真实板子上的展开对照待实测。

主要文件

probe-rs-debug/src/ 共 11 912 行(含文件内测试),依赖 gimli 0.34、addr2line 0.27、object 0.39:

文件行数管什么章
debug_info.rs2 190DebugInfo、unwind、CFA 与寄存器规则、源码行1、2
unit_info.rs、function_die.rs2 466 / 308编译单元、函数 DIE、类型与位置表达式1、3
exception_handling.rs、exception_handling/2 001ExceptionInterface 与 Armv6-M / v7-M / v8-M、RISC-V、Xtensa 的实现2
variable.rs、variable_cache.rs、language/976 / 753 / 1 418变量、缓存树、Rust 与 C 的特殊处理3
debug_step.rs、source_instructions.rs460 / 604单步、合法的断点位置3
registers.rs、stack_frame.rs、lib.rs313 / 96 / 327DebugRegisters、StackFrame、错误类型与 ObjectRef2

本部分的三个夹具:

夹具核场景来源
nRF52833_xxAA_hardfault_from_usagefaultArmv7-M(Cortex-M4)执行 udf 触发 UsageFault,升级成 HardFault一个叫 probe-rs-debugger-test 的测试固件(仓库里只有它的一个 ELF,没有源码)
RP2040_full_unwindArmv6-M(Cortex-M0+)递归函数 test_deep_stack 停在第 6 层同上
stm32u585_nested_exceptionsArmv8-M(Cortex-M33)同一个 MSP 上两层嵌套异常:SVCall 里再触发 HardFaulttests/debug-unwind-tests/stm32u585-src/

夹具的 DWARF 里记着生成它的机器上的路径;本部分贴出的输出一律改写成 <项目>/src/...、<cargo registry>/...、<rustup>/...。

💬 关联讨论

发起讨论
加载中...