源码地图
主线位置:三条主线经过的 crate 和模块。命令主线从
probe-rs-tools开始,访问主线和调试主线的前半段全在核心 crateprobe-rs里,调试主线的后半段在probe-rs-debug。本页说明这些 crate 谁依赖谁、核心 crate 内部怎么分层,以及 0.32 起插件怎么往四张全局表里加东西。
flowchart TB tools["probe-rs-tools:probe-rs、cargo-flash、cargo-embed"] --> debug[probe-rs-debug] tools --> esp[probe-rs-espressif] tools --> linux["probe-rs-linux(仅 Linux)"] tools --> mi[probe-rs-mi] tools --> core[probe-rs] tgen[target-gen] --> esp tgen --> core debug --> core esp --> core linux --> core core --> target[probe-rs-target] debug --> target esp --> target tgen --> target
工作区里的 crate
根 Cargo.toml 的 [workspace] members 有 11 个成员,其中 smoke-tester、smoke-tester-macros、xtask 标了 publish = false,是仓库自己的硬件回归测试和构建脚本,本卷不讲。另有一个 probe-rs-meta 不在成员列表里。发布到 crates.io 的是下面这些(行数是 src/ 下 .rs 的总行数,含文件内测试):
| crate | 版本 | 管什么 | 依赖的本仓库 crate | src/ 行数 |
|---|---|---|---|---|
probe-rs-target | 0.32.0 | 目标描述的数据结构:ChipFamily、Chip、内存区、flash 算法、核的访问选项;只有数据,不碰硬件 | 无 | 2 304 |
probe-rs | 0.32.0 | 核心库:探针驱动、ARM / RISC-V / Xtensa 调试架构、Session 与 Core、烧录、RTT、semihosting、coredump | probe-rs-target | 81 495 |
probe-rs-debug | 0.32.0 | DWARF:栈展开、变量、源码行、单步 | probe-rs、probe-rs-target | 11 912 |
probe-rs-espressif | 0.32.0 | ESP 芯片支持:目标描述、调试序列、内置 USB-JTAG 探针驱动、IDF 镜像格式;一个插件 | probe-rs、probe-rs-target | 3 633 |
probe-rs-linux | 0.32.0 | Linux 主机直接驱动 SWD:linuxgpiod(GPIO 翻转)和 linuxspidevswd(借 SPI);一个插件 | probe-rs | 1 419 |
target-gen | 0.32.0 | 从 CMSIS-Pack 生成目标描述 YAML 的命令行工具 | probe-rs、probe-rs-espressif、probe-rs-target | 2 233 |
probe-rs-mi | 0.2.0 | 给程序读的输出格式(machine interface)定义 | 无 | 35 |
probe-rs-tools | 0.32.0 | 可执行文件 probe-rs、cargo-flash、cargo-embed:命令行、RPC、DAP 服务器、gdb 服务器 | 以上全部(probe-rs-linux 只在 Linux 上) | 32 301 |
probe-rs-meta | 0.1.0 | no_std 小 crate,固件依赖它,用 chip! 宏把芯片名写进 ELF 的一个段,probe-rs run 读出来就不用再传 --chip | 无(不在 workspace 成员里) | 64(含 build.rs) |
依赖方向只有一个:所有东西都依赖 probe-rs,probe-rs 只依赖 probe-rs-target。probe-rs-debug 没有被核心库依赖,它通过公开的 MemoryInterface 和 Core 读目标,所以同一套展开代码可以喂活的目标,也可以喂 coredump(08 调试信息)。
probe-rs-tools 里三个可执行文件其实是一个。cargo-flash 和 cargo-embed 是两个 30 多行的小程序,把 argv[0] 改成自己的名字后 exec 一个 probe-rs;probe-rs 的 main 先用 multicall_check 看自己是以哪个名字被调用的,再分派到 cmd::cargo_flash::main 或 cmd::cargo_embed::main。
核心 crate 的分层
probe-rs/src/ 下各部分的行数(含测试):
| 目录 / 文件 | 行数 | 管什么 | 本卷 |
|---|---|---|---|
probe.rs、probe/ | 19 640 | DebugProbe trait、驱动注册表、各家探针驱动、FakeProbe | 02 探针层 |
architecture/arm/ | 23 676 | ADI 的 DP / AP、MEM-AP、ROM table、Cortex-M / A / R 内核、调试序列、polyfill | 03 ARM 调试架构 |
architecture/riscv/、architecture/xtensa/ | 6 874 / 4 440 | RISC-V 调试模块、Xtensa XDM | 04 RISC-V 与 Xtensa |
vendor/ | 11 869 | 16 家厂商的调试序列和芯片识别 | 调试序列与 vendor |
session.rs、core.rs、core/、memory/ | 约 4 200 | Session、Core、CoreInterface、MemoryInterface、coredump | Session、Core 与 MemoryInterface |
config/ | 965 | 目标注册表、按芯片名或 ROM table 信息找目标 | 目标描述 |
flashing/ | 5 761 | 镜像格式、烧录计划、flash 算法 | 06 烧录 |
rtt.rs、rtt/、semihosting.rs | 1 260 / 692 | RTT 与 semihosting | 07 RTT、defmt 与 semihosting |
从下往上:探针驱动只管「把一次 DP / AP 寄存器访问(或者一串 SWD 位)送到线上」;架构层把寄存器访问组织成「读一块内存」「让核停下」;Session 和 Core 把架构层包成和架构无关的接口;烧录、RTT、semihosting 都只用 Core 和 MemoryInterface。
flowchart TB flashing["flashing、rtt、semihosting"] --> sess["Session、Core、MemoryInterface"] sess --> armcore["arm/core:Armv6m、Armv7m、Armv8m"] sess --> rv["riscv、xtensa"] armcore --> memap["arm/memory:ADIMemoryInterface、ROM table"] memap --> ci["arm/communication_interface:DP、AP、SELECT"] ci --> rawdap["RawDapAccess"] rawdap --> cmsis["CMSIS-DAP、Glasgow 驱动"] rawdap --> poly["polyfill:RawSwdIo / RawJtagIo"] poly --> bitbang["J-Link、FTDI、CH347、linuxgpiod"] vendor["vendor:调试序列"] -.-> ci vendor -.-> armcore
虚线表示 vendor 不在调用链上,而是作为 ArmDebugSequence 被注入:attach、复位、解锁这些步骤遇到具体芯片时,由序列决定做什么。
四张全局表和插件
probe-rs 在四个地方用 static LazyLock<RwLock<Vec<...>>> 存「已知的东西」:
| 表 | 定义 | 内置内容 | 注册函数 |
|---|---|---|---|
探针驱动 DRIVERS | probe.rs:47 | 9 个 ProbeFactory | register_probe_factory,追加到末尾 |
厂商 VENDORS | vendor/mod.rs:86 | 16 个 Vendor | register_vendor,插到最前面,让用户的实现覆盖内置的 |
镜像格式 LOADERS | flashing/loader.rs:62 | 开 builtin-formats 时有 ELF、BIN、HEX、UF2 | register_image_format,追加 |
目标 BUILTIN_TARGETS | config/registry.rs:157 | 开 builtin-targets 时,编译期把 targets/*.yaml 转成 bincode 嵌进二进制 | add_builtin_target,追加 |
0.32 之前 ESP 支持直接写在核心库里。0.32 把它搬进 probe-rs-espressif(CHANGELOG #3813),同时把原来的 vendor::register_vendor 换成了一个统一的插件入口(#3834)。整个插件机制就是 plugin.rs 这 30 行:
// probe-rs/src/plugin.rs
pub struct Plugin<'p> {
pub vendors: &'p [&'static dyn Vendor],
pub image_formats: &'p [&'static dyn ImageFormat],
pub targets: &'p [ChipFamily],
pub probe_drivers: &'p [&'static dyn ProbeFactory],
}
pub fn register_plugin(plugin: Plugin<'_>) {
for vendor in plugin.vendors {
crate::vendor::register_vendor(*vendor);
}
// ... image_formats、targets、probe_drivers 同理
}
ESP 插件一次往四张表里各放一样东西:
// probe-rs-espressif/src/lib.rs
pub fn register_plugin() {
let targets = targets();
plugin::register_plugin(plugin::Plugin {
vendors: &[&Espressif],
image_formats: &[&IdfLoaderFactory],
targets: &targets,
probe_drivers: &[&EspUsbJtagFactory],
});
}
(probe-rs-espressif/src/lib.rs:32)。Linux 插件只放两个探针驱动,而且整个函数体包在 #[cfg(target_os = "linux")] 里,在别的系统上是空函数(probe-rs-linux/src/lib.rs:12)。命令行在 main 的第一行注册它们:
// probe-rs-tools/src/bin/probe-rs/main.rs
async fn main() -> Result<()> {
probe_rs_espressif::register_plugin();
#[cfg(target_os = "linux")]
probe_rs_linux::register_plugin();
// ...
(main.rs:537)。直接用 probe-rs 库写工具的人如果忘了这一行,就看不到任何 ESP 芯片:这是常见坑里的一个。全局可变表换来的是「拆出去的东西不必在核心库里留钩子」,代价是初始化顺序成了调用方的责任,见设计取舍。
feature
probe-rs/Cargo.toml 的 [features]:
| feature | 默认 | 打开什么 |
|---|---|---|
builtin-targets | 是 | 依赖 bincode 和 probe-rs-target 的 bincode 支持;build.rs 把 targets/ 下 242 个目标描述文件转成 bincode,BUILTIN_TARGETS 用 include_bytes! 读回 |
builtin-formats | 是 | ELF、BIN、Intel HEX、UF2 四种镜像格式(object、ihex、uf2-decode、flate2);0.32 起可以关掉(#3817) |
coredump | 是 | 写 coredump 需要的 rmp-serde 等;0.32 起可以关掉(#3773) |
cmsisdap_v1 | 是 | CMSIS-DAP v1(HID)支持,依赖 hidapi;关掉后只剩 v2(USB bulk) |
test | 否 | 测试辅助:FakeProbe::with_mocked_core_and_binary 等需要读 ELF 的部分、probe_rs::test 模块 |
flate2 | 否 | 单独的压缩支持,probe-rs-espressif 会打开它 |
probe-rs-tools 自己有一个 remote feature(默认关),打开后才有 probe-rs serve 命令和 --host 参数,用 websocket 或 unix socket 连远端的 probe-rs。本机安装的 0.32.0 没有开它:probe-rs serve --help 报 unrecognized subcommand 'serve'。
每个 crate 从哪读起
| crate | 入口 | 先读 |
|---|---|---|
probe-rs | src/lib.rs | probe.rs(DebugProbe、Probe)、session.rs(Session::new)、architecture/arm/communication_interface.rs |
probe-rs-target | src/lib.rs | chip_family.rs、chip.rs、memory.rs |
probe-rs-debug | src/lib.rs | debug_info.rs、stack_frame.rs、exception_handling/ |
probe-rs-tools | src/bin/probe-rs/main.rs | cmd/run.rs、util/cli.rs、rpc/functions.rs |
probe-rs-espressif | src/lib.rs | register_plugin、sequences/ |
小结
- 依赖只朝一个方向:一切依赖
probe-rs,probe-rs只依赖数据 crateprobe-rs-target;probe-rs-debug只通过公开接口读目标。 - 核心库从下往上:探针驱动 → ARM / RISC-V / Xtensa 架构层 →
Session/Core→ 烧录、RTT、semihosting;vendor 以调试序列的形式注入。 - 探针驱动、厂商、镜像格式、目标四张全局表,0.32 起由
plugin::register_plugin统一扩充;ESP 支持和 Linux 探针都是插件,命令行在main第一行注册,库用户要自己注册。 - 默认 feature 打开内置目标、四种镜像格式、coredump 和 CMSIS-DAP v1;
test打开 FakeProbe 读 ELF 的能力;命令行的远程功能在probe-rs-tools的remotefeature 里。