Clang 12 documentation
Clang 12 documentation包含了一系列工具,如 AddressSanitizer、ThreadSanitizer、LeakSanitizer、LibTooling等。
- clang之AddressSanitizer
- clang之MemorySanitizer
- clang之LeakSanitizer
- clang之UndefinedBehaviorSanitizer
- clang之Hardware-assisted-AddressSanitizer
- clang之SafeStack
- clang之ShadowCallStack
- clang之ThreadSanitizer
- clang之Thread-Safety-Analysis
- clang之DataFlowSanitizer
这部分是对clang文档 Clang 12 documentation MemorySanitizer 的翻译。仅供参考。
介绍
MemorySanitizer 是一个内存检测器,能够检测未初始化内存的读取操作。包含一个编译器插桩模块和一个运行时库。
通常情况下,MemorySanitizer 会带来3x的性能损耗。
如何构建
使用 CMake 来构建 LLVM/Clang。
用法
简单地使用 -fsanitize=memory 标记来编译链接代码即可。MemorySanitizer 的 run-time library 应该被链接到最终的可执行文件中,所以确保使用 clang(而非ld) 来执行最终的链接操作。当链接共享库的时候,MemorySanitizer runtime 库不会被链接。所以,-Wl, -z, defs 可能导致链接错误(不要跟 MemorySanitizer 一起使用)。使用 -O1 或更高的优化标记,可以获取更为合理的性能。使用 -fno-omit-frame-pointer ,可以在错误信息中提取更有意义的栈帧信息。为了获取完美的栈帧信息,可能需要禁用内联(仅使用 -O1)和跟踪调用消除(-fno-optimize-sibling-calls)(这里英文是 tail call elimination)。
% cat umr.cc
#include <stdio.h>
int main(int argc, char** argv) {
int* a = new int[10];
a[5] = 0;
if (a[argc])
printf("xx\n");
return 0;
}
% clang -fsanitize=memory -fno-omit-frame-pointer -g -O2 umr.cc
如果检测到了一个bug,程序会打印出一个错误信息到 stderr,并且退出(退出状态码非0)。
% ./a.out
WARNING: MemorySanitizer: use-of-uninitialized-value
#0 0x7f45944b418a in main umr.cc:6
#1 0x7f45938b676c in __libc_start_main libc-start.c:226
默认情况下,MemorySanitizer 会在检测到第一个错误的时候退出。如果错误报告难以理解,尝试开启 origin tracking。
__has_feature(memory_sanitizer)
在一些场景下,可能需要根据 MemorySanitizer 是否开启来执行不同的代码。__has_feature可以用来实现这一的目的。
#if defined(__has_feature)
# if __has_feature(memory_sanitizer)
// code that builds only under MemorySanitizer
# endif
#endif
attribute((no_sanitize("memory")))
有一些代码不应该使用 MemorySanitizer 来检测。在一些特定函数中,可以使用函数属性 no_sanitize("memory") 来禁用未初始化检查。 MemorySanitizer 依然需要插桩来避免错误提示。该属性在其他编译器中可能不支持,所以建议将其与 __has_feature(memory_sanitizer) 一起使用。
禁用名单
在 Sanitizer 的特定使用场景中,MemorySanitizer 支持 src and fun entity types ,可以用于针对特定源码文件和函数不要使用 MemorySanitizer 检查。所有的使用未初始化的警告都会单独剔除,所有从内存中加载的值都会被认为是已初始化过的。
符号化报告
MemorySanitizer 使用一个外部的符号化器,在报告中打印文件和行号信息。确保 llvm-symbolizer 二进制的路径已经被加入到了 $PATH 中,或者设置环境变量 MSAN_SYMBOLIZER_PATH 指向该路径。
来源追踪
MemorySanitizer 可以跟踪未初始化值的来源,类似于 Valgrind 中的 –track-origins 选项。该特性可以使用Clang选项 -fsanitize-memory-track-origins=2 (或者简单使用 -fsanitize-memory-track-origins)来开启。样例代码如下:
% cat umr2.cc
#include <stdio.h>
int main(int argc, char** argv) {
int* a = new int[10];
a[5] = 0;
volatile int b = a[argc];
if (b)
printf("xx\n");
return 0;
}
% clang -fsanitize=memory -fsanitize-memory-track-origins=2 -fno-omit-frame-pointer -g -O2 umr2.cc
% ./a.out
WARNING: MemorySanitizer: use-of-uninitialized-value
#0 0x7f7893912f0b in main umr2.cc:7
#1 0x7f789249b76c in __libc_start_main libc-start.c:226
Uninitialized value was stored to memory at
#0 0x7f78938b5c25 in __msan_chain_origin msan.cc:484
#1 0x7f7893912ecd in main umr2.cc:6
Uninitialized value was created by a heap allocation
#0 0x7f7893901cbd in operator new[](unsigned long) msan_new_delete.cc:44
#1 0x7f7893912e06 in main umr2.cc:4
默认情况下,MemorySanitizer 会收集未初始化值涉及到的内存分配以及所有的中间存储。Origin tracking 已被证明对于调试 MemorySanitizer 报告非常有用。它会降低程序执行效率(大概1.5x-2x on top of the usual MemorySanitizer slowdown),增加内存消耗。
开启Clang选项 -fsanitize-memory-track-origins=1 ,可以使得 MemorySanitizer 仅收集内存分配指针,而不收集中间存储(collects only allocation points but not intermediate stores)。这种模式相对轻量,运行更快一点。
对象被销毁后再使用的检测
可以在 MemorySanitizer 中使用正在实验阶段的 use-after-destruction (对象被销毁后再使用)检测工具。在析构函数被调用后,对象内存即被认为已经不可读了,如果继续使用该内存即会导致运行时的错误。
该特性依然处于实验阶段,如果需要在运行时期开启,需要如下步骤:
- 在编译阶段传递额外的Clang选项 -fsanitize-memory-use-after-dtor。
- 在程序运行之前,设置环境变量 MSAN_OPTIONS=poison_in_dtor=1。
处理外部代码
MemorySanitizer 要求程序的所有代码都被插桩。也会包含程序依赖的所有库,即便是 libc 库。如果不这样做,可能导致错误报告。因此,需要将所有对内存有写操作的内联的汇编代码,替换成纯 C/C++ 的代码。
完全的 MemorySanitizer 插桩比较难以完成。为了便于操作,MemorySanitizer 的运行时库包含了70+的拦截器,用于针对最普遍的 libc 函数。这样就可以在链接了未插桩 libc* 的情况下,运行 MemorySanitizer 插桩后的程序。例如,开发者可以使用 MemorySanitizer 插桩后的 Clang 编译器,即便在链接了他们自行开发的插桩后的 libc++ 库(用于替代 libstdc++ 库)。
支持的平台
MemorySanitizer 支持如下操作系统:
- Linux
- NetBSD
- FreeBSD
限制
- MemorySanitizer 会多消耗2x的实际内存,使用 origin tracking 的时候可能多达3x。
- 在64位平台上,MemorySanitizer映射到(未来不一定)16+ Terabytes的虚拟地址空间. 这意味着一些工具如 ulimit 可能就不像通常那样生效了。
- 不支持静态编译。
- 旧版本的 MSan(LLVM 3.7以前版本)不支持非地址独立(non-position-independent)的可执行文件,并且在禁用 ASLR 的Linux内核版本中可能失败。更多信息请参考旧版本的文档。
- 在 FreeBSD 13中,MemorySanitizer 可能与地址独立(position-independent)的二进制不兼容,但是依然会对测试场景进行检测,在运行结束后会抛出一个警告。
当前状态
MemorySanitizer 可以用于非常庞大的、由源码重新编译的程序(如 Clang/LLVM 自身),以及所有依赖的库。