简介
Sanitizer 项目是谷歌出品的一个开源项目,提供直接使用现有构建系统和现有测试资产的运行时错误查找技术。该项目包含了 ASAN(内存错误检测工具)、LSAN(内存泄漏检测工具)、MSAN(未初始化内存读取的检测工具)、TSAN(线程间数据竞争的检测工具)等内存、线程错误的检测工具。 ASAN全称 AddressSanitizer,可以用来检测内存问题,例如缓冲区溢出或对悬空指针的非法访问等。
根据谷歌的工程师介绍 ASAN 已经在 chromium 项目上检测出了300多个潜在的未知bug,而且在使用 ASAN 作为内存错误检测工具对程序性能损耗也是及其可观的。根据检测结果显示可能导致性能降低2倍左右,比Valgrind(官方给的数据大概是降低10-50倍)快了一个数量级。
从LLVM3.1、GCC4.8、XCode7.0、MSVC16.9开始ASAN就已经成为众多主流编译器的内置工具了,因此,要在项目中使用ASAN也是十分方便。现在只需要在编译命令中加上-fsanitize=address检测选项就可以让ASAN在你的项目中大展神通。
原理:
内存泄漏的检测原理,基本上都是通过 hook malloc free 等内存分配函数实现,asan 同样也是如此。详细介绍 : https://github.com/google/sanitizers/wiki/AddressSanitizerAlgorithm
使用方式:
使用 ASAN 的方式很简单,因为编译器已经将 ASAN 作为内置工具,对于手动使用 gcc 构建的工程,使用非常简单,只需要在编译的时候添加-fsanitize=address编译选项即可。对于 cmake , configure 等构建工程的使用方式稍微麻烦一点,我已经总结出来放在文档最后。
实战分析:
当编译目标程序编译完成之后,运行目标程序即可。程序退出后,检测日志输出到标准输出。下面使用简单的示例说明:
源码如下:
Main.c int main() { char* s = new char[256]; return 0; } |
编译:
g++ -fsanitize=address -g main.cpp -o main |
运行:
$ export LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libasan.so.5 $ ./main ================================================================= ==2499561==ERROR: LeakSanitizer: detected memory leaks Direct leak of 256 byte(s) in 1 object(s) allocated from: #0 0x7fadbeb0f787 in operator new[](unsigned long) ../../../../src/libsanitizer/asan/asan_new_delete.cc:107 #1 0x56386698619e in main /root/memoryLeakTest/main.cpp:3 #2 0x7fadbe4e6082 in __libc_start_main ../csu/libc-start.c:308 SUMMARY: AddressSanitizer: 256 byte(s) leaked in 1 allocation(s). |
Direct leak of 256 byte(s) in 1 object(s) allocated from 表示 有256 byte 的内存为 直接泄漏。下面为导致该泄漏的调用堆栈。
除了上述的直接泄漏,还会下图这样的间接泄漏,Indirect leak 表示泄漏是不确定的,可能是误报,需要结合代码进行分析。
总之 asan 对于内存泄漏的检测还是很全面。不管是直接泄漏,还是间接泄漏都是需要我们注意的。Asan 除了内存泄漏的检测,还有开头介绍的一些其他内存错误的检测工具,可以帮助我们是自己的程序更加健壮,感兴趣的可以自行研究。
其他工程使用方式
- 使用 gcc 命令直接编译
gcc example.c -o -fsanitize=address -g |
- 使用 cmake 构建的工程
直接在 CMakelists.txt 中添加以下代码:
set(CMAKE_CXX_FLAGS "-fsanitize=undefined,address,leak -fno-omit-frame-pointer") set(CMAKE_C_FLAGS "-fsanitize=undefined,address,leak -fno-omit-frame-pointer") set(CMAKE_L_FLAGS "-fsanitize=undefined,address,leak -fno-omit-frame-pointer") |
- 使用 qmake 构建的工程
和上述 cmake一样,直接在 pro 工程中添加以下代码即可:
QMAKE_CXXFLAGS += -fsanitize=undefined,address,leak -fno-omit-frame-pointer QMAKE_CFLAGS += -fsanitize=undefined,address,leak -fno-omit-frame-pointer QCMAKE_LFLAGS += -fsanitize=undefined,address,leak -fno-omit-frame-pointer |
- 使用 CMake 构建的工程
set(CMAKE_CXX_FLAGS "-fsanitize=undefined,address,leak -fno-omit-frame-pointer") set(CMAKE_C_FLAGS "-fsanitize=undefined,address,leak -fno-omit-frame-pointer") set(CMAKE_L_FLAGS "-fsanitize=undefined,address,leak -fno-omit-frame-pointer") |
- 使用 configure 构建的工程
./configure CPPFLAGS= “ -fsanitize=undefined,address,leak -fno-omit-frame-pointer” CFLAGS=” -fsanitize=undefined,address,leak -fno-omit-frame-pointer” LDFLAGS=” -fsanitize=undefined,address,leak -fno-omit-frame-pointer” |
- 使用 meson 构建的工程
meson -Db_sanitize=address |
- Deb 包构建
如果需要构建 带有 ASAN 的 deb 包,设置以下环境变量,再通过构建命令构建deb 即可
export DEB_CXXFLAGS_SET="-fsanitize=undefined,address,leak -fno-omit-frame-pointer" export DEB_CFLAGS_SET="-fsanitize=undefined,address,leak -fno-omit-frame-pointer" export DEB_LDFLAGS_SET="-fsanitize=undefined,address,leak -fno-omit-frame-pointer" |
参考文献:
Asan 项目wiki: https://github.com/google/sanitizers/wiki/AddressSanitizer
Microsoft Asan 介绍:AddressSanitizer | Microsoft Learn