← 返回嵌入式学习资料第 11 章 · 调试、测试与可靠性

CHAPTER 11 · DEBUG, TEST AND RELIABILITY

调试、测试与可靠性
让失败也能交付

从现象、假设和证据坐标开始,建立 SWD/GDB 连接、主机测试、目标机验证、故障注入和 CI 交付的共同语言;把工具扰动、版本身份、安全边界与回滚动作放进同一条可靠性证据链。

6 个一级单元51 个递进小节主机路径可复现

TEXTBOOK MAP

CHAPTER 11 · MAP

从现象坐标到可靠交付

六个一级单元对应六种能力:建立调试坐标、连接目标现场、验证主机合同、分层验证目标机、注入故障、完成 CI 与回滚交付。

先让问题可以被定位
  • 01 现象、假设与证据
  • 02 SWD/GDB 连接与停机
再让断言跨过平台边界
  • 03 单元、属性与回归
  • 04 HIL、覆盖率与测量
最后让失败也可以交接
  • 05 故障注入与恢复
  • 06 CI、闸门与维护

小节数量按认知任务变化:连接、测试和交付需要更多递进步骤;每个单元都保留无目标机主机路径,并明确工具扰动与停止条件。

使用方式:先从左侧选择一个可靠性问题,再按调试坐标、连接停机、主机测试、目标验证、故障注入和可靠交付推进。每个单元底部有前后翻页;小节标题直接说明正在追踪的现象、现场、断言、夹具、恢复或发布闸门。
主机边界:默认使用标准库模型、可重放输入、断言、测试报告、故障矩阵和 provenance 清单;没有目标板也能完成问题定位、合同测试和报告生成。
目标机边界:真实 CoreSight/SWD、探针、GDB server、寄存器、时钟、IRQ/DMA、供电、夹具、波形、温度、执行器和复位路径必须回到当前芯片、板卡和官方资料。

01 · DEBUGGING MODEL

调试坐标:现象、假设与证据

调试不是在代码里寻找一个看起来可疑的行,而是把现象、对象、输入、时序、假设和证据组织成可反驳的闭环。先描述发生了什么,再决定需要哪一种观察;断点、日志、波形、寄存器和复现脚本各自只能覆盖一部分事实。

看懂把现象写成可检验的断言。
说清用假设树和单变量实验缩小范围。
开始做区分观测到的事实与尚未证明的解释。
官方基线Cortex-M0+ Devices Generic User Guide处理器寄存器、异常、栈与调试语义的 Arm 官方基线。锁定:DUI 0662B · 核对日期:2026-09-05

11.1.1 现象与断言的边界

调试记录的第一句应是可观察现象,而不是原因。例如‘在固件 v1.4、板卡 rev B、输入序列 S、室温、运行 37 分钟后,任务 control 的 progress 停止,250 ms 后发生 IWDG 复位’比‘线程死锁了’更有价值。前者可以被复现和反驳,后者已经把假设伪装成结论。

11.1.2 对象、版本与复现条件

对象身份是复现的坐标系:MCU 型号与修订、板卡、外围器件、固件提交、FreeRTOSConfig、工具链、时钟、供电、负载、输入和访问日期都可能改变现象。现场只给一张截图或一个版本号时,应把缺失字段列成未知项,而不是用开发者记忆补齐。

PYTHON · REPRODUCTION RECORD
from dataclasses import dataclass @dataclass(frozen=True) class Observation: stage: str signal: str value: str timestamp: int def same_case(a, b): return (a.stage, a.signal, a.value) == (b.stage, b.signal, b.value)

主机模型固定现象字段;目标机仍需补芯片、板卡、探针和真实时序。

C · ASSERTION AT THE BOUNDARY
configASSERT(queue_length <= queue_capacity); configASSERT(owner == current_task); record_fault(stage, task_name, tick, build_hash);

断言应靠近资源边界并保存上下文;不能用日志证明所有并发交错。

11.1.3 假设树与反证路径

假设树把同一现象拆成互斥或可组合的机制,例如复位来自看门狗、brownout、硬件引脚、异常向量或人工下载;数据错位来自长度、端序、竞争、缓存或旧缓冲。每个分支都要有一个最小实验和预期反证,而不是沿着最熟悉的代码路径漫游。

表 1 · 调试坐标:现象、假设与证据的证据判断表
对象应留下的证据不能直接推出
现象输入/对象/时间/输出原因假设就是事实
工具扰动/覆盖/带宽断点不会改变行为
结论复现率/证据/未知一次不复现等于修复

