怎么读这一卷
主线位置:三条主线的总览。本页把命令、访问、调试三条主线各画一遍,标出每一步落在哪个文件、哪一章;再说明本卷没有开发板时怎么做实验,页面上的标注是什么意思。
flowchart TB
start[从这里开始] --> q{读这一卷为了什么}
q -->|会用| u1[01 全景]
u1 --> u2["06 烧录、07 RTT"]
u2 --> u3["10 和 embassy 连起来"]
u3 --> u4[12 常见坑]
q -->|读懂实现| all[00 到 13 按顺序]
锁定版本
| 项 | 值 |
|---|---|
| 版本 | probe-rs 0.32.0;工作区里一起发布的 probe-rs-target、probe-rs-debug、probe-rs-tools、target-gen、probe-rs-espressif、probe-rs-linux 版本号相同,probe-rs-mi 单独编号为 0.2.0 |
| tag | v0.32.0 |
| commit | 48f5e4d5(2026-07-22,chore: bump probe-rs-mi to 0.2.0 (#4163)) |
| edition / MSRV | 2024 / 1.89(根 Cargo.toml 的 rust-version) |
| 历史 | 第一个提交在 2019-02-10,到 tag 共 6391 个提交 |
| 实验用的工具 | 本机安装的 probe-rs 0.32.0(git commit: 48f5e4d,未开 remote feature);实验程序依赖 probe-rs =0.32.0,rustc 1.99.0,Apple M2 Max、macOS |
源码链接都用 tag,形如 https://github.com/probe-rs/probe-rs/blob/v0.32.0/probe-rs/src/probe.rs#L669。想在本地对照:
git clone https://github.com/probe-rs/probe-rs
cd probe-rs
git checkout v0.32.0
写作时上游 main 已经比 tag 多了 313 个提交。本卷的每一处行为、每一个行号都只对 0.32.0 负责;读 main 看到的代码可能不一样。
三条主线
每一页开头都会标出本页在哪条主线上。
命令主线(probe-rs run fw.elf):从敲下命令到终端上出现第一行 defmt 日志。
flowchart TB main["main:注册 ESP、Linux 插件"] --> rpc["run_app:本进程起 RPC 服务端"] rpc --> runcmd["cmd/run.rs:Cmd::run"] runcmd --> sel["cli::attach_probe:先 select_probe"] sel --> att["RPC attach:Probe::attach_with_registry"] att --> sess["Session::new:序列、DP、AP、核"] sess --> fl["cli::flash:FlashLoader::commit"] fl --> mon["cli::monitor:RunLoop::run_until"] mon --> poll["每 100 ms:查 halt、读 RTT"] poll --> dec["客户端:defmt 解码、打印"]
| 一步 | 源码 | 本卷 |
|---|---|---|
| 注册插件、本地起 RPC | probe-rs-tools/src/bin/probe-rs/main.rs | 一条 probe-rs run |
| 选探针、attach | util/cli.rs、rpc/functions/probe.rs、util/common_options.rs | 同上;探针怎么被找到见 DebugProbe 与驱动注册表 |
Session::new 的 attach 顺序 | probe-rs/src/session.rs | 调试序列与 vendor |
| 烧录 | probe-rs/src/flashing/ | 06 烧录 |
| 轮询、RTT、semihosting | rpc/utils/run_loop.rs、rpc/functions/monitor.rs、probe-rs/src/rtt.rs | 07 RTT、defmt 与 semihosting |
访问主线(一次 32 位读,从 API 到线上):core.read_word_32(0x2000_0000) 最后变成线上的哪几个 SWD 包。
flowchart TB api["Core::read_word_32"] --> armv7["Armv7m 的 CoreMemoryInterface"] armv7 --> adi["ADIMemoryInterface::read_32:写 TAR、读 DRW"] adi --> sel["ArmCommunicationInterface:需要时先写 DP SELECT"] sel --> raw["RawDapAccess:raw_read_register / raw_write_register"] raw --> dap["CMSIS-DAP:batch_add 攒成一个 DAP_Transfer"] raw --> poly["位级探针:polyfill 拼出 46 位的 SWD 包"]
| 一步 | 源码 | 本卷 |
|---|---|---|
ADIMemoryInterface、TAR 自增与 1 KiB 边界 | architecture/arm/memory/adi_memory_interface.rs | MEM-AP 与 ROM table |
| SELECT 缓存、DP / AP 寄存器访问 | architecture/arm/communication_interface.rs | DP、AP 与 SELECT |
| CMSIS-DAP 的批处理与块传输 | probe/cmsisdap/mod.rs | CMSIS-DAP 驱动 |
| SWD 位格式、WAIT 重试 | architecture/arm/traits/polyfill.rs | 位级探针 |
调试主线(停下来、看得懂):前半段在本卷 03 部分,后半段在 08 调试信息、09 调试服务器。
flowchart TB halt["DHCSR:C_HALT、C_STEP"] --> bp["FPB:FP_CTRL、FP_COMPn"] bp --> why["DFSR:停下的原因"] why --> regs["DCRSR / DCRDR:读寄存器"] regs --> unwind["probe-rs-debug:CFI 栈展开、异常帧"] unwind --> vars["VariableCache:变量"] vars --> dapsrv["DAP server:VS Code 请求"]
| 一步 | 源码 | 本卷 |
|---|---|---|
| halt、run、step、断点、寄存器 | architecture/arm/core/armv7m.rs、cortex_m.rs | Cortex-M 内核 |
| 栈展开、变量、单步 | probe-rs-debug/src/ | 08 调试信息 |
| DAP 与 gdb 服务器 | probe-rs-tools/src/bin/probe-rs/cmd/dap_server/、cmd/gdb_server/ | 09 调试服务器 |
阅读路线
- 只想会用:读 01 全景,然后 06 烧录、07 RTT、10 和 embassy 连起来、12 常见坑。读完能配好 cargo runner 和 VS Code,知道常见报错从哪来。
- 想读懂实现:按顺序读。02 探针层和 03 ARM 调试架构是访问主线和调试主线的前半段,后面各章都会链回来;08 调试信息是本卷另一个重点。
摘自 probe-rs 的代码片段一律保留原来的格式(4 空格缩进),方便和原文件对照;本站自己写的实验代码用 2 空格缩进。
前置知识
这一卷默认你写过 Cortex-M 上的 Rust 固件,至少知道下面几件事。它们在 embassy 卷里从固件一侧出现过,这里只列 probe-rs 会碰到的部分:
| 概念 | 本卷用到它的地方 |
|---|---|
| Cortex-M 的 16 个核心寄存器(R0–R12、SP、LR、PC)和 xPSR,Thumb 状态 | 调试器通过 DCRSR / DCRDR 一个一个地读写它们;reset_and_halt 之后要检查 xPSR 的 Thumb 位 |
| 向量表:第 0 项是初始 SP,第 1 项是复位向量(中断绑定看过它在 ELF 里的样子) | 在 RAM 里运行固件时,probe-rs 手动设 SP、PC 和 VTOR |
异常进入时硬件自动压栈 8 个字,LR 里放 EXC_RETURN | 栈展开跨过异常帧(栈展开) |
BKPT 指令;BKPT 0xAB 是 semihosting 调用,embassy 卷在 QEMU 上用 hprintln! 走的就是它 | 核停在 BKPT 上时,probe-rs 要分辨是断点还是 semihosting |
系统控制空间 SCS(0xE000_E000 起)是一段内存映射的寄存器 | 调试器的一切操作都是对这段地址的读写 |
调试端口一侧的知识(SWD、DP、AP)本卷从头讲,不需要先读 ARM 的规范。
没有开发板时怎么做实验
本机没有调试探针和开发板。本卷的实验分五类,页面上会写明用的是哪一类:
| 办法 | 能看到什么 | 看不到什么 |
|---|---|---|
本机 probe-rs 的无硬件命令:chip list、chip info、list、--help | 内置目标的数量、内存布局、命令行参数 | 任何和探针通信的行为 |
probe-rs gdb --dry-run + macOS 自带 lldb 的 gdb-remote | 每条调试命令变成了哪些调试寄存器的读写(FakeProbe 的模拟核会把每次访问打印出来) | DP / AP 层:FakeProbe 在内存接口这一层就截住了访问 |
FakeProbe(test feature) | attach 流程、halt / step 的寄存器序列、按 ELF 的加载地址读出的内容 | RAM 里的变量(读出来都是 0)、断点(比较器个数是 0);碰到 25 处 todo! / unimplemented! 之一就 panic,见 FakeProbe |
软件模拟的 SWD 目标:实验程序自己实现 RawDapAccess 或 RawSwdIo,背后是一个模拟的 SW-DP、AHB-AP、内存和 Cortex-M 调试寄存器 | probe-rs 自己的代码(attach 序列、ArmCommunicationInterface、MEM-AP、polyfill)实际发出的每一次 DP / AP 访问、每一个 SWD 包、每一个 SWCLK 周期 | 真实芯片的时序、WAIT 应答、掉电和复位行为;模拟目标的寄存器值是照规范填的,不代表任何一颗具体芯片 |
| QEMU 加自己写的 GDB 远程协议客户端(只在 13 动手和常见坑里用) | 一个真在执行指令的固件:调用栈、RAM 里的 RTT 缓冲区,交给 probe-rs-debug 展开 | probe-rs 自己的探针和架构层:QEMU 的 GDB 服务端在内存接口这一层就接管了访问 |
第四种办法是本卷的主力:探针 trait 是公开的,ArmCommunicationInterface::create 也是公开的,所以可以在库外接上一个假的探针,让 probe-rs 的原生代码一路跑到「线上」。用它测出来的事务数、周期数都注明「模拟目标」,它们反映的是 probe-rs 的代码行为,而不是硬件。
页面上的标注:
- 待实测:结论需要真硬件,本机没法验证。这些项登记在内容仓库的硬件待测清单里,有了板子集中测一次再回填。
- 未核实:从源码或文档推出来、但没有实验支撑的说法。
- 实验输出里的本机路径一律改写成
<项目>/src/...、<cargo registry>/...这样的形式。
目录
| 部分 | 内容 |
|---|---|
| 00 导读 | 本部分;源码地图 |
| 01 全景 | probe-rs 解决什么问题;一条 probe-rs run 从命令到日志 |
| 02 探针层 | 驱动注册表;位级探针与 polyfill;CMSIS-DAP;FakeProbe |
| 03 ARM 调试架构 | 本卷重点:DP、AP 与 SELECT;MEM-AP 与 ROM table;Cortex-M 内核;调试序列与 vendor |
| 04 RISC-V 与 Xtensa | RISC-V 调试模块;Xtensa 与 ESP 插件 |
| 05 会话与目标描述 | Session、Core 与 MemoryInterface;YAML 到注册表;target-gen |
| 06 烧录 | 烧录计划;flash 算法 |
| 07 RTT、defmt 与 semihosting | RTT 控制块;defmt 与 semihosting |
| 08 调试信息 | 本卷另一个重点:DWARF、栈展开、变量与单步 |
| 09 调试服务器 | DAP server、gdb server、RPC 架构 |
| 10 和 embassy 连起来 | cargo runner 与 defmt 三件套;从 QEMU 换到真板子;VS Code 调试配置 |
| 11 设计取舍 | 把各章的取舍汇总到一起 |
| 12 常见坑 | 用报错原文做标题,六个坑 |
| 13 动手 | 写一个最小的软件探针;QEMU 的 GDB 服务端接 probe-rs-debug |
小结
- 全卷锁定 tag
v0.32.0(48f5e4d5,2026-07-22);probe-rs-mi在同一个 tag 里,版本是 0.2.0。 - 命令主线:插件 → 本地 RPC → 选探针 → attach → 烧录 → 每 100 ms 轮询 halt 与 RTT;访问主线:
read_word_32→ TAR / DRW → SELECT → CMSIS-DAP 批处理或 polyfill 位流;调试主线:DHCSR、FPB、DFSR、DCRSR → DWARF 展开 → DAP server。 - 只想会用读 01、06、07、10、12;想读懂实现按顺序读,02、03、08 是重点。
- 没有开发板:用无硬件命令、
gdb --dry-run+ lldb、FakeProbe,以及自己写的软件模拟 SWD 目标;需要真硬件的数字写「待实测」。