做爲 C/ C++ 工程師,在開發過程當中會遇到各種問題,最多見即是內存使用問題,好比,越界,泄漏。過去經常使用的工具是 Valgrind,但使用 Valgrind 最大問題是它會極大地下降程序運行的速度,初步估計會下降 10 倍運行速度。而 Google 開發的 AddressSanitizer 這個工具很好地解決了 Valgrind 帶來性能損失問題,它很是快,只拖慢程序 2 倍速度。html
AddressSanitizer 是一個基於編譯器的測試工具,可在運行時檢測 C/C++ 代碼中的多種內存錯誤。嚴格上來講,AddressSanitizer 是一個編譯器插件,它分爲兩個模塊,一個是編譯器的 instrumentation 模塊,一個是用來替換 malloc/free 的動態庫。ios
Instrumentation 主要是針對在 llvm 編譯器級別對訪問內存的操做(store,load,alloc等),將它們進行處理。動態庫主要提供一些運行時的複雜的功能(好比 poison/unpoison shadow memory)以及將 malloc/free 等系統調用函數 hook 住。git
根據 AddressSanitizer Wiki 能夠檢測下面這些內存錯誤github
這裏我只簡單地介紹下基本的使用,詳細的使用文檔能夠看官方的編譯器使用文檔,好比 Clang 的文檔:https://clang.llvm.org/docs/AddressSanitizer.html算法
下面這段代碼是一個很簡單的 Use after free 的例子:shell
//use_after_free.cpp #include <iostream> int main(int argc, char **argv) { int *array = new int[100]; delete [] array; std::cout << array[0] << std::endl; return 1; }
編譯代碼,而且運行,這裏能夠看到只須要在編譯的時候帶上 -fsanitize=address
選項就能夠了。數據庫
clang++ -O -g -fsanitize=address ./use_after_free.cpp ./a.out
最終咱們會看到以下的輸出:bash
==10960==ERROR: AddressSanitizer: heap-use-after-free on address 0x614000000040 at pc 0x00010d471df0 bp 0x7ffee278e6b0 sp 0x7ffee278e6a8 READ of size 4 at 0x614000000040 thread T0 #0 0x10d471def in main use_after_free.cpp:6 #1 0x7fff732c17fc in start (libdyld.dylib:x86_64+0x1a7fc) 0x614000000040 is located 0 bytes inside of 400-byte region [0x614000000040,0x6140000001d0) freed by thread T0 here: #0 0x10d4ccced in wrap__ZdaPv (libclang_rt.asan_osx_dynamic.dylib:x86_64h+0x51ced) #1 0x10d471ca1 in main use_after_free.cpp:5 #2 0x7fff732c17fc in start (libdyld.dylib:x86_64+0x1a7fc) previously allocated by thread T0 here: #0 0x10d4cc8dd in wrap__Znam (libclang_rt.asan_osx_dynamic.dylib:x86_64h+0x518dd) #1 0x10d471c96 in main use_after_free.cpp:4 #2 0x7fff732c17fc in start (libdyld.dylib:x86_64+0x1a7fc) SUMMARY: AddressSanitizer: heap-use-after-free use_after_free.cpp:6 in main
能夠看到一目瞭然,很是清楚的告訴了咱們在哪一行內存被釋放,而又在哪一行內存再次被使用。app
還有一個是內存泄漏,好比下面的代碼,顯然 p 所指的內存沒有被釋放。分佈式
void *p; int main() { p = malloc(7); p = 0; // The memory is leaked here. return 0; }
編譯而後運行
clang -fsanitize=address -g ./leak.c ./a.out
能夠看到以下的結果
================================================================= ==17756==ERROR: LeakSanitizer: detected memory leaks Direct leak of 7 byte(s) in 1 object(s) allocated from: #0 0x4ffc80 in malloc (/home/simon.liu/workspace/a.out+0x4ffc80) #1 0x534ab8 in main /home/simon.liu/workspace/./leak.c:4:8 #2 0x7f127c42af42 in __libc_start_main (/usr/lib64/libc.so.6+0x23f42) SUMMARY: AddressSanitizer: 7 byte(s) leaked in 1 allocation(s).
不過這裏要注意內存泄漏的檢測只會在程序最後退出以前進行檢測,也就是說若是你在運行時若是不斷地分配內存,而後在退出的時候對內存進行釋放,AddressSanitizer 將不會檢測到內存泄漏,這種時候可能你就須要另外的工具了 JeMalloc / TCMalloc。
這裏簡單介紹一下 AddressSanitizer 的實現,更詳細的算法實現能夠看《AddressSanitizer: a fast address sanity checker》:https://www.usenix.org/system/files/conference/atc12/atc12-final39.pdf
AddressSanitizer 會替換你的全部 malloc 以及 free,而後已經被分配(malloc)的內存區域的先後會被標記爲 poisoned
(主要是爲了處理 overflow 這種狀況),而釋放(free)的內存會被標記爲 poisoned(主要是爲了處理 Use after free)。你的代碼中的每一次的內存存取都會被編譯器作相似下面的翻譯.
before:
*address = ...; // or: ... = *address;
after:
shadow_address = MemToShadow(address); if (ShadowIsPoisoned(shadow_address)) { ReportError(address, kAccessSize, kIsWrite); } *address = ...; // or: ... = *address;
這裏能夠看到首先會對內存地址有一個翻譯(MemToShadow)的過程,而後再來判斷當所訪問的內存區域是否爲 poisoned,若是是則直接報錯並退出。
這裏之因此會有這個翻譯是由於 AddressSanitizer 將虛擬內存分爲了兩部分:
下圖是 AddressSanitizer 與其餘的一些內存檢測工具的對比:
http://www.javashuo.com/tag/addresssanitizer | Valgrind/Memcheck | Dr. Memory | Mudflap | Guard Page | gperftools | |
---|---|---|---|---|---|---|
technology | CTI | DBI | DBI | CTI | Library | Library |
ARCH | x86, ARM, PPC | x86, ARM, PPC, MIPS, S390X, TILEGX | x86 | all(?) | all(?) | all(?) |
OS | Linux, OS X, Windows, FreeBSD, Android, iOS Simulator | Linux, OS X, Solaris, Android | Windows, Linux | Linux, Mac(?) | All (1) | Linux, Windows |
Slowdown | 2x | 20x | 10x | 2x-40x | ? | ? |
Detects: | ||||||
Heap OOB | yes | yes | yes | yes | some | some |
Stack OOB | yes | no | no | some | no | no |
Global OOB | yes | no | no | ? | no | no |
UAF | yes | yes | yes | yes | yes | yes |
UAR | yes (see AddressSanitizerUseAfterReturn) | no | no | no | no | no |
UMR | no (see MemorySanitizer) | yes | yes | ? | no | no |
Leaks | yes (see LeakSanitizer) | yes | yes | ? | no | yes |
參數說明:
能夠看到相比於 Valgrind,AddressSanitizer 只會拖慢程序 2 倍運行速度。當前 AddressSanitizer 支持 GCC 以及 Clang,其中 GCC 是從 4.8 開始支持,而 Clang 的話是從 3.1 開始支持。
export ASAN_OPTIONS='abort_on_error=1'/
咱們在 Nebula Graph 中也使用了 AddressSanitizer,它幫助咱們發現了很是多的問題。而在 Nebula Graph 中開啓 AddressSanitizer 很簡單,只須要在 Cmake 的時候帶上打開 ENABLE_ASAN 這個 Option 就能夠,好比:
Cmake -DENABLE_ASAN=On
這裏建議全部的開發者在開發完畢功能運行單元測試的時候都打開 AddressSanitizer 來運行單元測試,這樣能夠發現不少不容易發現的內存問題,節省不少調試的時間。