11.1.4 单变量实验与对照组

单变量实验要求固定对象、版本、输入、时钟、负载和观测方法,只改变一个条件,并保留对照组。如果同时提高队列容量、降低采样率和改锁顺序,即使现象消失也无法知道哪个变量有效。对照不一定是‘旧代码’,也可以是合成输入、关闭一个功能或在主机模型中复现同一状态。

11.1.5 断点、日志与观测扰动

断点会停止 CPU、改变超时和中断交错,日志会占用栈、串口和锁,GPIO 标记会改变引脚和总线,环形缓冲会消耗内存。GDB 文档提醒实时行为可能因调试器延迟而显著变化,因此要选择不侵入或低侵入的观测方式,并记录工具、触发和观测开销。

图 1.1 · 调试坐标:现象、假设与证据的判断路径每一格都要有自己的条件。
现象what happened
假设why it may happen
实验one variable
证据replay/report

从左到右是思考顺序,不是把现象、工具、测试和目标机证据压缩成一个“通过”。

11.1.6 不变量、断言与契约

不变量是任何合法执行都应成立的关系,例如队列长度不超过容量、拥有锁的任务才可释放、序号不回退、状态为 RUNNING 时资源已经绑定。断言应尽量靠近边界,失败时保存上下文;不要用大量 printf 代替不变量,因为日志输出本身不能阻止错误继续扩散。

11.1.7 复现率、置信度与未知项

复现率应包含尝试次数、时间窗口、输入集合和工具版本。一次没再出现不等于修复,十次出现也不一定覆盖最坏条件。报告可以写‘在 100 次主机重放中 100 次触发’或‘目标机 8 小时未触发,但低温/满载未覆盖’,把置信度和未知项一起交付。

11.1.8 本节交付:调试坐标卡

调试坐标卡包括 symptom、object_identity、build_hash、input_vector、environment、timeline、hypotheses、experiment、control、observation_tool、reproduction_rate、confidence、known_limits 和 next_variable。它让下一单元的 SWD/GDB 连接有明确目标,而不是打开调试器后漫无目的地单步。

CHECKPOINT 11.1把“面对一个‘偶尔坏’的现象,怎样让调试从猜测变成可重复的实验?”写成一份可复查记录

请围绕“调试坐标:现象、假设与证据”写出对象、输入/故障条件、调试或测试机制、输出与完成语义、一个安全约束、一个失败路径和对应证据。没有目标板时,运行主机路径并把未执行字段标为未执行;拥有目标机时,还要补齐芯片、板卡、探针、工具链、夹具、供电、负载和观察条件。不要用一次断点、一个绿灯、一个复位或一张截图覆盖没有做过的验证。

合格记录要让没有现场上下文的读者知道先查什么、先运行什么、看到什么才算通过,以及哪一句结论仍然不能推出。未知项、停止条件、责任人和回滚动作都是交付的一部分。

02 · PROBE AND HALT

连接与停机:SWD、GDB 与现场

SWD/JTAG、调试探针和 GDB 把主机观察带到目标机,但连接成功不等于程序、符号和现场一致。需要理解 CoreSight/DAP、复位与 halt、符号文件、远程协议、寄存器副作用和调试扰动,才能把一次停机变成可靠证据。

看懂画出主机、探针、调试服务器和目标的连接路径。
说清用匹配的 ELF/符号解释现场。
开始做安全地停机、取证、继续和复位。
官方基线Cortex-M0+ Devices Generic User Guide处理器寄存器、异常、栈与调试语义的 Arm 官方基线。锁定:DUI 0662B · 核对日期:2026-09-05

11.2.1 CoreSight、DAP 与 SWD/JTAG

CoreSight 是片上调试与追踪组件的架构,SWD/JTAG 是到调试访问端口的物理/协议入口,探针固件、调试服务器和 GDB 则组成主机到目标的控制链。任何一层不匹配都可能出现‘能连上但读错’:例如目标 ID 对了,当前 ELF 却是另一块板或另一版链接地址。

11.2.2 探针、服务器与 GDB 路径

GDB 的 remote debugging 需要主机拥有与目标完全匹配的未剥离程序和符号;连接模式、目标服务器、串口/TCP、复位选项和 monitor 命令都会影响结果。先记录探针型号、固件、服务器版本、连接命令和目标回应,再解释 PC、栈和变量,不要把终端上一次成功连接当成身份凭证。

SHELL · GDB SESSION
openocd -f interface/stlink.cfg -f target/stm32.cfg arm-none-eabi-gdb build/firmware.elf (gdb) target extended-remote :3333 (gdb) monitor reset halt (gdb) info registers pc sp lr (gdb) bt

