AddressSanitizer使用总结及原理分析
AddressSanitizer
AddressSanitizer是一个内存工具,可以帮助我们快速发现进程中存在的内存问题,这对写C++人来说是一个神器。
特点
- 高效,官方给出的说法是性能只降低一倍。实测我们游戏没感觉出来性能降低。
- 受到官方编译编译器(gcc,clang)支持
- 支持多个平台:
- 完全支持的平台:linux,OS X,IOS模拟器环境,FreeBSD,Android
- 部分支持的平台:windows(需要使用clang来编译),只支持 /MT /MD,并且不支持检测内存泄漏和use-after-return情况
可检测内容
- use after free:使用已经free的内存
- 各种(堆,栈,静态内存)内存溢出检测
- use after return/use after scope:使用不在作用于的内存
- 静态对象初始化顺序导致的问题
- 内存泄漏
- 申请和释放不匹配
使用方式
- 加入编译选项
-fsanitize=address
,如果在问题出现时打印的堆栈比较清晰,可以加入-fno-omit-frame-pointer
- use after return需要在启动命令前加入
ASAN_OPTIONS=detect_stack_use_after_return=1
- 静态对象初始化顺序需要在启动命令前加入
ASAN_OPTIONS=check_initialization_order=true
- 内存泄漏需要在启动命令前加入
ASAN_OPTIONS=detect_leaks=1
- 如果检测到问题推出的时候产生core,需要在启动命令前加入
ASAN_OPTIONS=disable_coredump=0:unmap_shadow_on_exit=1:abort_on_error=1
原理
替换内存函数
- malloc:在申请的内存周围插入内存(检测内存越界访问,如果越界的比较多就可能没办法检测出来)
- free:将释放的内存放入一个隔离的列表中(检测释放内存被使用)
内存映射
将程序的虚拟内存分为两段:
- 程序内存:程序正常运行需要的内存
- shadown内存:用来记录程序内存是否有效,每8 bytes程序内存会映射到1 byte shadown内存。shadown内存的值有以下几种:
- 0:所有byte都有效
- 负数:所有byte都无效,全无效一般都是插入的内存,并且每种插入的内存对应的负数值不同
- k:表示前k个byte是有效,后8-k个无效,前提:malloc返回的地址内存对齐
程序内存到shadown内存映射关系:
1 | byte* MemToShadow(address) { |
插桩代码
AddressSanitizer会在所有访问内存的地方进行内存检测。类似于
1 | // before |
这也是所有内存工具的常规找问题的方式。难点是如果高效的检测这一块内存是否有效。上面介绍的内存映射可知AddressSanitizer的处理方式:
1 | byte *shadow_address = MemToShadow(address); |
实际的例子:
1 | void foo() { |
其他
- 默认情况下AddressSanitizer遇到问题会中断进程,但是AddressSanitizer提供了方式来避免这种情况(官方说这种方式目前在实验阶段,不是很可靠,并且第一次错误之后的错误可能是误报,所以不建议使用):
- 编译选项加入:
-fsanitize-recover=address
,gcc 5.0之后版本才支持 - 运行的时候加入:
ASAN_OPTIONS=halt_on_error=0
- 编译选项加入:
- use after scope试了gcc 4.8和6.3两个版本,官方例子都没有出结果