No available hardware breakpoints
主线位置:调试主线前半段的「FPB 设硬件断点」。probe-rs 在 Cortex-M 上不写软件断点,每个断点占一个 FPB 比较器;比较器怎么编码见 Cortex-M 内核。本页讲比较器用完时三个前端各自的反应。
flowchart TB
req["下断点:gdb 的 Z1、DAP 的 setBreakpoints、库的 set_hw_breakpoint"] --> hw["Core::hw_breakpoints:读 FP_CTRL 和每个 FP_COMPn"]
hw --> free{"有空闲或同地址的比较器"}
free -->|有| set["写 FP_COMPn"]
free -->|没有| err["Error::Other:No available hardware breakpoints"]
err --> gdb["gdb server:TargetError::Fatal,会话退出"]
err --> dap["DAP:这一个断点 verified false"]
err --> lib["库:返回 Err"]
现象
本卷的软件模拟目标设了 4 个比较器(真实芯片的个数待实测),连下 5 个断点:
breakpoint units = 4; set 5 breakpoints: [Ok(()), Ok(()), Ok(()), Ok(()), Err("No available hardware breakpoints")]
probe-rs gdb --dry-run 背后的 FakeProbe 读 FP_CTRL 得 0,一个都没有,lldb 的第一个断点就撞上,而且 lldb 那头的表现是 Resume timed out,服务端的最后几行是(gdb server):
Write 0xe0002000 = 0x00000003
Read 0xe0002000 = 0
During the execution of GDB an error was encountered:
Target threw a fatal error: No available hardware breakpoints
服务端进程随之结束。
从哪来
Core::set_hw_breakpoint 先确保 FPB 全局打开,再读出每个比较器的当前地址:已有同地址的就复用,否则找第一个空的,找不到:
// probe-rs/src/core.rs
let breakpoint_comparator_index =
match breakpoints.iter().position(|&bp| bp == Some(address)) {
Some(breakpoint_comparator_index) => breakpoint_comparator_index,
None => breakpoints
.iter()
.position(|bp| bp.is_none())
.ok_or_else(|| Error::Other("No available hardware breakpoints".to_string()))?,
};
比较器个数是 FP_CTRL 的 NUM_CODE,由芯片实现决定。这里没有软件断点的退路:Cortex-M 的代码多半在 flash 里,写 BKPT 要擦写扇区,probe-rs 不做(gdb server)。
还有一种相近的失败:FPB rev 1(FP_CTRL.REV = 0,Armv7-M 上常见)的比较器只能匹配 0x2000_0000 以下的地址,断在 RAM 里的代码上会得到 UnsupportedBreakpointAddress:「Unable to create a breakpoint at address … Hardware breakpoints are only supported at addresses < 0x2000_0000.」。
三个前端的反应
| 前端 | 处理 | 用户看到的 |
|---|---|---|
probe-rs gdb | add_hw_breakpoint 对组内每个核设断点,错误走 into_target_result:除「核未启用」外一律 TargetError::Fatal | gdbstub 状态机退出,服务端打印上面那两行后结束,GDB 失去连接 |
dap-server | set_breakpoints 逐个设,失败的那个在响应里 verified: false、message 是错误原文、reason 是 failed | VS Code 里那个断点显示为未验证(灰色),会话照常 |
| 库 | Err(Error::Other(..)) | 自己处理 |
gdb server 这一行最伤人:GDB 用户习惯随手下很多断点,第 N+1 个断点不是失败一次,而是让整个调试会话断掉。原因是 into_target_result 把错误分得很粗,只有 CoreDisabled 映射成 EIO。
另有两处会悄悄占用比较器:
- gdb 的断点对组内每个核都设。同类型的多核芯片上,每个核各占一个,但各核的 FPB 是独立的,个数不会互相挤占。
- 语句级单步要借一个。step over / into / out 用「在目标地址下临时断点再
run」实现,全占满时借 0 号,跑完写回原值(变量与单步)。所以占满了断点仍能单步,不会因此报这个错。
怎么办
- 先看芯片有几个:库里
core.available_breakpoint_units()。按 ARM 各内核的技术参考手册,Cortex-M3 / M4 的 FPB 有 6 个指令比较器,Cortex-M0 / M0+ 最多 4 个,芯片厂商还可以配置得更少;实际读出来的数待实测(登记在硬件待测清单的 HW-4)。 - 用 GDB 时控制断点数,删掉不用的(
delete)再下新的;需要大量断点、条件断点时换 VS Code +dap-server,多出来的只是未验证,不会断开会话。 - 想在 RAM 里运行的代码上下断点,先确认 FPB 版本;rev 1 上只能靠单步或在代码里放
cortex_m::asm::bkpt()。
小结
- probe-rs 在 Cortex-M 上只有硬件断点,个数等于 FPB 的
NUM_CODE,用完时Core::set_hw_breakpoint报「No available hardware breakpoints」。 - gdb server 把它当致命错误,GDB 会话随之结束;DAP 把那个断点标成
verified: false,会话继续。 - FPB rev 1 不能断在
0x2000_0000以上,是另一个错误;单步借 0 号比较器,不受「占满」影响。