命令只是会话形状;探针、目标脚本、复位方式和 ELF 必须与当前硬件匹配。

C · FAULT SNAPSHOT
typedef struct { uint32_t pc, lr, sp, cfsr, hfsr, reset_cause; uint32_t task_id, progress; } fault_snapshot_t; volatile fault_snapshot_t last_fault;

现场结构帮助重放;寄存器取值和保存区域必须按芯片与启动代码核对。

11.2.3 复位、halt、attach 与 resume

连接卡死目标时,先确认供电、复位、SWDIO/SWCLK、调试权限和低功耗状态,选择不会自动 mass erase 的命令。reset halt、attach、load、resume 的顺序应符合当前探针和芯片手册;如果固件在启动早期关闭调试或重映射时钟,可能需要硬件复位、connect-under-reset 或安全的最小恢复镜像。

表 2 · 连接与停机:SWD、GDB 与现场的证据判断表
对象应留下的证据不能直接推出
连接probe/target/server能连就身份正确
现场ELF/PC/SP/寄存器读数没有副作用
恢复resume/reset/safe继续运行不会丢证据

11.2.4 ELF、符号与地址一致性

ELF、map、源码和目标 Flash 必须来自同一构建。编译器优化会让变量消失、代码折叠或行号不直观;断点落在近似行不代表执行了那条源语句。保存 ELF/source/config 哈希、链接地址和加载命令,必要时用反汇编、寄存器和栈帧交叉核对。

11.2.5 寄存器、内存与副作用

内存和寄存器访问并非普通数组读写:读状态可能清标志、弹 FIFO 或触发总线,写保留位可能产生未定义行为,停机时 DMA/外设也可能继续改变内存。取证前查当前芯片参考手册,先读安全快照区和核心寄存器,再决定是否访问有副作用的地址,并记录每次访问。

图 2.1 · 连接与停机:SWD、GDB 与现场的判断路径每一格都要有自己的条件。
路径host/probe/target
身份ELF/symbol
停机halt/inspect
恢复resume/reset

从左到右是思考顺序,不是把现象、工具、测试和目标机证据压缩成一个“通过”。

11.2.6 断点、观察点与实时扰动

软件断点可能修改指令,硬件断点/观察点数量有限,单步会改变中断交错,实时 trace 也有带宽和时间成本。GDB 文档对实时程序的警告意味着:若问题依赖时间,优先用非停机 trace、GPIO 标记、环形日志或复制输入;若必须 halt,要把‘在调试器下观察’单列为不同条件。

11.2.7 安全、权限与调试生命周期

调试口是权限边界。开发板可以开放 SWD、读写内存和 monitor 命令,量产设备可能启用读保护、认证调试或彻底关闭端口。证据包不得公开密钥、生产令牌或未经授权的内存内容;记录调试生命周期、解锁审批、恢复镜像和重新锁定步骤。

11.2.8 现场快照与继续运行

最小现场快照包括 reset cause、fault status、PC、LR、SP、任务名/状态、当前锁/队列、时间、构建哈希和安全输出状态。保存后再决定 continue、detach、reset 或断电;继续运行可能覆盖证据,直接复位则可能丢失现场。目标机记录要注明哪些寄存器因副作用未读取。

11.2.9 本节交付:连接停机卡

连接停机卡包括 probe、server、target_id、reset_mode、connection_log、elf_hash、symbol_hash、pc/lr/sp、fault_regs、stack_window、side_effects、security_scope、resume_action 和 target_limits。它把调试器从‘万能钥匙’还原成有前提、有扰动、有恢复动作的测量工具。

CHECKPOINT 11.2把“调试器连接上目标后,哪些寄存器、符号和内存读数才值得相信?”写成一份可复查记录

请围绕“连接与停机:SWD、GDB 与现场”写出对象、输入/故障条件、调试或测试机制、输出与完成语义、一个安全约束、一个失败路径和对应证据。没有目标板时,运行主机路径并把未执行字段标为未执行;拥有目标机时,还要补齐芯片、板卡、探针、工具链、夹具、供电、负载和观察条件。不要用一次断点、一个绿灯、一个复位或一张截图覆盖没有做过的验证。

合格记录要让没有现场上下文的读者知道先查什么、先运行什么、看到什么才算通过,以及哪一句结论仍然不能推出。未知项、停止条件、责任人和回滚动作都是交付的一部分。

03 · HOST TESTING

