第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问题都讲完了。最后我总结一下通用排查思路:

  1. 复现:稳定复现是定位的前提。如果概率性出现,先找规律
  2. 抓日志:打开所有日志级别,特别是ALOGE和SIGSEGV信号
  3. 看堆栈:用addr2line或ndk-stack解析backtrace
  4. 加断言:在可疑位置加assert,快速定位
  5. 用工具:ASan、Valgrind、ThreadSanitizer轮番上阵

下一章,我会详细讲如何用这些工具实战定位Crash。咱们到时候见。