这篇文章上次修改于 902 天前,可能其部分内容已经发生变化,如有疑问可询问作者。

1 ASAN 简介

AddressSanitizer(内存除毒器简称ASAN)早先是LLVM中的特性,后被加入GCC 4.8,在GCC 4.9后加入对ARM平台的支持。因此GCC 4.8以上版本使用ASAN时不需要安装第三方库,通过在编译时指定编译CFLAGS即可打开开关 ( 例如:-fsanitizer=address);可以诊断内存越界,非法访问,内存泄漏,double free等常见内存问题,原理与valgrind不同(valgrind利用位图模拟内存的分配策略,asan则利用额外分配的shadow memory以及redzone等插桩内存对内存进行检测),效率比valgrind高好几倍(降低2倍左右,valgrind则10倍左右),也可以检测出valgrind无法检测出的内存错误;

Address Sanitizer可以用来检测如下内存使用错误:

  • 内存释放后又被使用
  • 内存重复释放
  • 释放未申请的内存
  • 使用栈内存作为函数返回值
  • 使用了超出作用域的栈内存
  • 内存越界访问

2 ASAN 原理

代码中所有的内存访问操作都被编译器转换为如下形式:

Before:

*address = ...;  // or: ... = *address;

After:

if (IsPoisoned(address)) {
  ReportError(address, kAccessSize, kIsWrite);
}
*address = ...;  // or: ... = *address;

详见:

3 ASAN 用法示例

项目结构:

example
├── ascan   
│      ├── ascan.cc
│      ├── BUILD
│      └── example.cc
├── .bazelrc              # 编译参数,如 ascan 编译参数
├── WORKSPACE             # 项目依赖,内容可为空

example.cc:内存泄露示例,分配了内存但是没有释放

#include <iostream>

int main(int argc, char** argv) {
  int *p = new int(5);
  return 0;
}

BUILD:

load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library")

cc_binary(
    name = "example",
    srcs = ["example.cc"],
    deps = [],
)

.bazelrc: 编译参数

# bazel build --config asan
build:asan --strip=never
build:asan --copt="-fsanitize=address"  # 开启内存越界检测,包括内存泄露检测
build:asan --copt="-fno-omit-frame-pointer"  # 不省略函数帧数据,主要指栈顶,栈底寄存器,便于分辨帧的开始结尾
build:asan --copt="-g"
build:asan --linkopt="-fsanitize=address"

编译参数举例:

  • -fsanitize=address:开启内存越界检测,包括内存泄露检测
  • -fno-omit-frame-pointer:不省略函数帧数据,主要指栈顶,栈底寄存器,便于分辨帧的开始结尾
  • -fsanitize-recover=address:检测到错误之后继续运行程序,默认生效,如果没有生效的话,可进一步设置ASAN_OPTIONS=halt_on_error=0;该功能需要升级到 gcc6 才支持。

结果:

$ bazel build --config=asan ascan:example
$ export ASAN_OPTIONS=halt_on_error=0:use_sigaltstack=0:detect_leaks=1:malloc_context_size=15:log_path=/home/asan.log
$ ./bazel-bin/ascan/example

=================================================================
==10296==ERROR: LeakSanitizer: detected memory leaks

Direct leak of 4 byte(s) in 1 object(s) allocated from:
    #0 0x7fc2a70aa592 in operator new(unsigned long) (/usr/lib/x86_64-linux-gnu/libasan.so.2+0x99592)
    #1 0x400bae in main ascan/example.cc:4
    #2 0x7fc2a657a83f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2083f)

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

可以看到给出了内存泄漏的错误提示,并指出了泄漏的位置是 ascan/example.cc:4

其它示例见:https://docs.microsoft.com/en-us/cpp/sanitizers/asan-error-examples?view=msvc-160

运行参数举例:

  • halt_on_error=0:检测内存错误后继续运行,否则程序会终止
  • detect_leaks=1:进行内存泄露检测,默认进行
  • malloc_context_size=15:内存错误发生时,显示的调用栈层数为15
  • log_path=/home/asan.log:内存检查问题日志存放文件路径。