Rust in Everything
基于 probe-rs 0.32.0

怎么读这一卷

主线位置:三条主线的总览。本页把命令、访问、调试三条主线各画一遍,标出每一步落在哪个文件、哪一章;再说明本卷没有开发板时怎么做实验,页面上的标注是什么意思。
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
tagv0.32.0
commit48f5e4d5(2026-07-22,chore: bump probe-rs-mi to 0.2.0 (#4163))
edition / MSRV2024 / 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 解码、打印"]
一步源码本卷
注册插件、本地起 RPCprobe-rs-tools/src/bin/probe-rs/main.rs一条 probe-rs run
选探针、attachutil/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、semihostingrpc/utils/run_loop.rs、rpc/functions/monitor.rs、probe-rs/src/rtt.rs07 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.rsMEM-AP 与 ROM table
SELECT 缓存、DP / AP 寄存器访问architecture/arm/communication_interface.rsDP、AP 与 SELECT
CMSIS-DAP 的批处理与块传输probe/cmsisdap/mod.rsCMSIS-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.rsCortex-M 内核
栈展开、变量、单步probe-rs-debug/src/08 调试信息
DAP 与 gdb 服务器probe-rs-tools/src/bin/probe-rs/cmd/dap_server/、cmd/gdb_server/09 调试服务器

阅读路线

摘自 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 与 XtensaRISC-V 调试模块;Xtensa 与 ESP 插件
05 会话与目标描述Session、Core 与 MemoryInterface;YAML 到注册表;target-gen
06 烧录烧录计划;flash 算法
07 RTT、defmt 与 semihostingRTT 控制块;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 目标;需要真硬件的数字写「待实测」。

💬 关联讨论

发起讨论
加载中...