主机测试:单元、属性与回归

主机测试把纯逻辑、协议、状态机、配置解析和故障策略从寄存器依赖中拆出来。单元测试验证局部合同,属性/变形测试验证规律,回归测试保护已修复的行为;测试通过的范围必须和目标机仍需测量的事实并列呈现。

看懂为函数、状态和数据合同写独立断言。
说清用边界、属性和回归输入扩大覆盖。
开始做让失败输出能定位到输入、版本和假设。
官方基线STM32CubeG0课程设备支持、启动代码与示例的锁定来源;链接指向固定 tag。锁定:v1.6.3 · 核对日期:2026-09-05

11.3.1 测试对象与可观察输出

一个好单元测试只承诺一个局部合同:给定对象、输入、时钟和依赖,函数应返回什么、改变什么、留下什么错误。Python unittest 将 fixture、TestCase、suite 和 runner 分开,测试命名和 discovery 让读者可以独立运行;同样的思想也适合 C 的状态机和协议解析器。

11.3.2 fixture、fake 与依赖隔离

fake 和 stub 的价值是控制依赖,不是让测试永远成功。假时钟应可推进,假总线应记录顺序和副作用,假存储应模拟满、坏块和重启;如果 mock 只返回 happy path,测试会证明 mock 的行为,而不是被测代码的边界。依赖接口越小,主机和目标实现越容易对照。

PYTHON · UNIT TEST
import unittest class QueueContract(unittest.TestCase): def test_full_queue_is_explicit(self): queue, accepted, reason = offer([1, 2], 3, 2) self.assertFalse(accepted) self.assertEqual(reason, "full")

主机断言队列合同;目标机还需测量 ISR、缓存和真实生产速率。

PYTHON · PROPERTY SHAPE
def round_trip(codec, values): for value in values: encoded = codec.encode(value) assert codec.decode(encoded) == value # Save seed and failing value when a generated case breaks.

属性示例强调可重放输入;它不替代目标寄存器和电气验证。

11.3.3 边界、错误和不变量

边界测试应优先覆盖零长度、最大长度、截断、重复、超时、空队列、满队列、无效状态、整数溢出、资源耗尽和异常恢复。每个输入都要有明确处理:拒绝、截断、降级、重试、故障或安全停机。静默接受坏输入往往比显式失败更难定位。

表 3 · 主机测试:单元、属性与回归的证据判断表
对象应留下的证据不能直接推出
局部fixture/input/assert通过 happy path 就够
规律property/seed/mutation覆盖率等于质量
边界host/target scope主机通过就是实机通过

11.3.4 属性与变形测试

属性测试不只检查几个样例,而是检查规律,例如 encode/decode round-trip、排序保持长度、序号去重不增加副作用、同一 seed 产生同一 trace。变形测试可以对输入做可解释变换,再比较输出关系;失败时保存原始输入、变换、seed 和版本,避免随机数字成为不可重放的故事。

11.3.5 随机种子与确定性重放

随机测试必须把 seed、生成器版本、输入摘要、环境和失败序列写入报告。固定 seed 的重放是最低要求;更长期的回归还应把失败样例转成独立命名的确定性测试。这样修复不会依赖‘再随机一次刚好撞到’,也能比较不同实现或编译选项的行为。

图 3.1 · 主机测试:单元、属性与回归的判断路径每一格都要有自己的条件。
局部unit/fixture
规律property/edge
历史regression
边界host/target

从左到右是思考顺序,不是把现象、工具、测试和目标机证据压缩成一个“通过”。

11.3.6 覆盖率、突变与盲区

覆盖率可以发现未经过的行、分支或状态,但高覆盖率不代表断言正确,也不代表真实硬件路径覆盖。突变测试通过故意改变实现来检查测试是否能杀死错误;仍存活的突变是测试盲区提示。报告要说明测量工具、样本集合、排除项和目标机未覆盖的寄存器/时序事实。

11.3.7 回归套件与失败隔离

回归套件要隔离 fixture、避免测试顺序依赖,并保存失败最小输入。一个测试可以在单独运行时通过、整套运行时失败,说明共享状态、随机种子、时间源或临时文件污染了证据。为每项测试设置清理、超时和资源上限,失败后仍输出可以直接重放的命令。

11.3.8 测试报告与主机边界

主机测试卡包括 test_id、contract、fixture、input、expected、actual、seed、command、toolchain、coverage_scope、mutation_notes、replay_command、host_result、target_required 和 stop_conditions。结论应写‘模型合同通过’,而不是把它升级为‘板卡实时性、EMI 或电气安全通过’。

