Administrator
发布于 2024-01-25 / 11 阅读
0
0

asan内存检测

asan内存检测

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设置
  • 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设置

  • 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 的时候,添加该参数


do_cmake.sh -DWITH_ASAN=ON

编译时踩过的坑

asan 版本 要和 gcc 版本相匹配,目前测试了gcc 8 和 gcc 9的,两者都能编译,但是 gcc8 会遇到很多问题,用gcc9 编译则不会,gcc9 对应的 asan6 ,gcc 匹配对应的glibc 版本

\\![](http://rui.vin/../%E5%9B%BE%E7%89%87/98f6a84f5c0c8311c1d32a608b2fc60f.png)

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

当前编译成功且跑得通的配置是:


[root@node86 build]# uname -a
Linux node86 4.18.0-305.3.1.el8.x86_64 #1 SMP Tue Jun 1 16:14:33 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux

[root@node86 build]# gcc -v
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/opt/rh/gcc-toolset-9/root/usr/libexec/gcc/x86_64-redhat-linux/9/lto-
gcc version 9.2.1 20191120 (Red Hat 9.2.1-2) (GCC)

[root@node86 build]# ll /lib64/libasan.so.6
lrwxrwxrwx. 1 root root 16 Nov 13  2021 /lib64/libasan.so.6 -> libasan.so.6.0.0
[root@node86 build]#

[root@node86 build]# ldd --version
ldd (GNU libc) 2.28
Copyright (C) 2018 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Written by Roland McGrath and Ulrich Drepper.
[root@node86 build]#

gcc 和 glibc 版本都不能太低

运行时遇到的问题

我目前就时搭建一个简单的单点存储,用 vstart 搭建

  • 搭建之前的准备工作

# 开启内存泄漏检测、遇到检测异常不退出、日志重定向
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 导入作为全局变量,因为导入后,使用一些系统命令时会卡死,直接在运行程序前添加即可,如果不加,默认不开

LD_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之后继续使用)

!Pasted image 20240129182101

  • Out of memory

!Pasted image 20240129200307

  • detecte memory(堆溢出)

!Pasted image 20240130145834

内存泄漏误报场景

  • 结构体非 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的情况下一般都很难发现。
  • 一些显而易见的访问无效内存操作可能会被编译器优化而会漏报。

评论