3、故障注入技术:硬件故障注入与软件故障注入
故障注入,说白了就是故意让系统出问题。你可能会问:好好的系统,干嘛要折腾它?
我在项目中遇到过不少这样的场景:产品在实验室跑得好好的,一到客户现场就莫名其妙地重启。查了几天,最后发现是电源纹波在特定负载下超标了。如果早做电压扰动注入,这个问题在研发阶段就能暴露。
这一章,我们就来聊聊怎么主动给系统“下绊子”,看看它到底有多扛造。
3.1 硬件故障注入
硬件故障注入,就是直接在物理层面对电路施加干扰。我个人习惯把它分成两类:电压扰动和信号毛刺。
3.1.1 电压扰动注入
电压扰动,说白了就是让电源“喘口气”。你想想看,一个嵌入式系统如果供电不稳,会发生什么?
- 欠压:CPU 逻辑电平混乱,程序跑飞
- 过压:器件击穿,永久损坏
- 纹波:时钟抖动,通信误码
- 跌落:系统复位,数据丢失
我在做一款工业控制器时,就遇到过纹波导致 ADC 采样值跳变的问题。当时用示波器一看,电源纹波有 80mV,而 ADC 的参考电压纹波抑制比只有 60dB,算下来误差刚好超标。
常用的注入手段有这些:
| 注入方式 | 实现方法 | 典型场景 |
|---|---|---|
| 直流偏移 | 串联可调直流源 | 模拟电池电压下降 |
| 交流叠加 | 信号发生器 + 功率放大器 | 模拟电源纹波 |
| 瞬态跌落 | 电子负载快速切换 | 模拟大电流冲击 |
| 脉冲干扰 | 静电枪 / 浪涌发生器 | 模拟 ESD / 雷击 |
3.1.2 信号毛刺注入
信号毛刺,就是给数字信号线上塞一个窄脉冲。为什么这么做?因为很多芯片的时序容限并没有数据手册写的那么宽裕。
我曾经测试一款 SPI Flash,数据手册说片选信号低电平时间最小 20ns。我实际用毛刺发生器注入 18ns 的片选脉冲,结果有 3% 的概率读出错误数据。这个 bug 在正常时序下根本复现不了。
信号毛刺的典型注入点:
- 时钟线:注入毛刺导致时钟沿偏移,触发时序违规
- 数据线:注入毛刺改变数据采样值,模拟串扰
- 控制线:注入毛刺模拟错误的使能/复位信号
- 中断线:注入毛刺模拟虚假中断
3.2 软件故障注入
硬件注入成本高、周期长。软件注入就灵活多了,你可以在代码层面模拟各种异常。
3.2.1 代码插桩
代码插桩,就是在源代码或二进制中插入故障触发点。我常用的做法是:
- 在关键函数入口/出口插入钩子
- 运行时通过配置文件控制故障类型和概率
- 故障触发后记录上下文(寄存器、堆栈)
举个例子,模拟内存分配失败:
// 原始代码
void* my_malloc(size_t size) {
return malloc(size);
}
// 插桩后的代码
void* my_malloc(size_t size) {
// 故障注入点:模拟 5% 概率的分配失败
if (fault_inject_enabled && (rand() % 100 < 5)) {
log_fault("malloc_fail", size);
return NULL;
}
return malloc(size);
}
嗯,这里要注意:插桩代码不能影响正常逻辑的时序。我见过有人插桩后忘了关,结果生产版本里还带着故障注入代码,导致系统随机崩溃。
3.2.2 API 劫持
API 劫持,就是拦截系统调用或库函数,替换成你的故障模拟版本。这种方法不需要修改源代码,特别适合测试第三方库或闭源组件。
在 Linux 上,我常用 LD_PRELOAD 来实现:
// fault_malloc.c
#define _GNU_SOURCE
#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>
void* malloc(size_t size) {
static void* (*real_malloc)(size_t) = NULL;
if (!real_malloc) {
real_malloc = dlsym(RTLD_NEXT, "malloc");
}
// 模拟 10% 概率返回 NULL
if (rand() % 100 < 10) {
fprintf(stderr, "[FAULT] malloc(%zu) returns NULL\n", size);
return NULL;
}
return real_malloc(size);
}
// 编译:gcc -shared -fPIC -o libfault_malloc.so fault_malloc.c -ldl
// 运行:LD_PRELOAD=./libfault_malloc.so ./your_program
我在测试一个网络协议栈时,就用这个手法劫持了 send() 和 recv(),模拟丢包和超时。效果立竿见影,半小时就找到了三个未处理的错误码。
3.3 故障注入知识体系
下面这张图,是我自己梳理的故障注入技术全景。你可以把它当作一个检查清单,做可靠性验证时对照着看,不容易漏项。
3.4 故障注入的工程实践要点
最后,分享几个我在项目中踩过的坑:
- 不要只做单点注入:真实故障往往是组合出现的。比如电压跌落的同时,通信线路上刚好有个毛刺。我建议设计组合注入场景。
- 记录注入日志:每次注入的时间、类型、参数、系统响应都要记下来。否则复现问题的时候,你根本不知道当时做了什么。
- 区分故障和缺陷:故障注入是为了验证系统的容错能力,不是用来找代码 bug 的。如果注入后系统崩溃,先确认是容错逻辑没写好,还是注入本身破坏了保护机制。