如何启用 semihosting?
解读
在国内嵌入式 Rust 岗位面试中,"semihosting" 常被用来快速验证候选人是否真正做过 裸机调试 与 底层日志输出。
面试官真正想确认的是:
- 你知道 semihosting 是 ARM 官方调试机制,不是 Rust 语法糖;
- 你能在 no_std + no_main 环境下,用 Rust 生态工具链 把调试信息打到 Host 端,而不是依赖串口;
- 你清楚 Cargo.toml、memory.x、runner、.cargo/config.toml 四条线的配合,且能解释 "为什么 Release 模式要关 semihosting";
- 你了解 cortex-m-semihosting 与 rtt-target 的取舍,避免"只会抄模板"的嫌疑。
知识点
- ARM semihosting 原理:BKPT 0xAB + 寄存器传参,由 OpenOCD/J-Link 在 Host 端代理 syscall
- cortex-m-rt 启动流程与 __RESET_HANDLER 链接脚本
- panic-handler 与 exit 语义:semihosting 的 SYS_EXIT 会 halt 整个 MCU,生产固件必须剔除
- cargo-embed、probe-run、pyOCD 三种 runner 对 semihosting 的自动识别差异
- scb::sysreset 与 semihosting::heprintln! 的 同步/阻塞 行为对实时性的影响
- 特征开关 cfg(debug_assertions) 与 link-arg -lnosys 的配套用法,防止 Release 误链接 semihosting
答案
-
新建 cortex-m 裸机工程
cargo generate --git https://github.com/rust-embedded/cortex-m-quickstart -
在 Cargo.toml 引入官方包并 只在 dev 模式启用
[target.thumbv7em-none-eabi.dependencies] cortex-m-semihosting = "0.5" panic-semihosting = { version = "0.6", optional = true } [features] default = [] debug = ["panic-semihosting"] -
配置
.cargo/config.toml让 runner 识别 semihosting[target.thumbv7em-none-eabi] runner = "probe-run --chip STM32F407VG" # 或 J-Link GDB Server rustflags = [ "-C", "link-arg=-Tlink.x", "-C", "link-arg=-lnosys", # Release 模式屏蔽 semihosting ] [build] target = "thumbv7em-none-eabi" -
主程序 按条件初始化
#![no_std] #![no_main] use cortex_m_rt::entry; use cortex_m_semihosting::hprintln; #[cfg(feature = "debug")] use panic_semihosting as _; #[cfg(not(feature = "debug"))] use panic_halt as _; #[entry] fn main() -> ! { hprintln!("Hello from MCU @ {:08X}", 0x0800_0000).unwrap(); loop { cortex_m::asm::wfi(); } } -
编译与运行
cargo run --features debug # 开发阶段,OpenOCD 终端可见日志
cargo build --release # 发布阶段,无 semihosting,体积最小 -
验证是否真正启用
- OpenOCD 终端出现 "Hello from MCU @ 08000000"
- 查看 map 文件,仅 debug elf 含
sh_*符号,release 无
拓展思考
-
生产固件如何无缝切换日志通道?
自定义 log::Logger,dev 模式指向 hprintln!,release 指向 ITM stimulus port 0 或 RTT,用 link-time feature 保证零开销。 -
semihosting 的性能惩罚到底多大?
实测 STM32F407 72 MHz 下,单条 hprintln! 约 850 µs,而 ITM 仅 1.2 µs;因此 中断上下文严禁调用 semihosting。 -
RISC-V 没有 BKPT 0xAB,怎么办?
国内 GD32VF103 项目常用 OpenOCD 自定义 SBI call 模拟 semihosting,但 Rust 层需用 riscv-semihosting crate,并 把 _start 中的 tohost 地址写进 .data,否则 GDB 收不到数据。 -
面试官常追问的坑:
- "为什么 Release 要加 -lnosys?"——防止 newlib nano 把 write() 默认导向 semihosting,导致 板子跑飞。
- "如何防止 SYS_EXIT 触发断点复位?"——在 panic_handler 里 永不调用 semihosting::exit,而是 loop {} 或 scb::sysreset()。