11.3.9 本节交付:主机测试卡

交付问题: 提交命令、种子、报告、覆盖和边界。交付动作是“把结果交给目标机验证单元”,相应输入、输出或未执行边界必须在记录中逐项对应。

CHECKPOINT 11.3把“没有目标板时,怎样先把软件合同、边界和故障策略测扎实?”写成一份可复查记录

请围绕“主机测试:单元、属性与回归”写出对象、输入/故障条件、调试或测试机制、输出与完成语义、一个安全约束、一个失败路径和对应证据。没有目标板时,运行主机路径并把未执行字段标为未执行;拥有目标机时,还要补齐芯片、板卡、探针、工具链、夹具、供电、负载和观察条件。不要用一次断点、一个绿灯、一个复位或一张截图覆盖没有做过的验证。

合格记录要让没有现场上下文的读者知道先查什么、先运行什么、看到什么才算通过,以及哪一句结论仍然不能推出。未知项、停止条件、责任人和回滚动作都是交付的一部分。

04 · TARGET VALIDATION

目标机验证:HIL、覆盖率与测量

目标机验证不是把主机测试重新跑一遍,而是确认平台边界:编译产物、链接地址、时钟、端口、寄存器、IRQ/DMA、功耗、仪器和负载是否让主机断言仍然成立。QEMU、native_sim、HIL 与真实板卡各自覆盖不同事实,应按风险安排层级。

看懂把同一输入/预期映射到仿真与实机。
说清区分 build-only、emulation、HIL 和现场测量。
开始做用波形、串口和寄存器证据补齐主机盲区。
官方基线STM32CubeG0课程设备支持、启动代码与示例的锁定来源;链接指向固定 tag。锁定:v1.6.3 · 核对日期:2026-09-05

11.4.1 仿真、仿真器与真实硬件

主机、仿真器、HIL 和真实板卡是证据层级,不是质量等级。主机适合快速验证算法和合同,QEMU/native_sim 可能覆盖部分架构和系统调用,HIL 把真实输入输出接入固件,实机才承担芯片寄存器、电气、时钟、供电和环境事实。每层报告都要写清‘证明了什么’和‘没有证明什么’。

11.4.2 Twister/ZTest 的测试场景

Zephyr Twister 会扫描测试应用,按场景和平台构建,并可在仿真器或真实设备上运行;它的报告也区分只构建、仿真和设备测试。把同一测试场景放进平台矩阵,能发现配置差异,但不能保证所有本地改动在完整环境成功,仍需审查覆盖。

YAML · TEST MATRIX
tests: realtime.contract: platform_allow: - native_sim - qemu_cortex_m3 harness: ztest realtime.board: platform_allow: - nucleo_f401re harness: console

平台矩阵要区分构建、仿真和真机;名称、版本和板卡清单按项目实际配置。

C · GPIO TIME MARK
TRACE_SET(); run_contract_step(); TRACE_CLEAR(); /* capture UART, GPIO, power and reset cause together */

GPIO 标记只覆盖标记点之间的路径;触发、采样率、负载和环境必须记录。

11.4.3 HIL 夹具与输入生成

HIL 夹具要定义输入精度、延迟、阻抗、时序、复位、故障注入和安全默认态。一个继电器、传感器模拟器或串口脚本都可能改变电源和负载;夹具版本和校准同样要锁定。若输入无法停止、输出过热或保护电路未知,应停止而不是提高测试速度。

表 4 · 目标机验证:HIL、覆盖率与测量的证据判断表
对象应留下的证据不能直接推出
平台host/sim/HIL/board仿真覆盖电气
夹具input/observe/environment串口日志完整
矩阵build/run/failure一块板代表全部

11.4.4 串口、GPIO 与波形证据

串口日志适合语义结果,GPIO 标记适合外部时间差,示波器/逻辑分析仪适合边沿、电平和抖动,寄存器快照适合解释内部状态。把它们映射到同一单调时间线时,要记录时钟、触发、采样率、探头和调试开销;一条漂亮波形不能自动代表整个测试场景。

11.4.5 构建产物与启动核对

烧录成功只说明编程器完成了写入,不说明目标运行的是期望版本。比较 ELF/二进制/配置哈希,读取设备 ID、启动日志和版本回报,确认链接地址、时钟和 FreeRTOS/Zephyr 配置。若目标带安全启动或压缩镜像,还要记录验证链和实际启动槽位。

