asan内存检测
Last updated on 8 months ago
asan 是什么
ASAN(Address Sanitizer)是针对 C/C++ 的快速内存错误检测工具,用于检测原生代码中的内存错误。相对于我们比较熟悉的 valgrind ,Asan 开销更小、检测到的错误范围更大;
ASAN 早先是 LLVM 中的特性,后被集成到 GCC 4.8 中,可通过在编译时设置 CFLAGS,指定相关的编译参数来开启ASAN 特性
ASan 可以检测以下问题:
- 堆栈和堆缓冲区上溢/下溢
- 释放之后的堆使用情况
- 超出范围的堆栈使用情况
- 重复释放/错误释放
相关参数配置
在官方的wiki 有更多的介绍, 这里介绍常用的 https://github.com/google/sanitizers/wiki
编译flag
- -fsanitize=address:启用快速内存错误检测器 ASAN,内存访问指令用于检测out-of-bounds 和 use-after-free 错误
- -fsanitize-recover=addressr : 不能遇到错误就简单退出,而是继续运行,采用该选项支持内存出错之后程序继续运行(需要叠加设置 ASAN_OPTIONS=halt_on_error=0 才会生效;若未设置此选项,则内存出错即报错退出)
…..
运行时flag
ASAN_OPTIONS设置
- ASAN_OPTIONS是Address-Sanitizier的运行选项环境变量。
- halt_on_error=0:检测内存错误后继续运行 (编译时需要添加参数 -fsanitize-recover=addressr )
- detect_leaks=1:使能内存泄露检测 (默认是开启的)
- malloc_context_size=15:内存错误发生时,显示的调用栈层数为15
- log_path=/home/asan.log:内存检查问题日志存放文件路径
- suppressions=$SUPP_FILE: 屏蔽打印某些内存错误
- detect_stack_use_after_return=1:检查访问指向已被释放的栈空间
- handle_segv=1:处理段错误;也可以添加handle_sigill=1处理SIGILL信号
- quarantine_size=4194304:内存cache可缓存free内存大小4M
使用方法: export ASAN_OPTIONS=halt_on_error=0:use_unaligned=4
LSAN_OPTIONS设置
LSAN_OPTIONS是LeakSanitizier运行选项的环境变量,而LeakSanitizier是ASAN的内存泄漏检测模块,常用运行选项有:
exitcode=0:设置内存泄露退出码为0,默认情况内存泄露退出码0x16 (在程序中 如果有检测 退出码的, 需要注意)
use_unaligned=4:4字节对齐
export LSAN_OPTIONS=exitcode=0:use_unaligned=4
LeakSanitizer: detected memory leaks
handle_segv=1:处理段错误;也可以添加handle_sigill=1处理SIGILL信号
使用方法: export LSAN_OPTIONS=exitcode=0:use_unaligned=4
在ceph 中怎么使用?
ceph 编译宏中已经加入了相关配置,用以控制是否开启 asan
- -DWITH_ASAN=ON
再执行do_cmake.sh 的时候,添加该参数1
do_cmake.sh -DWITH_ASAN=ON
编译时踩过的坑
asan 版本 要和 gcc 版本相匹配,目前测试了gcc 8 和 gcc 9的,两者都能编译,但是 gcc8 会遇到很多问题,用gcc9 编译则不会,gcc9 对应的 asan6 ,gcc 匹配对应的glibc 版本
**
asan 和gcc 版本对应关系
在 GCC 4.8后 就集成asan ,每个gcc 版本都有相对的 asan 库
What is the difference between these packages: The different packages are for different versions of gcc libasan0: gcc-4.8
libasan2: gcc-5
libasan3: gcc-6
libasan4: gcc-7
libasan5: gcc-8 ( libasan5 相关的bug ,遇到rwlock 会core掉 gnu bug )
libasan6: gcc-9
当前编译成功且跑得通的配置是:
1 |
|
gcc 和 glibc 版本都不能太低
运行时遇到的问题
我目前就时搭建一个简单的单点存储,用 vstart 搭建
搭建之前的准备工作
1
2
3
4# 开启内存泄漏检测、遇到检测异常不退出、日志重定向
export ASAN_OPTIONS=detect_leaks=1:halt_on_error=0:log_path=/home/asan.log
# 设置内存泄露退出码为0
export LSAN_OPTIONS=exitcode=1编译asan的时候,需要 用 LD_PRELOAD 来指定 asan的库,优先加载 asan的库,不建议用export 导入作为全局变量,因为导入后,使用一些系统命令时会卡死,直接在运行程序前添加即可,如果不加,默认不开
1
2
3
4
5LD_PRELOAD=/usr/lib64/libasan.so.6 <your daemon>
# eg
LD_PRELOAD=/usr/lib64/libasan.so.6 ./bin/ceph -s
为什么要用 LD_PRELOAD?
Linux操作系统的动态链接库在加载过程中,动态链接器会优先读取
LD_PRELOAD
环境变量和默认配置文件/etc/ld.so.preload
, 并将读取到的动态链接库文件进行预加载。即使程序不依赖这些动态链接库,LD_PRELOAD
环境变量和/etc/ld.so.preload
配置文件中指定的动态链接库依然会被加载,因为它们的优先级比LD_LIBRARY_PATH环境变量所定义的链接库查找路径的文件优先级要高,所以能够提前于用户调用的动态库载入LD_PRELOAD > LD_LIBRARY_PATH > /etc/ld.so.cache > /lib > /usr/lib
asan编译时候是 重入了关于内存的函数,需要替换成glibc提供的接口 ,如mallco,memcpy等等(所以才能监控内存的使用),使用LD_PRELOAD 优先加载asan 库,这意味着就程序代码优先使用asan的函数
具体的案例分析
- use after free(free之后继续使用)
- Out of memory
- detecte memory(堆溢出)
内存泄漏误报场景
- 结构体非 4 字节对齐:报错提示结构体 A 内存泄漏,A 内存的指针存放在结构体 B 中,A 内存指针在结构体 B 中的偏移量非 4 的整数倍,由于 ASan 扫描内存时是按照 4 字节偏移进行,从而扫描不到 A 内存指针导致误报。解决方法:对非4字节对齐的结构体进行整改。
- 信号栈内存:该内存是在信号处理函数执行时做栈内存用的,其指针会保存在内核中,所以在用户态的 ASan 扫描不到,产生误报;
- 内存指针偏移后保存;
- 存在ASan未监控的内存接口;
- 越界太离谱,越界访问的地址不在 buffer 的 redzone 内:
- 对于memcpy的dest和src是在同一个malloc的内存块中时,内存重叠的情况无法检测到。
- ASan对于overflow的检测依赖于安全区,而安全区总归是有大小的。它可能是64bytes,128bytes或者其他什么值,但不管怎么样终归是有限的。如果某次踩踏跨过了安全区,踩踏到另一片可寻址的内存区域,ASan同样不会报错。这是ASan的另一种漏检。
- ASan对于UseAfterFree的检测依赖于隔离区,而隔离时间是非永久的。也就意味着已经free的区域过一段时间后又会重新被分配给其他人。当它被重新分配给其他人后,原先的持有者再次访问此块区域将不会报错。因为这一块区域的shadow memory不再是0xfd。所以这算是ASan漏检的一种情况。
在项目中的应用注意事项
- 项目的构建方案应当有编译选项,能随时启用/关闭ASan。
- 项目送测阶段可以打开ASan,以帮助暴露更多的低概率诡异问题。
- 请勿在生产版本中启用ASan,其会降低程序运行速度大概2-5倍,并会出现内存持续增长现象(占用的RedZone并不会自动释放,所以会出现内存溢出的假象,关闭ASan现象即会消失)。
- 实际开发测试过程中通过ASan扫出的常见问题有:多线程下临界资源未加保护导致同时出现读写访问,解决方案一般是对该资源恰当地加锁即可;内存越界,如申请了N字节的内存却向其内存地址拷贝大于N字节的数据,这种情况在没有开启ASan的情况下一般都很难发现。
- 一些显而易见的访问无效内存操作可能会被编译器优化而会漏报。