Rust in Everything
基于 probe-rs 0.32.0

源码地图

主线位置:三条主线经过的 crate 和模块。命令主线从 probe-rs-tools 开始,访问主线和调试主线的前半段全在核心 crate probe-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版本管什么依赖的本仓库 cratesrc/ 行数
probe-rs-target0.32.0目标描述的数据结构:ChipFamily、Chip、内存区、flash 算法、核的访问选项;只有数据,不碰硬件无2 304
probe-rs0.32.0核心库:探针驱动、ARM / RISC-V / Xtensa 调试架构、Session 与 Core、烧录、RTT、semihosting、coredumpprobe-rs-target81 495
probe-rs-debug0.32.0DWARF:栈展开、变量、源码行、单步probe-rs、probe-rs-target11 912
probe-rs-espressif0.32.0ESP 芯片支持:目标描述、调试序列、内置 USB-JTAG 探针驱动、IDF 镜像格式;一个插件probe-rs、probe-rs-target3 633
probe-rs-linux0.32.0Linux 主机直接驱动 SWD:linuxgpiod(GPIO 翻转)和 linuxspidevswd(借 SPI);一个插件probe-rs1 419
target-gen0.32.0从 CMSIS-Pack 生成目标描述 YAML 的命令行工具probe-rs、probe-rs-espressif、probe-rs-target2 233
probe-rs-mi0.2.0给程序读的输出格式(machine interface)定义无35
probe-rs-tools0.32.0可执行文件 probe-rs、cargo-flash、cargo-embed:命令行、RPC、DAP 服务器、gdb 服务器以上全部(probe-rs-linux 只在 Linux 上)32 301
probe-rs-meta0.1.0no_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 640DebugProbe trait、驱动注册表、各家探针驱动、FakeProbe02 探针层
architecture/arm/23 676ADI 的 DP / AP、MEM-AP、ROM table、Cortex-M / A / R 内核、调试序列、polyfill03 ARM 调试架构
architecture/riscv/、architecture/xtensa/6 874 / 4 440RISC-V 调试模块、Xtensa XDM04 RISC-V 与 Xtensa
vendor/11 86916 家厂商的调试序列和芯片识别调试序列与 vendor
session.rs、core.rs、core/、memory/约 4 200Session、Core、CoreInterface、MemoryInterface、coredumpSession、Core 与 MemoryInterface
config/965目标注册表、按芯片名或 ROM table 信息找目标目标描述
flashing/5 761镜像格式、烧录计划、flash 算法06 烧录
rtt.rs、rtt/、semihosting.rs1 260 / 692RTT 与 semihosting07 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<...>>> 存「已知的东西」:

表定义内置内容注册函数
探针驱动 DRIVERSprobe.rs:479 个 ProbeFactoryregister_probe_factory,追加到末尾
厂商 VENDORSvendor/mod.rs:8616 个 Vendorregister_vendor,插到最前面,让用户的实现覆盖内置的
镜像格式 LOADERSflashing/loader.rs:62开 builtin-formats 时有 ELF、BIN、HEX、UF2register_image_format,追加
目标 BUILTIN_TARGETSconfig/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-rssrc/lib.rsprobe.rs(DebugProbe、Probe)、session.rs(Session::new)、architecture/arm/communication_interface.rs
probe-rs-targetsrc/lib.rschip_family.rs、chip.rs、memory.rs
probe-rs-debugsrc/lib.rsdebug_info.rs、stack_frame.rs、exception_handling/
probe-rs-toolssrc/bin/probe-rs/main.rscmd/run.rs、util/cli.rs、rpc/functions.rs
probe-rs-espressifsrc/lib.rsregister_plugin、sequences/

小结

  • 依赖只朝一个方向:一切依赖 probe-rs,probe-rs 只依赖数据 crate probe-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 的 remote feature 里。

💬 关联讨论

发起讨论
加载中...