和其他可以自动管理内存的语言相比,C/C++最令人头疼的问题无过于内存管理。Clang编辑器中携带了很多工具,可以帮助你检查内存访问,其中一款就是AddressSanitizer

例子

我们先来看一个小例子main.cc

#include <vector>
using namespace std;

int main()
{
  vector<char> vc;
  vc.reserve(10);
  vc[1] = 'a';
  return 0;
}

上面例子的问题,是vc的大小其实是0,但是vc[1]却访问了索引为1的元素。虽然之前通过vc.reserve(10)申请了内存,但是越过vc的大小去访问边界外的元素可能是一个编程错误。clang的AddressSanitizer可以帮你检查到这个问题。

编译

AddressSanitizer的功能通过编译器选项开启:

  • -fsanitize=address, 这个选项开启AddressSanitizer的检查功能;注意,不仅在编译的时候需要开启,在链接的时候也要开启
  • -fno-omit-frame-pointer和-fno-optimize-sibling-calls, 这两个选项是为了更方便定位错误

下面写一个cmake文件来编译上面的main.cc:

PROJECT(asan)

set(CMAKE_BUILD_TYPE Debug)
set (CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fno-omit-frame-pointer -fsanitize=address -fno-optimize-sibling-calls")
set (CMAKE_LINKER_FLAGS_DEBUG "${CMAKE_LINKER_FLAGS_DEBUG} -fno-omit-frame-pointer -fsanitize=address -fno-optimize-sibling-calls")

add_executable(asan main.cc)

上面的内容保存成CMakeLists.txt文件,和main.cc在同一个目录。然后执行以下命令:

# 确保当前目录下有CMakeLists.txt和main.cc
mkdir build # 创建编译目录
cd build
cmake .. # 生成Makefile
make # 开始编译

有一点需要注意的是,如果clang不是系统默认的编译器,那么需要指定cmake使用clang编译器,具体的做法是调用cmake之前,创建下面的环境变量:

# 假设clang编译器在/opt/bin目录
export CC=/opt/bin/clang
export CXX=/opt/bin/clang++

上面步骤全部成功的话,就会生成一个可执行程序asan

错误信息

执行上一步生成的asan,可以看到下面的错误信息:

==25840==ERROR: AddressSanitizer: container-overflow on address 0x6020000000d1 at pc 0x000103403a3c bp 0x7ffeec7fe3f0 sp 0x7ffeec7fe3e8
WRITE of size 1 at 0x6020000000d1 thread T0
    #0 0x103403a3b in main main.cc:8
    #1 0x7fff6163aed8 in start (libdyld.dylib:x86_64+0x16ed8)

0x6020000000d1 is located 1 bytes inside of 10-byte region [0x6020000000d0,0x6020000000da)
allocated by thread T0 here:
    #0 0x10346ffa2 in wrap__Znwm (libclang_rt.asan_osx_dynamic.dylib:x86_64h+0x62fa2)
    #1 0x103406095 in std::__1::__split_buffer<char, std::__1::allocator<char>&>::__split_buffer(unsigned long, unsigned long, std::__1::allocator<char>&) new:228
    #2 0x1034046dc in std::__1::__split_buffer<char, std::__1::allocator<char>&>::__split_buffer(unsigned long, unsigned long, std::__1::allocator<char>&) __split_buffer:310
    #3 0x103403f13 in std::__1::vector<char, std::__1::allocator<char> >::reserve(unsigned long) vector:1541
    #4 0x103403997 in main main.cc:7
    #5 0x7fff6163aed8 in start (libdyld.dylib:x86_64+0x16ed8)

HINT: if you don't care about these errors you may set ASAN_OPTIONS=detect_container_overflow=0.
If you suspect a false positive see also: https://github.com/google/sanitizers/wiki/AddressSanitizerContainerOverflow.
SUMMARY: AddressSanitizer: container-overflow main.cc:8 in main
Shadow bytes around the buggy address:
  0x1c03ffffffc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1c03ffffffd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1c03ffffffe0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1c03fffffff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1c0400000000: fa fa fd fd fa fa fd fd fa fa 00 00 fa fa 00 04
=>0x1c0400000010: fa fa 00 00 fa fa 00 06 fa fa[fc]fc fa fa fa fa
  0x1c0400000020: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x1c0400000030: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x1c0400000040: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x1c0400000050: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x1c0400000060: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07 
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
==25840==ABORTING
[2]    25840 abort      ./asan

上面的错误信息很冗长,但是明确告诉你:

  • main.cc:8 执行了vc[1] = 'a';,这个代码有问题
  • 除了上一条信息之外,还告诉你vc[1]的内存是在 main.cc:7 分配的

其他

Switching between GCC and Clang/LLVM using CMake