概述

一、介绍

AddressSanitizer(又名 ASAN)可以检测 C/C++ 的内存错误。可以发现如下的内存错误:

  • Use after free (dangling pointer dereference),使用 free 后的指针。默认开启
  • Heap buffer overflow,堆内存越界。默认开启
  • Stack buffer overflow,栈内存越界。默认开启
  • Global buffer overflow,全局/静态内存块使用越界,默认开启
  • Use after return,栈中的内存访问,默认不开启
  • Use after scope,作用域外的内存访问,编译时加选项开启
  • Initialization order bugs,内存初始化顺序问题,默认不开启
  • Memory leaks,内存泄漏,

Global buffer overflow

1
2
3
4
5
// RUN: clang -O -g -fsanitize=address %t && ./a.out
int global_array[100] = {-1};
int main(int argc, char **argv) {
return global_array[argc + 100]; // BOOM
}

Use after return

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
int *ptr;
__attribute__((noinline))
void FunctionThatEscapesLocalObject() {
int local[100];
ptr = &local[0];
}

int main(int argc, char **argv) {
FunctionThatEscapesLocalObject();
return ptr[argc];
}

编译:clang -O -g -fsanitize=address %t && ./a.out

默认情况下,不开启发现这种 Bug。想要开启检测,必须设置环境变量:ASAN_OPTIONS=detect_stack_use_after_return=1

Use after scope

例子:

1
2
3
4
5
6
7
8
9
10
11
12
volatile int *p = 0;

int main() {
{
int x = 0;
p = &x;
}
*p = 5;
return 0;
}

编译:clang -O -g -fsanitize=address -fsanitize-address-use-after-scope use-after-scope.cpp -o ./a.out

如果想在运行时禁止这种检测,可以设置环境变量:ASAN_OPTIONS=detect_stack_use_after_scope=0

Initialization order bugs

不同源文件中的全局对象的构造函数的初始化顺序是未定义的。如下举一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// a.c
extern int extern_global;
int __attribute__((noinline)) read_extern_global() {
return extern_global;
}
int x = read_extern_global() + 1;
int main() {
printf("%d\n", x);
return 0;
}

// b.c
int foo() { return 42; }
int extern_global = foo();

如上代码,这里 x 的值取决于 extern_global 的值。同时 extern_global 的值取决于 a.c 还是 b.c 先编译。这种问题很难发现,编译器或者链接器的一些策略变化可能会导致代码行为的改变。

ASAN 在编译时插入检查,检测初始化的顺序问题。默认情况下,他们是关闭的,需要使用环境变量开启。

并且此检测有两种模式,一种是宽松的检测模式,另一种是严格的检测模式。

  • 宽松的初始化顺序检测模式
1
2
clang++ -fsanitize=address -g a.c b.c -o a.out
ASAN_OPTIONS=check_initialization_order=true ./a.out

这种模式,如果初始化顺利改变,也就是编译或链接顺序改变,可能会检测不到问题

1
2
// 这样编译可能检测不到问题
clang++ -fsanitize=address -g b.c a.c -o a.out
  • 严格的初始化顺序检测模式
1
2
clang++ -fsanitize=address -g b.c a.c
ASAN_OPTIONS=check_initialization_order=true:strict_init_order=true ./a.out

在这种模式下,如果一个源文件的全局变量的初始化依赖另一个源文件的任何动态初始化的全局变量,都会报错。同时可能会产出误报:https://github.com/google/sanitizers/wiki/AddressSanitizerInitializationOrderFiasco

Memory leaks

LeakSanitizer 是一个集成到 AddressSanitizer 中的内存泄漏检测器。该工具在 x86_64 Linux 和 OS X 上受支持。

LeakSanitizer 在 x86_64 Linux 的 ASan 版本中默认启用,在 x86_64 OS X 上启用ASAN_OPTIONS=detect_leaks=1。LSan 处于休眠状态,直到该过程的最后阶段,此时有一个额外的泄漏检测阶段。在性能关键场景中,LSan 也可以在没有 ASan 检测的情况下使用。

如果不想检测内存泄漏,可以使用环境变量:ASAN_OPTIONS=detect_leaks=0。如果只想要检测内存泄漏,编译时使用:-fsanitize=leak 而不是 -fsanitize=address

设计文档:https://github.com/google/sanitizers/wiki/AddressSanitizerLeakSanitizerDesignDocument

对于 tcmalloc 的泄漏检测:https://github.com/google/sanitizers/wiki/AddressSanitizerLeakSanitizerVsHeapChecker

其他的一些行为:https://github.com/google/sanitizers/wiki/AddressSanitizerLeakSanitizer