图 4.1 · 目标机验证:HIL、覆盖率与测量的判断路径每一格都要有自己的条件。
层级host→sim→HIL
夹具input/observe
测量wave/log/reg
边界board/environment

从左到右是思考顺序,不是把现象、工具、测试和目标机证据压缩成一个“通过”。

11.4.6 功耗、温度与环境矩阵

温度、电源、电磁环境、负载、低功耗和通信对端都是测试输入。至少列出室温/高低温、轻载/满载、稳定/边界电压、冷启动/热复位、长时间/短时窗口;若无法执行,要保留未覆盖。环境矩阵不是越多越好,而是对应风险并能重放。

11.4.7 失败重试与现场保存

目标机失败时先保存输入、固件哈希、串口原始输出、波形、寄存器、复位原因、供电和环境,再决定是否重试。重复下载或复位可能清掉唯一 fault 现场;重试必须有上限、间隔和变化说明。失败后应进入安全态,不能让 HIL 夹具继续驱动未知输出。

11.4.8 本节交付:目标验证卡

目标验证卡包括 platform、board_revision、fixture、firmware_hash、config_hash、test_id、input、harness、clock、power、environment、observe_channels、raw_artifacts、retry_policy、host_gap 和 stop_conditions。它让下一单元可以有计划地注入故障,而不是在实机上随意拔线。

CHECKPOINT 11.4把“主机测试之后,怎样把同一条断言带到 QEMU、仿真器和真实板卡?”写成一份可复查记录

请围绕“目标机验证:HIL、覆盖率与测量”写出对象、输入/故障条件、调试或测试机制、输出与完成语义、一个安全约束、一个失败路径和对应证据。没有目标板时,运行主机路径并把未执行字段标为未执行;拥有目标机时,还要补齐芯片、板卡、探针、工具链、夹具、供电、负载和观察条件。不要用一次断点、一个绿灯、一个复位或一张截图覆盖没有做过的验证。

合格记录要让没有现场上下文的读者知道先查什么、先运行什么、看到什么才算通过,以及哪一句结论仍然不能推出。未知项、停止条件、责任人和回滚动作都是交付的一部分。

05 · FAULT INJECTION

故障注入:重现、隔离与恢复

可靠性来自对失败的准备,而不是对成功路径的信仰。故障注入要先定义风险、注入点、可逆性、预期状态、停止条件和证据,再选择主机模型、仿真器或目标板。注入之后要区分检测、隔离、降级、复位和恢复是否真的发生。

看懂建立按阶段组织的故障矩阵。
说清让注入可逆、可重复且有安全边界。
开始做验证恢复后状态,而非只看复位发生。
官方基线Cortex-M0+ Devices Generic User Guide处理器寄存器、异常、栈与调试语义的 Arm 官方基线。锁定:DUI 0662B · 核对日期:2026-09-05

11.5.1 故障模式与危险等级

故障矩阵以阶段和对象组织,而不是以‘随机坏一下’组织。bind、时钟、队列、锁、解析、存储、看门狗和输出各自有不同故障模式、可检测信号和恢复动作。先给每个故障一个 ID、严重度、可逆性和停止条件,才能比较主机注入和目标机注入是否覆盖同一合同。

11.5.2 输入、依赖与时钟注入

输入注入通常最安全:坏长度、重复序号、越界数值、丢包、NACK、延迟和状态翻转可以在主机或仿真中重放。依赖注入要保留真实时序和错误阶段,不能直接跳到最终返回值,否则恢复路径可能没经历等待、部分写入或资源占用。

PYTHON · FAULT MATRIX
def replay(case): return case["expect"] cases = [ {"id": "queue_full", "inject": "producer_burst", "expect": "reject"}, {"id": "clock_stall", "inject": "freeze_time", "expect": "watchdog"}, ] for case in cases: print(case["id"], replay(case))

故障向量要有阶段、预期检测、恢复和停止条件;打印结果不等于安全。

C · SAFE FAULT PATH
void fail_safe(fault_code_t code) { disable_actuators(); save_reset_reason(code); signal_fault_led(code); request_controlled_reset(); }

安全态先于重试和诊断;输出、复位和保存动作要按目标硬件验证。

11.5.3 超时、死锁与活锁重现

超时、死锁和活锁的共同点是‘没有按预期进展’,但证据不同。超时要有 deadline 和单调时基,死锁要有等待图和持有者,活锁要有状态变化却没有有效输出。故障注入应设置上限和外部停止按钮,避免把测试本身变成不可恢复的卡死。

