调试序列与 vendor
主线位置:命令主线的「attach」一步,以及访问主线、调试主线上所有「芯片说了算」的地方。前三章讲的是规范里的通用做法,本页讲 probe-rs 怎么让每颗芯片在固定的几个点上改写这些做法,并把
Session::new的 attach 顺序完整走一遍。
sequenceDiagram participant S as Session::new participant Q as 调试序列 participant I as ArmCommunicationInterface participant T as 目标 S->>I: select_debug_port I->>Q: debug_port_setup 和 debug_port_start Q->>T: 线复位 读 DPIDR 上电 S->>Q: debug_device_unlock Q->>T: STM32 写 DBGMCU_CR 或 nRF 读 CTRL-AP S->>Q: 每个核 debug_core_start Q->>T: 写 DHCSR C_DEBUGEN S->>T: clear_all_hw_breakpoints 停核 清 FPB 放行
为什么需要序列
ADI 和 Armv7-M 规定了「通用」的连接、上电、复位方法,但真实芯片有各种例外:低功耗模式下调试时钟会被关掉,要先改厂商的调试控制寄存器;芯片被读保护锁住了,要先通过厂商自定义的 AP 擦除解锁;系统复位会把调试端口也复位掉,要重新连。CMSIS-Pack 的调试描述(debug description)为这类差异定义了一组标准的「序列」名字(DebugPortSetup、DebugDeviceUnlock、ResetSystem 等),厂商在 pack 里给出自己的实现。
probe-rs 把这组名字做成了一个 trait:ArmDebugSequence,每个方法都有默认实现(大多照 CMSIS 的默认行为写),厂商只覆盖需要的几个。方法文档里大都链到 Open-CMSIS-Pack 规范的对应条目,#[doc(alias = "...")] 写着 CMSIS 的名字。
18 个钩子
| 组 | 方法 | 对应 CMSIS 序列 | 谁调 |
|---|---|---|---|
| DP | debug_port_setup | DebugPortSetup | select_dp,第一次连 DP 时 |
debug_port_connect | 无(probe-rs 自己的) | debug_port_setup;multidrop 切换 DP 时 | |
debug_port_start | DebugPortStart | select_dp,DP 第一次被访问时 | |
debug_port_stop | DebugPortStop | 接口断开时 | |
| 设备 | debug_device_unlock | DebugDeviceUnlock | Session::new,连上 DP 之后 |
debug_erase_sequence | DebugDeviceUnlock 里的擦除部分 | erase 的 chip erase | |
allowed_access_ports | 无 | 枚举 AP 时的白名单 | |
| 核 | on_attach | 无 | 连核之前 |
debug_core_start | DebugCoreStart | Session::new,给每个核使能调试 | |
debug_core_stop | DebugCoreStop | Session 被 drop 时 | |
recover_support_start | RecoverSupportStart | step / run 之前(默认空) | |
| 复位 | reset_hardware_assert / reset_hardware_deassert | ResetHardwareAssert / ResetHardwareDeassert | connect-under-reset |
reset_catch_set / reset_catch_clear | ResetCatchSet / ResetCatchClear | reset_and_halt、connect-under-reset | |
reset_system | ResetSystem | reset、reset_and_halt | |
| 其他 | trace_start | TraceStart | 配置 SWO / TPIU |
prepare_running_on_ram | 无 | 固件下载到 RAM 运行时设 SP、PC、VTOR |
18 个方法里,DP 一组拿到的是 &mut dyn DapProbe(直接面对探针,因为这时还没有可用的 DP),核一组拿到的是某个 MEM-AP 上的 ArmMemoryInterface,设备一组拿到的是整个 ArmDebugInterface。参数类型本身就说明了每个钩子能碰到哪一层。
不覆盖任何方法的 DefaultArmSequence 就是「规范里的通用做法」。Cortex-M 的默认实现都很短:debug_core_start 读 DHCSR,没开 C_DEBUGEN 就写上(cortex_m_core_start);reset_system 写 AIRCR 的 SYSRESETREQ,然后最多等 600 ms 让 DHCSR 的 S_RESET_ST 清掉(cortex_m_reset_system,注释说 600 ms 来自 PSOC 6 的文档)。
Vendor:按名字选序列,按 ROM table 认芯片
序列是按芯片选的,选的人是 Vendor:
// probe-rs/src/vendor/mod.rs
pub trait Vendor: Send + Sync + std::fmt::Display {
/// Tries to create a debug sequence for the given chip.
fn try_create_debug_sequence(&self, chip: &Chip) -> Option<DebugSequence>;
/// Tries to identify an ARM chip. Returns `Some(target name)` on success.
fn try_detect_arm_chip(
&self,
_registry: &Registry,
_probe: &mut dyn ArmDebugInterface,
_chip_info: ArmChipInfo,
) -> Result<Option<String>, Error> {
Ok(None)
}
// ... RISC-V、Xtensa、从探针固件识别,同样默认返回 None
}
建 Target 时,config/target.rs:114 挨个问 16 个 vendor(插件注册的排在最前面),第一个返回 Some 的说了算;都不认识就用该架构的默认序列。选法很朴素,就是比芯片名前缀。ST 的 try_create_debug_sequence:STM32F0、L0、G0 用 Stm32Armv6;F1、F2、F3、F4、F7、G4、L1、L4、WB、WL 用 Stm32Armv7;H7 系列、H5 / L5 / U3 / U5 / WBA、N6 各有各的。Nordic 的同理,nRF52 开头的用 Nrf52。
没给芯片名时(--chip 和 ELF 元数据都没有),auto_determine_target 先连上 DP,读最外层 ROM table 的设计者和部件号(MEM-AP 与 ROM table),再问每个 vendor 的 try_detect_arm_chip。Nordic 的实现只在设计者是 Nordic 时才往下走,再读芯片的 FICR 或 CONFIGID 比对注册表里每个家族的识别规则;注释坦白「FIXME: This is a bit shaky but good enough for now.」所有 vendor 都不认识时,退回按 ROM table 的部件号查注册表。
例一:STM32 的 DBGMCU_CR
STM32 在睡眠、停止、待机模式下会关掉调试相关的时钟,调试器就连不上了。DBGMCU_CR(0xE0042004)的低三位 DBG_SLEEP、DBG_STOP、DBG_STANDBY 让这些模式下调试保持可用。Stm32Armv7 在 debug_device_unlock 里把三位置 1,同时记下原值;在 debug_core_stop 里写回原值:
// probe-rs/src/vendor/st/sequences/stm32_armv7.rs
fn debug_device_unlock(
&self,
interface: &mut dyn ArmDebugInterface,
default_ap: &FullyQualifiedApAddress,
_permissions: &crate::Permissions,
) -> Result<(), ArmError> {
let mut memory = interface.memory_interface(default_ap)?;
let mut cr = dbgmcu::Control::read(&mut *memory)?;
self.saved_cr_value.lock().unwrap().replace(cr.0);
cr.enable_standby_debug(true);
cr.enable_sleep_debug(true);
cr.enable_stop_debug(true);
cr.write(&mut *memory)?;
Ok(())
}
这就是 probe-rs gdb --dry-run --chip STM32F103C8 打印的头两行 Read 0xe0042004 = 0、Write 0xe0042004 = 0x00000007,和退出时的最后一行 Write 0xe0042004 = 0x00000000(FakeProbe)。它借用的是「解锁」这个钩子,做的事和解锁无关,只因为这是「连上之后、碰核之前」唯一的时机。
例二:nRF52 的 CTRL-AP
nRF52 除了 AP 0(AHB-AP)还有一个 Nordic 自定义的 CTRL-AP,编号 1。它的 APPROTECTSTATUS(偏移 0x0C)为 0 表示芯片开了读保护(APPROTECT),此时 AHB-AP 访问不到内存,只能通过 CTRL-AP 的 ERASEALL 整片擦除来解锁。Nrf52::debug_device_unlock:
// probe-rs/src/vendor/nordicsemi/sequences/nrf52.rs
let ctrl_ap = &FullyQualifiedApAddress::v1_with_default_dp(1);
tracing::info!("Checking if core is unlocked");
if self.is_core_unlocked(iface, ctrl_ap)? {
tracing::info!("Core is already unlocked");
return Ok(());
}
tracing::warn!("Core is locked. Erase procedure will be started to unlock it.");
permissions
.erase_all()
.map_err(|MissingPermissions(desc)| ArmError::MissingPermissions(desc))?;
// Reset
iface.write_raw_ap_register(ctrl_ap, RESET, 1)?;
iface.write_raw_ap_register(ctrl_ap, RESET, 0)?;
// Start erase
iface.write_raw_ap_register(ctrl_ap, ERASEALL, 1)?;
// Wait for erase done
while iface.read_raw_ap_register(ctrl_ap, ERASEALLSTATUS)? != 0 {}
// ... 再复位一次,确认已解锁
Err(ArmError::ReAttachRequired)
三点:擦除会抹掉整片 flash,所以要用户显式给 Permissions::allow_erase_all()(命令行的 --allow-erase-all),没给就报 MissingPermissions;等擦除完成的 while 没有超时;解锁成功后返回 ReAttachRequired,Session::new 收到它会调 reattach_arm_interface 重连一次。第一步 is_core_unlocked 读的就是 AP 1 的地址 12,所以 FakeProbe 上 nRF52 一 attach 就 panic;用 expect_operation 登记一个非 0 的应答就能过(FakeProbe)。锁住的芯片上解锁的实际日志待实测。
Session::new 的 attach 顺序
把序列放回调用顺序里。ARM 目标走 attach_arm_debug_interface:
| 步 | 普通 attach | connect-under-reset 多出的 |
|---|---|---|
| 0 | reset_hardware_assert:拉低 nRESET | |
| 1 | probe.attach_to_unspecified():探针自己的连接(CMSIS-DAP 发 DAP_Connect) | |
| 2 | try_into_arm_debug_interface,select_debug_port:debug_port_setup、debug_port_start | |
| 3 | debug_device_unlock;返回 ReAttachRequired 时重连 | |
| 4 | 每个核 reset_catch_set(置 VC_CORERESET),然后 reset_hardware_deassert:放开 nRESET,读引脚等它真的变高,最多 1 秒 | |
| 5 | 每个核 debug_core_start | |
| 6 | 建 Session | 等每个核停在复位向量上,reset_catch_clear |
| 7 | clear_all_hw_breakpoints:停下所有核,清空 FPB 比较器,再放行原本在跑的核 | 同左 |
connect-under-reset 的意义在于:如果固件一上电就把 SWD 引脚改作他用、或者进了深睡眠,正常 attach 就连不上;在复位期间连接,固件还没来得及跑。reset_hardware_deassert 超时时,Session::new 会打一条警告,提示复位引脚大概没接好。
实验:142 次访问
模拟目标上普通 attach STM32F103C8,日志按 MEM-AP 的重建(每次 memory_interface 都有的那 8 次访问)切开,正好对应上表的步骤:
| 步 | DP / AP 访问 | 内存一层做了什么 |
|---|---|---|
2 select_debug_port | 3 个 SWJ 序列 + 15 | 线复位、读 DPIDR、上电、开溢出检测(见 DP、AP 与 SELECT) |
3 debug_device_unlock | 13 | 读 DBGMCU_CR 得 0,写 7(多出的 1 次是 CSW 改传输宽度:模拟目标的 CSW 复位值是字节宽度) |
5 debug_core_start | 12 | 读 DHCSR 0x00010000,写 0xa05f0001 |
| 7 停核 | 30 | 第一次拿 Core:读 DHCSR、清 DFSR、读 MVFR0;核在跑,于是 halt 并读 PC |
| 7 清 FPB | 34 | 读 FP_CTRL 得 6 个比较器,逐个读出(都已为空,不用写) |
| 7 放行 | 38 | run:先单步,再清 C_MASKINTS、C_STEP |
| 合计 | 142 |
== attach STM32F103C8
...
R DBGMCU_CR 0x00000000
W DBGMCU_CR 0x00000007
R DHCSR 0x00010000
W DHCSR 0xa05f0001
R DHCSR 0x00010001
W DFSR 0x0000001f
R MVFR0 0x00000000
R DHCSR 0x00010001
W DHCSR 0xa05f0003
...
## attach: raw transfers=142 est. DAP commands=62
(上面只列了内存一层的访问,每次访问前的 SELECT、TAR 写省略。)其中有 40 次(5 次 MEM-AP 重建 × 8)花在反复重建 MEM-AP 上,这是 MEM-AP 与 ROM table 讲过的每次 memory_interface 的固定开销。真实芯片上 attach 的耗时、connect-under-reset 的时序待实测。
Session 被 drop 时反向收尾:每个核 debug_core_stop(STM32 在这里写回 DBGMCU_CR),接口断开时 debug_port_stop 清掉上电请求并等两个 ACK 位落下。模拟目标日志的最后三行就是:
W DBGMCU_CR 0x00000000
W DP SELECT = 0x00000000
W DP CTRL/STAT = 0x00000000
R DP CTRL/STAT -> 0x00000000
小结
ArmDebugSequence把 CMSIS-Pack 调试描述里的标准序列做成 18 个带默认实现的钩子,按参数类型分别面对探针、MEM-AP 或整个调试接口。Vendor按芯片名前缀选序列,插件注册的排在最前;不给芯片名时先读 ROM table 的设计者和部件号,再交给各 vendor 识别,Nordic 还要读 FICR。- STM32 借
debug_device_unlock打开 DBGMCU_CR 的低功耗调试位、在debug_core_stop恢复;nRF52 在同一个钩子里读 CTRL-AP,锁住时需要allow_erase_all才擦除解锁,并要求重连。 - attach 顺序:探针连接 → DP setup / start → 设备解锁 → 每个核
debug_core_start→ 清硬件断点;connect-under-reset 在前后加上拉低 / 放开 nRESET 和 reset catch。模拟目标上普通 attach 是 142 次 DP / AP 访问,近三成花在重建 MEM-AP 上。