1.目的
本文主要从Sanitizer相关的实现探索其使用的场景,ASan,MSan,UBSan,TSan;介绍其如何解决内存泄漏,缓存区溢出和未定义行为的bug问题
2.概述
Sanitizers路径:https://github.com/google/sanitizers
使用场景:
3.AddressSanitizer (ASan)
1.介绍
AddressSanitizer(简称 ASan)用于检测释放后使用、双重释放、缓冲区(堆栈、堆和全局缓冲区)溢出和下溢以及其他内存错误。
它由一个编译器插桩模块和一个运行时库组成,该库会在运行时使用__ascan_malloc来替换所有的malloc调用,同时会对free进行投毒,用于追溯堆栈的调用关系;插桩模块则是在编译时对每一个store/load指令进行插桩
例如:下图时我们没有使用ASan和使用了ASan的样子
使用AScan后虚拟内存空间则会被划分成如下两个类型:
- Main application memory 主应用程序内存:即应用程序代码所使用的内存。
- Shadow memory 影子内存:即包含有影子数据的内存。与主应用程序内存相关,对主存的一个字节投毒,意味着在对应影子内存中写入特殊数据。
如上逻辑,当出现访问redzone时就会触发Crash了;因此这个也是我们为什么使用ASan需要确保内存空间的原因了。
2.适用平台
3.性能消耗
如上图所见ASan CPU影响大概在~2x,MEM影响大概在~2x
4.使用方法
因为本文为科普篇,这里就不详细介绍了,我们的同事已经有相关的篇幅介绍:详见《地址消毒剂 ·google/sanitizers 维基 ·GitHub上》
4.MemorySanitizer (MSan)
1.介绍
ASan 无法覆盖到未初始化的内存,对于未初始化的内存,进行读取的行为同样危险,这时候就需要MSan;
MSan 将允许您复制未初始化的内存并对其执行简单的逻辑和算术运算,但是当您尝试在决策语句中使用它时,您会得到一个危险信号如下图所示
MSan实现的是一个bit to bit的影子内存,0 代表已初始化过的,1 代表未初始化的,所以开始时,影子内存填满了 0xFF。
2.适用平台
3.性能消耗
MSan 的CPU影响大概在~3x,MEM影响大概在~2x。
4.使用方法
与AScan相比编译选项按照-fsanitize=memory -fPIE -pie 编译并链接程序。
要获取任何堆栈信息添加 -fno-omit-frame-pointer即可
5.UndefinedBehaviorSanitizer (UBSan)
1.介绍
UBSan 主要就是检查未定义行为(undefined behavior),包括有符号整型溢出、空指针的使用、除 0 操作、越界读取等
UBSan 在编译时对可疑操作进行插桩,以捕获程序运行时的未定义行为,如果检查到UB 行为,则会调用 __ubsan_handle_ 函数进行处理,输出报错信息;除了未定义行为,其实还有很多可检查项,比如:
详见《UndefinedBehaviorSanitizer — Clang 19.0.0git documentation (llvm.org)》
正如下面的例子,UB很好的处理了简单的错误:
2.适用平台
3.性能消耗
UBSan 的CPU影响大概在~1.25x,MEM无影响。
4.使用方法
与MScan相比编译选项按照-fsanitize=undefined即可
6.ThreadSanitizer (TSan)
1.介绍
TSan 用来检查数据争用、死锁等问题的
TSan在编译时会在每一个内存存取操作、函数的调用、函数的 entry 和 exit 进行插桩;同样它会使用影子内存来读取相关信息,如下图所示
TScan在运行时主要是维护了状态机的使用,对 libc/pthread 的大部分函数也进行了拦截操作。
程序执行可以看作是一连串的事件:
- 内存存取事件:读、写
- 同步事件:锁、happen-before(signal & wait)
TSan 包含有全局的和 内存 ID (per-ID,a unique ID of a memory location) 的状态。全局状态是关于同步事件的信息,而 per-ID 状态是内存地址的信息。
如下左图所示: SS 代表 set of segment,segment 表示一个线程上包含的一连串的内存存取事件,比如对于一个内存 ID 而言,SSWr 表示对某个内存 ID 进行读操作的事件的集合。
另外判断是否存在数据争用,其主要逻辑是:对同一内存 ID 进行操作时,却没有进行相同的锁来限制,如上右图所示;
2.适用平台
3.性能消耗
TSan 的CPU影响大概在2x~20x,MEM影响大概在5x~10x 。
4.使用方法
TSan 需要所有代码都以 -fsanitize=thread 编译参数进行编译,不然某些代码可能会导致误判。
7.参考资料
1.Sanitizer:https://mp.weixin.qq.com/s/fvqRoXTLXtflC2PN0q9nVg
2. AddressSanitizer:https://clang.llvm.org/docs/AddressSanitizer.html
3.AddressSanitizer-wiki:https://github.com/google/sanitizers/wiki/AddressSanitizer
4.MemorySanitizer:https://static.googleusercontent.com/media/research.google.com/en//pubs/archive/43308.pdf
5.UBSan: https://lwn.net/Articles/617364/
6.ThreadSanitizer/MemorySanitizer:https://llvm.org/devmtg/2012-11/Serebryany_TSan-MSan.pdf