一、ASan 是什么
- ASan 全称 AddressSanitizer,通常称为地址消毒器,是一种运行时内存错误检测工具。
- 主要用于检测 C/C++ 程序中的非法内存访问问题。
- 它通过编译器插桩(Instrumentation)和运行时库(Runtime Library)工作。
- 与 GDB 的区别:
- GDB:用于调试程序,查看变量、调用栈、断点和崩溃现场。
- ASan:用于检测内存错误,并报告错误类型、访问位置及相关内存分配/释放位置。
二、ASan 能检测的常见问题
1. Heap Buffer Overflow(堆内存越界)
- 使用 malloc/new 申请的堆内存,访问超出申请范围的地址。
- 例子:
char *p = malloc(10); p[10] = 'A'; // 越界,合法下标为 0~9
2. Stack Buffer Overflow(栈内存越界)
int arr[5] = {0}; arr[5] = 100; // 越界,合法下标为 0~4
3. Global Buffer Overflow(全局变量越界)
4. Use After Free(释放后使用)
- 内存已经通过 free/delete 释放,程序仍然通过旧指针访问它。
- 例子:
int *p = malloc(sizeof(int)); free(p); printf("%d", *p); // 错误:释放后继续访问
- 注意:free(p) 不会自动把指针 p 置为 NULL。
5. Double Free(重复释放)
int *p = malloc(sizeof(int)); free(p); free(p); // 错误
- 常见原因:多个模块对同一资源的所有权和释放责任不清晰。
6. Use After Return(函数返回后使用栈内存)
- 函数返回后,仍然访问该函数栈帧中的局部对象。
- 是否检测到取决于编译器、运行时和相关配置。
7. Memory Leak(内存泄漏)
- 动态申请的内存不再使用,却没有释放。
- 在支持 LeakSanitizer 的平台上,ASan 通常可以配合泄漏检测功能发现此类问题。
- 是否可用取决于平台和运行时支持。
三、ASan 的工作原理
1. 编译器插桩
- 编译时使用
-fsanitize=address。 - 编译器会在内存访问等位置插入额外检查代码。
2. 运行时库(ASan Runtime)
- 程序运行时配合插桩代码执行检查。
- 发现非法访问时,输出错误报告,通常终止程序。
3. Shadow Memory(影子内存)
- ASan 使用额外的影子内存记录应用内存区域的可访问状态。
- 程序访问内存时,插桩代码查询相应状态,判断访问是否合法。
- 可以把它理解为:为程序内存维护一份辅助的状态记录。
4. 性能影响
- ASan 会增加内存占用和运行时间。
- 常见资料中提到的运行时间开销约为 2 倍,但实际开销与程序和平台有关。
- 通常用于开发、测试和问题定位,不应未经评估就用于正式生产版本。
四、GCC / G++ 中如何使用 ASan
1. C 程序编译
gcc -g -O1 -fsanitize=address -fno-omit-frame-pointer test.c -o test
2. C++ 程序编译
g++ -g -O1 -fsanitize=address -fno-omit-frame-pointer test.cpp -o test
3. 运行
./test
4. 常用参数说明
-g:生成调试信息,便于定位源代码行号。-O1:使用较低级别优化,兼顾运行表现和调试定位。-fsanitize=address:启用 AddressSanitizer。-fno-omit-frame-pointer:保留帧指针,有助于生成更清晰的调用栈。
5. 重要
- 编译阶段和最终链接阶段都要带上
-fsanitize=address。 - 多文件工程应确保相关目标文件按一致的方式启用 ASan。
- 运行时需要能够找到匹配的 ASan Runtime。
五、CMake 工程中启用 ASan
可以在 CMakeLists.txt 中添加:
cmake_minimum_required(VERSION 3.10) project(MyProject) set(CMAKE_CXX_STANDARD 11) add_compile_options( -g -O1 -fsanitize=address -fno-omit-frame-pointer ) add_link_options( -fsanitize=address ) add_executable(app main.cpp network.cpp video.cpp )
构建和运行:
mkdir build cd build cmake .. cmake --build . ./app
说明:
add_compile_options用于添加编译选项。add_link_options用于添加链接选项。- 如果只想对某个目标启用,可以使用
target_compile_options()和target_link_options()。 add_link_options()需要较新的 CMake 版本;旧版本可使用目标级链接选项等方式配置。
六、如何阅读 ASan 错误报告
常见报告关键字:
heap-buffer-overflow:堆内存越界。stack-buffer-overflow:栈内存越界。global-buffer-overflow:全局变量内存越界。heap-use-after-free:访问已经释放的堆内存。attempting double-free:重复释放同一块内存。
阅读报告时重点关注:
- ERROR:错误类型。
- READ / WRITE:非法读取还是非法写入。
- at ... file.c:行号:错误访问发生的位置。
- allocated by:内存最初申请的位置。
- freed by:内存释放的位置。
- thread T0 / T1:发生问题的线程信息。
排查思路:
- 先确认错误类型。
- 找到报告中的源代码行号。
- 检查出错地址对应的内存对象及其边界。
- 如果涉及动态内存,继续查看分配和释放位置。
- 沿调用栈检查资源所有权、生命周期和并发访问逻辑。
- 修复后重新编译并运行,确认报告消失,并补充回归测试。
七、ASan 在嵌入式 Linux 项目中的用途
1. TCP 网络通信
- 检查接收缓冲区长度计算错误。
- 检查数据拷贝时是否超出缓冲区边界。
2. 视频流与图像处理
- 检查视频帧缓存越界。
- 检查帧缓冲区释放后仍被其他模块访问。
- 检查图像尺寸、步长和内存长度计算错误。
3. 多线程程序
- 检查线程访问已经释放的对象。
- 检查队列节点、任务对象等资源的生命周期问题。
- 注意:ASan 不是专门的数据竞争检测工具,数据竞争通常使用 TSan。
4. 链表、队列和缓存管理
- 检查删除节点后继续访问。
- 检查重复释放节点。
- 检查缓冲区读写越界。
5. 动态内存管理
- 检查申请后未释放的内存(需要平台支持泄漏检测)。
- 帮助定位内存对象的分配和释放位置。
八、交叉编译和开发板上的注意事项
在 i.MX6ULL、RK3568 等嵌入式 Linux 平台上使用 ASan,需要同时满足:
- 交叉编译器支持
-fsanitize=address。 - 目标架构有匹配的 ASan 运行时库。
- 目标根文件系统中包含运行时所需的库及依赖。
- 程序能够在开发板上正确加载这些库。
- 编译器、运行时库和目标系统的架构及 ABI 必须匹配。
注意:
- 编译器接受
-fsanitize=address,不代表目标板一定能够运行程序。 - 交叉编译时要检查工具链 sysroot 和开发板的根文件系统中有没有 ASan 的运行时库。
- 建议先在 Ubuntu x86 Linux 上练习,再研究目标板上的支持情况。
- 嵌入式设备资源有限,ASan 的额外内存和性能开销需要评估。
九、相关工具对比
1. GDB
- 用途:断点调试、单步执行、查看变量、调用栈和崩溃现场。
2. ASan(AddressSanitizer)
- 用途:检测堆/栈/全局内存越界、释放后使用、重复释放等问题。
3. TSan(ThreadSanitizer)
4. UBSan(UndefinedBehaviorSanitizer)
- 用途:检测部分未定义行为,例如有符号整数溢出、非法移位、某些类型对齐问题等。
5. Valgrind
- 用途:动态分析内存访问错误、内存泄漏等;运行开销通常较大,且需要考虑目标平台支持。
十、建议掌握程度
对于嵌入式 Linux C/C++ 应用开发,建议掌握:
- 理解 ASan 的作用和基本工作原理。
- 能区分堆越界、栈越界、Use After Free、Double Free。
- 能使用 GCC / G++ 的
-fsanitize=address编译程序。 - 能在 CMake 工程中启用 ASan。
- 能读懂 ASan 报告,定位错误访问、内存申请和释放位置。
- 了解 ASan 与 GDB、TSan、UBSan、Valgrind 的区别。
- 了解交叉编译时 ASan Runtime 的依赖。
建议动手练习:
- 编写一个堆内存越界程序。
- 编写一个栈数组越界程序。
- 编写一个 Use After Free 程序。
- 编写一个 Double Free 程序。
- 分别使用 ASan 编译运行,观察报告并修复问题。
一句话总结:ASan 是一种通过编译器插桩和运行时检查来发现内存错误的工具。它能帮助开发者定位非法内存访问及相关的申请、释放位置,是 C/C++ 程序开发和测试阶段非常实用的内存排错工具。