sanitizer工具集

sanitizer工具集的介绍

Sanitizers是谷歌发起的开源工具集,包括了Address Sanitizer, undefined behavior Sanitizer, Thread Sanitizer, Leak Sanitizer。GCC从4.8版本开始支持Address sanitizer和Thread Sanitizer,4.9版本开始支持Leak Sanitizer和undefined behavior Sanitizer。

Address Sanitizer(ASAN):

  也即地址消毒技术,通过编译插桩(CTI),能够发现此堆/栈/全局变量读写溢出,内存泄露等问题,并将信息直接打印到日志中。Address Sanitizer(ASan)是一个快速的内存错误检测工具。它非常快,只拖慢程序两倍左右(比起Valgrind快多了)。它包括一个编译器instrumentation模块和一个提供malloc()/free()替代项的运行时库。

Thread Sanitizer(TSan):

  是一个检查线程Data Race的C/C++工具。

Leak Sanitizer(LSan):

  检测内存的LeakSanitizer是集成在Address Sanitizer中的一个相对独立的工具,它工作在检查过程的最后阶段。

Undefiend Behavior Sanitizer(UBSan):

  检测未定义行为(使用空指针、有符号整数溢出等)。

== 环境配置 ==
=== QMake环境 ===
在pro文件中添加:

QMAKE_CXXFLAGS+="-fsanitize=undefined,address,leak -fno-omit-frame-pointer"

QMAKE_CFLAGS+="-fsanitize=undefined,address,leak -fno-omit-frame-pointer"

QMAKE_LFLAGS+="-fsanitize=undefined,address,leak -fno-omit-frame-pointer"

用-fsanitize=address选项,编译和链接你的程序。

用undefined,可以使用undefined behavior sanitizer检测未定义行为。

用leak,可以使用leak sanitizer检测内存泄露(很多平台默认关闭,X86默认开启的)。

用-fno-omit-frame-pointer(与相对)编译,以得到更容易理解stack trace。

注:-fomit-frame-pointer是打开优化选项(-O1打开),与-fno-omit-frame-pointer相反,即在函数调用时不保存栈帧指针SFP,代价是不能通过backtrace进行调试根据堆栈信息了。

TSan环境配置:

主要用于检测多线程资源竞争的问题,但是该选项不能与-fsanitize=address、-fsanitize=leak组合。

在pro文件中添加:

QMAKE_CXXFLAGS+="-fsanitize=thread"

QMAKE_CFLAGS+="-fsanitize=thread"

QMAKE_LFLAGS+="-fsanitize=thread"

=== CMake环境 ===
在CMakeLists添加:

set(CMAKE_CXX_FLAGS “-fsanitize=undefined,address,leak -fno-omit-frame-pointer”)

set(CMAKE_C_FLAGS “-fsanitize=undefined,address,leak -fno-omit-frame-pointer”)

set(CMAKE_L_FLAGS “-fsanitize=undefined,address,leak -fno-omit-frame-pointer”)

TSan环境配置:

set(CMAKE_CXX_FLAGS “-fsanitize=thread”)

set(CMAKE_C_FLAGS “-fsanitize=thread”)

set(CMAKE_L_FLAGS “-fsanitize=thread”)

== 工作原理和使用方法 ==
=== 原理 ===
  Address Sanitizer替换了malloc和free的实现。当调用malloc函数时,它将分配指定大小的内存A,并将内存A周围的区域标记为”off-limits“。当free方法被调用时,内存A也被标记为”off-limits“,同时内存A被添加到隔离队列,这个操作将导致内存A无法再被重新malloc使用。

  当访问到被标记为”off-limits“的内存时,Address Sanitizer就会报告异常。

=== 错误类型 ===
Use after free  释放后使用

Heap buffer overflow 堆缓冲区溢出

Stack buffer overflow 栈缓冲区溢出

Global buffer overflow 全局缓冲区溢出

Use after return 返回后使用

Use after scope 作用域后使用

Initialization order bugs 初始化顺序错误

Memory leaks 内存泄露

Using misaligned or null pointer  使用未对齐的指针

Signed integer overflow  有符号整数溢出

Conversion to, from, or between floating-point types which would overflow the destination  和浮点数相关转换溢出

data race  数据竞争

=== 错误示例 ===
==== Address Sanitizer ====
int main(){
int* array = new int[100];
delete [] array;
return array[1];
}

16829ERROR: AddressSanitizer: heap-use-after-free on address 0x614000000044 at pc 0x00000065b3ea bp 0x7fffffffe3a0 sp 0x7fffffffe398

· 第一部分(ERROR)指出错误类型是heap-use-after-free;

READ of size 4 at 0x614000000044 thread T0

#0 0x65b3e9 in main …/…/deepin-image-viewer/viewer/main.cpp:33

#1 0x7ffff4b2409a in __libc_start_main …/csu/libc-start.c:308

#2 0x440319 in _start (/data/home/shuwenzhi/workspace/sp2/build-deepin-image-viewer-unknown-Debug/viewer/deepin-image-viewer+0x440319)

· 第二部分(READ), 指出线程名thread T0,操作为READ,发生的位置是main.cpp:33。

libc_start_main()函数应执行执行环境的任何必要初始化,使用适当的参数调用main函数,并处理main()的返回。

0x614000000044 is located 4 bytes inside of 400-byte region [0x614000000040,0x6140000001d0)

freed by thread T0 here:

