和其他可以自动管理内存的语言相比,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 分配的