表 5 · 故障注入:重现、隔离与恢复的证据判断表
对象应留下的证据不能直接推出
注入stage/vector/reversible随机拔电最真实
隔离safe/degrade/budgetwatchdog reset 就是恢复
复盘snapshot/post-health重启后 main 就健康

11.5.4 安全态与故障隔离

安全态先于诊断。关闭 PWM、禁止继电器、限速、停止发包或切断高能量负载应有明确顺序,并且故障注入时默认有效。隔离可以是任务边界、消息边界、硬件保护、断路器或独立监视器;任何隔离机制都要测试自身失败时的结果。

11.5.5 重试预算与不可逆动作

重试预算要包括次数、backoff、总时间和副作用。读取状态、重新建立连接通常更容易幂等;写 Flash、启动电机、增加计数器或提交一次性命令可能不能盲目重放。每次重试记录原始响应、已执行动作和剩余预算,超过上限就进入降级、维护或安全停机。

图 5.1 · 故障注入:重现、隔离与恢复的判断路径每一格都要有自己的条件。
注入stage/vector
检测timeout/assert
隔离safe/degrade
恢复reset/replay

从左到右是思考顺序,不是把现象、工具、测试和目标机证据压缩成一个“通过”。

11.5.6 复位原因与故障快照

复位原因、fault status、PC/LR/SP、任务状态、配置/固件哈希、最后 progress、队列年龄、锁持有者和安全输出是恢复证据的核心。先写入可靠保留区或独立日志,再清理和复位;如果保存动作本身可能失败,要准备最小硬件指示和复位计数。

11.5.7 恢复后健康检查

恢复后至少检查身份、时钟/电源、资源、协议版本、队列、锁、内存水位和关键健康信号,不能只看到系统再次进入 main。重新绑定设备、恢复任务和开放输出应分阶段进行;重复失败要限制次数并进入维护模式,防止复位风暴。

11.5.8 本节交付:故障注入卡

故障注入卡包括 fault_id、severity、injection_point、preconditions、vector、reversible、expected_detection、safe_state、retry_budget、snapshot、recovery_steps、post_health、host_result、target_required 和 stop_conditions。它把可靠性从‘测试人员胆子大’变成可审查的安全实验。

CHECKPOINT 11.5把“怎样安全制造一个失败,并证明系统真的按合同恢复?”写成一份可复查记录

请围绕“故障注入:重现、隔离与恢复”写出对象、输入/故障条件、调试或测试机制、输出与完成语义、一个安全约束、一个失败路径和对应证据。没有目标板时,运行主机路径并把未执行字段标为未执行;拥有目标机时,还要补齐芯片、板卡、探针、工具链、夹具、供电、负载和观察条件。不要用一次断点、一个绿灯、一个复位或一张截图覆盖没有做过的验证。

合格记录要让没有现场上下文的读者知道先查什么、先运行什么、看到什么才算通过,以及哪一句结论仍然不能推出。未知项、停止条件、责任人和回滚动作都是交付的一部分。

06 · RELIABLE DELIVERY

可靠交付:CI、报告与维护

可靠交付把代码、配置、测试、目标矩阵、故障记录、覆盖盲区、版本哈希、CI 报告、回滚和维护责任绑定在一起。持续集成可以缩短反馈,但不会替代目标机和现场;报告必须告诉读者跑了什么、没跑什么、失败如何复现、何时必须停止。

看懂构建可重建的测试与报告流水线。
说清让版本、工具、输入和证据互相绑定。
开始做写出维护、回滚、审查与停止条件。
官方基线SPDX Specification用固定规范描述组件、许可证、版本与供应链交付证据。锁定:3.0.1 · 核对日期:2026-09-05

11.6.1 CI 最小闭环

CI 最小闭环从干净工作区开始:固定 Python/C 编译器、依赖、配置、源码提交和环境变量,构建产物,再运行主机测试、静态检查和报告生成。每一步要有明确输入和输出,失败时保留日志与命令。一个开发者电脑上手动点过的绿灯不能代替别人重建。

11.6.2 unittest、CTest 与测试发现

Python unittest 的 discovery、CTest 的测试文件和 Zephyr Twister 的测试场景都把‘如何找到测试’变成配置。统一入口不等于统一平台;报告要保留测试框架、版本、命令、fixture、平台和原始输出,避免 CI 只给一个总数,读者却不知道失败在哪个场景。

SHELL · CI EVIDENCE
python -m unittest discover -s host_tests -p 'test_*.py' ctest --test-dir build --output-on-failure sha256sum build/firmware.elf build/report.json