#0 0x7ffff72f3c40 in operator delete (/usr/lib/x86_64-linux-gnu/libasan.so.5+0xebc40)

#1 0x65b3ab in main …/…/deepin-image-viewer/viewer/main.cpp:32

#2 0x7ffff4b2409a in __libc_start_main …/csu/libc-start.c:308

· 第二部分(freed), 该heap块之前已经在main.cpp:32被释放了;

previously allocated by thread T0 here:

#0 0x7ffff72f2ef0 in operator new[](unsigned long) (/usr/lib/x86_64-linux-gnu/libasan.so.5+0xeaef0)

#1 0x65b38b in main …/…/deepin-image-viewer/viewer/main.cpp:31

#2 0x7ffff4b2409a in __libc_start_main …/csu/libc-start.c:308

· 第二部分(allocated), 该heap块是在main.cpp:31分配。

SUMMARY: AddressSanitizer: heap-use-after-free …/…/deepin-image-viewer/viewer/main.cpp:33 in main

· 第三部分 (SUMMARY) 前面输出的概要说明。

==== Undefined Behavior Sanitizer ====
void testsignoverflow()
{
int k = 0x7fffffff;
k += 2;
}

…/worktest/testaddress/main.cpp:11:7: runtime error: signed integer overflow: 2147483647 + 2 cannot be represented in type ‘int’

· 超过int范围,不能用int表达。

==== Leak Sanitizer ====
void* p;
int main(){
p = malloc(4);
p = 0;
return 0;
}

7089ERROR: LeakSanitizer: detected memory leaks

错误类型:detected memory leaks。

Direct leak of 4 byte(s) in 1 object(s) allocated from:

#0 0x7f13b7aab330 in __interceptor_malloc (/usr/lib/x86_64-linux-gnu/libasan.so.5+0xe9330)

#1 0x65b36b in main …/…/deepin-image-viewer/viewer/main.cpp:33

#2 0x7f13b52de09a in __libc_start_main …/csu/libc-start.c:308

内存在main.cpp:33分配。

SUMMARY: AddressSanitizer: 4 byte(s) leaked in 1 allocation(s)。

==== Thread Sanitizer ====
int Global;
void *Thread1(void x) {
Global++;
return NULL;
}
void Thread2(void x) {
Global–;
return NULL;
}
int main(){
pthread_t t[2];
pthread_create(&t[0], NULL, Thread1, NULL);
pthread_create(&t[1], NULL, Thread2, NULL);
pthread_join(t[0], NULL);
pthread_join(t[1], NULL);
}

WARNING: ThreadSanitizer: data race (pid=26333)

· 错误类型:data race
Read of size 4 at 0x000000408174 by thread T2:
#0 Thread2(void
) …/worktest/testaddress/main.cpp:47 (testaddress+0x403520)
#1 (libtsan.so.0+0x29b3d)

· 在线程T2读取4个字节,发生在main.cpp:47。
Previous write of size 4 at 0x000000408174 by thread T1:
#0 Thread1(void
) …/worktest/testaddress/main.cpp:42 (testaddress+0x4034db)
#1 (libtsan.so.0+0x29b3d)

· 在线程T1读取4个字节,发生在main.cpp:42。
Location is global ‘Global’ of size 4 at 0x000000408174 (testaddress+0x000000408174)
Thread T2 (tid=26336, running) created by main thread at:
#0 pthread_create (libtsan.so.0+0x2be1b)
#1 main …/worktest/testaddress/main.cpp:74 (testaddress+0x403672)
Thread T1 (tid=26335, finished) created by main thread at:
#0 pthread_create (libtsan.so.0+0x2be1b)
#1 main …/worktest/testaddress/main.cpp:73 (testaddress+0x403651)

SUMMARY: ThreadSanitizer: data race …/worktest/testaddress/main.cpp:47 in Thread2(void
)

· 线程T1、T2在主线程main.cpp的73和74行创建。

=== 使用建议 ===
ASAN、LSan、UBSan:
对可能出现内存泄露、访问越界、堆栈溢出,可以使用此三种工具同时检查,建议在每次提交代码之前,开启此三项检查,可以排除大部分常见错误,项目不大的话也可以配置到debug里。
Thread Sanitizer(TSan):
由于此工具会和其他工具组合冲突,建议在新增线程或者线程中可能出现data trace的情况下使用。例如:出现多线程的线程安全问题,可以开启此工具检查。
错误输出:

在正常的项目开发中,会有存有大量的日志信息输出到应用程序输出里,这样会加大查找错误信息的难度,因此建议在将sanitizer错误信息输出到日志里。

#include <sanitizer/asan_interface.h>

__sanitizer_set_report_path(“asan.log”);

在指定的目录会生成一个asan.log.pid(进程号)的文件。

== 总结 ==
=== 环境兼容 ===
盘古V(wayland):

错误信息不在应用程序输出里,而在编译输出里,有一个问题,编译输出错误信息后将错误代码删除,重新编译仍有错误信息。
x86:

可以正常使用。

鲲鹏(arm):

可以正常使用,与x86使用相同。

龙芯(MIPS):

不支持MIPS,缺少对应环境。

=== 使用人员 ===
研发人员:

由于使用sanitizer工具集需要代码编译,因此建议研发人员使用。

测试人员:

使用建议使用valgrind,详细使用请参照valgrind工具使用。

上一篇:laravel日志查看器(Log Viewer)


下一篇:Chrome浏览器如何安装与使用PDFViewer扩展程序