第1章:常见Crash问题概述
各位同学好,我是老张。今天咱们聊聊Camera HAL开发中最让人头疼的问题——Crash。说实话,我做了这么多年嵌入式开发,遇到最多的就是空指针、数组越界这些基础问题。但越是基础,越容易翻车。
你可能觉得:「空指针?这谁不会啊?」但我在项目中见过太多老司机在这上面栽跟头。今天我就把这几类Crash问题的定位和修复方法,掰开了揉碎了讲给你听。
1.1 空指针访问:最熟悉的陌生人
空指针访问,说白了就是你对一个NULL指针解引用了。在Camera HAL里,最常见的场景就是:
- 从HAL接口拿到的buffer指针是NULL
- 某个回调函数没注册,但你直接调用了
- 设备节点打开失败,但你继续操作fd
我个人习惯,在写HAL代码时,所有传入的指针都要做空检查。别嫌麻烦,这是保命的基本功。
典型代码示例:
// 错误写法
void process_buffer(buffer_t *buf) {
buf->data[0] = 0xFF; // 如果buf为NULL,直接崩
}
// 正确写法
void process_buffer(buffer_t *buf) {
if (buf == NULL) {
ALOGE("process_buffer: buf is NULL");
return;
}
buf->data[0] = 0xFF;
}
我的经验:我曾经在一个项目中,因为某个vendor层回调没初始化,导致上层调用时直接空指针。排查了整整两天,最后发现是初始化顺序的问题。从那以后,我所有回调函数都加上了NULL检查,并且用日志打印出来。
1.2 数组越界:看不见的杀手
数组越界比空指针更隐蔽。为什么?因为空指针至少会立刻崩,你能马上知道。但数组越界可能只是覆盖了相邻内存,过一会儿才崩,排查起来特别痛苦。
在Camera HAL里,数组越界常发生在:
- stream配置时,stream数量超过预期
- metadata的tag索引超出范围
- buffer queue的索引计算错误
避坑指南:我曾经在调试一个3A算法时,发现预览画面偶尔出现花屏。查了三天,最后发现是某个数组的索引写成了i++而不是++i,导致在边界条件下越界访问。嗯,这种低级错误,越是老手越容易犯。
我的建议是:所有数组访问都要做边界检查。别相信「这个数组肯定不会超过100个」这种话。你想想看,代码是活的,需求是会变的。
// 安全写法
#define MAX_STREAMS 4
int stream_ids[MAX_STREAMS];
void add_stream(int id) {
if (stream_count >= MAX_STREAMS) {
ALOGE("add_stream: too many streams, max=%d", MAX_STREAMS);
return;
}
stream_ids[stream_count++] = id;
}
1.3 野指针:最让人头疼的问题
野指针,就是指向已经释放的内存的指针。这类问题在C/C++里特别常见,尤其是在Camera HAL这种多线程环境下。
典型的场景:
- 一个线程释放了buffer,另一个线程还在用
- 回调函数中使用了局部变量的地址
- 智能指针循环引用导致内存提前释放
定位技巧:我个人习惯用AddressSanitizer(ASan)来抓野指针。在编译时加上-fsanitize=address,运行时会自动检测非法内存访问。虽然会拖慢性能,但调试阶段绝对值得。
修复野指针的核心思路就一条:谁分配,谁释放;谁使用,谁保护。说白了就是明确内存的所有权。在Camera HAL里,我建议用shared_ptr或者weak_ptr来管理buffer生命周期,别裸用指针。
1.4 内存泄漏:温水煮青蛙
内存泄漏不像Crash那么明显,但长期运行下来,系统会越来越慢,最后OOM(Out Of Memory)挂掉。在Camera HAL里,内存泄漏常发生在:
| 泄漏场景 | 典型原因 | 排查方法 |
|---|---|---|
| buffer未释放 | dequeue后忘记queue回去 | 用dumpsys检查buffer计数 |
| metadata泄漏 | clone后没有free | Valgrind或ASan |
| 线程资源泄漏 | 线程退出时未清理 | 查看/proc/pid/maps |
我的经验:有一次客户反馈相机连续拍照100张后卡死。我查了半天,发现是每次拍照时都new了一个metadata对象,但只有在成功时才释放。失败路径上漏掉了。修复就加了一行delete,但排查花了两天。
对于内存泄漏,我推荐两个工具:
- Valgrind:适合单次测试,能精确定位泄漏位置
- ASan:适合集成测试,运行时检测
但说实话,工具只是辅助。真正要避免内存泄漏,还是得靠代码规范。我个人的习惯是:每次malloc/new,立刻写对应的free/delete。别想着「等会儿再补」,你等会儿就忘了。
1.5 总结:Crash问题的通用排查思路
好了,四种常见Crash问题都讲完了。最后我总结一下通用排查思路:
- 复现:稳定复现是定位的前提。如果概率性出现,先找规律
- 抓日志:打开所有日志级别,特别是ALOGE和SIGSEGV信号
- 看堆栈:用addr2line或ndk-stack解析backtrace
- 加断言:在可疑位置加assert,快速定位
- 用工具:ASan、Valgrind、ThreadSanitizer轮番上阵
下一章,我会详细讲如何用这些工具实战定位Crash。咱们到时候见。