Google出品的内存检测工具AddressSanitizer介绍与分析
介绍
AddressSanitizer是Google旗下的一个内存问题检测工具,项目地址:https://github.com/google/sanitizers/wiki/AddressSanitizer
它与传统的内存问题检测工具,例如 Valgrind
,有何区别?
用过 Valgrind
的朋友应该都清楚,其会极大的降低程序运行速度,大约降低10倍,而 AddressSanitizer
大约只降低2倍,这是什么概念,果然是Google大法好!
具体使用
在LLVM及高版本编译器中已经自带了该工具,编译时添加 -fsanitize=address
选项。
正常运行程序,如有内存相关问题,即会打印异常信息。
工具原理
工具用法比较简单,这里想重点说说该工具的原理。
可参考文档:https://github.com/google/sanitizers/wiki/AddressSanitizerAlgorithm
由于是内存检测工具,其需要对每一次内存读写操作进行检查:*address = ...; // or: ... = *address;
进行如下的逻辑判断:
1 | if (IsPoisoned(address)) { |
如果指针读写异常,则统计及打印异常信息,可见整个工具的关键在于 IsPoisoned
如何实现,该函数需要快速而且准确。
内存映射
其将内存分为两块:
- 主内存:程序常规使用
- 影子内存:记录主内存是否可用等meta信息
如果有个函数 MemToShadow
可以根据主内存地址获取到对应的影子内存地址,那么内存检测的实现,可以改写为:
1 | shadow_address = MemToShadow(address); |
影子内存
AddressSanitizer
用 1 byte 的影子内存,记录主内存中 8 bytes 的数据。
为什么是 8 bytes ,因为malloc分配内存是按照 8 bytes 对齐。
这样,8 bytes 的主内存,共构成 9 种不同情况:
- 8 bytes 的数据可读写,影子内存中的value值为 0
- 8 bytes 的数据不可读写,影子内存中的value值为 负数
- 前 k bytes 可读写,后 (8 - k) bytes 不可读写,影子内存中的value值为 k 。
如果 malloc(13)
,根据 8 bytes 字节对齐的原则,需要 2 bytes 的影子内存,第一个byte的值为 0,第二个byte的值为 5。
这时,整个判断流程,可改写为:
1 | byte *shadow_address = MemToShadow(address); |
主内存映射到影子内存
MemToShadow
采用简单直接映射的方式
64-bit Shadow = (Mem >> 3) + 0x7fff8000;
32-bit Shadow = (Mem >> 3) + 0x20000000;
例子
如何检测数组访问越界:
1 | void foo() { |
AddressSanitizer
将其改写为:
1 | void foo() { |
如图:
将 char a[8]
两侧用 redzone
包夹,这样数组访问越界时,立马能够侦测。