流水线保留命令、版本、产物哈希和原始报告;CI 绿灯不代替真机证据。

PYTHON · VERDICT
def release_gate(host_ok, target_ok, known_limits): if not host_ok: return "BLOCK" if not target_ok: return "TARGET_REQUIRED" return "PASS_WITH_LIMITS" if known_limits else "PASS"

门槛模型把未执行目标机条件显式化;发布仍需评审、回滚和责任人。

11.6.3 Twister/KUnit 与平台矩阵

平台矩阵应至少分 host、emulator/native_sim、HIL 和真实 board,并列出 build-only、run、环境和证据要求。Linux KUnit 通过 kunit_tool 配置、构建、运行和解析内核测试,也提醒构建目录与配置一致性;这些经验同样适用于 RTOS 目标,不能把‘编译成功’混进‘运行通过’。

表 6 · 可靠交付:CI、报告与维护的证据判断表
对象应留下的证据不能直接推出
重建source/config/tool二进制足够
闸门test/coverage/target绿灯等于可靠
维护owner/rollback/limitsflaky 可以永久忽略

11.6.4 构建产物、依赖与 provenance

provenance manifest 把 source_hash、config_hash、toolchain、dependency_lock、compiler_flags、linker、board_revision、test_seed、artifact_hash 和日期绑在一起。若产物被压缩、签名或转换,还要保存每一步的输入输出。时间戳和文件名只能辅助定位,不能代替内容哈希。

11.6.5 flaky 测试与失败隔离

flaky 测试先分类:产品随机性、时序竞争、环境不稳定、夹具/探针故障、资源占用还是测试本身有共享状态。暂时 quarantine 只能是有责任人、复现命令、期限和退出条件的隔离,不应把失败变成永久忽略;自动重试要保存每一次原始结果。

图 6.1 · 可靠交付:CI、报告与维护的判断路径每一格都要有自己的条件。
重建source/config/tool
验证test/matrix
发布gate/hash
维护rollback/owner

从左到右是思考顺序,不是把现象、工具、测试和目标机证据压缩成一个“通过”。

11.6.6 覆盖率门槛与发布闸门

覆盖率门槛是风险决策,不是装饰数字。未覆盖的异常、复位、低功耗、满载和真实电气路径应有解释、责任人和目标验证计划;合理豁免要写原因和期限。即使覆盖率 100%,错误断言、错误模型或错误配置仍然可能让产品失败。

11.6.7 回滚、维护与责任人

回滚包括旧镜像、旧配置、版本选择、签名/完整性、更新中断、复位原因和维护入口。发布闸门不应允许不可逆动作在证据不足时继续;出现启动失败、重复复位、安全态不确定或数据迁移不可逆时,回到已知镜像或停在维护模式。

11.6.8 已知限制与长期复查

可靠发布卡包括 source/config/toolchain、pipeline、test_discovery、platform_matrix、artifact_manifest、coverage_scope、flaky_policy、release_gates、rollback、owner、known_limits、review_date 和 stop_conditions。它是下一章联网、Linux 与综合交付可以直接复用的可靠性接口。

11.6.9 本节交付:可靠发布卡

交付问题: 提交流水线、报告、门槛和回滚。交付动作是“完成本章到联网/Linux 的交接”,相应输入、输出或未执行边界必须在记录中逐项对应。

CHECKPOINT 11.6把“怎样把一次通过变成别人可以重建、审查和继续维护的可靠性证据?”写成一份可复查记录

请围绕“可靠交付:CI、报告与维护”写出对象、输入/故障条件、调试或测试机制、输出与完成语义、一个安全约束、一个失败路径和对应证据。没有目标板时,运行主机路径并把未执行字段标为未执行;拥有目标机时,还要补齐芯片、板卡、探针、工具链、夹具、供电、负载和观察条件。不要用一次断点、一个绿灯、一个复位或一张截图覆盖没有做过的验证。

合格记录要让没有现场上下文的读者知道先查什么、先运行什么、看到什么才算通过,以及哪一句结论仍然不能推出。未知项、停止条件、责任人和回滚动作都是交付的一部分。

资料卡(访问日期:2026-09-05):Arm CoreSight 与 GDB 文档支撑调试连接、符号和实时扰动边界;Zephyr Twister/ZTest、Python unittest、CTest 与 Linux KUnit/KASAN 提供不同层级的测试与内存安全入口;OpenOCD 文档补充 reset、halt、flash 和 verify 命令。通用测试模型不能替代当前芯片、探针、板卡、供电、夹具、环境和现场恢复条件。