Address-Sanitizer(ASAN): 一個 C/C++ 記憶體偵錯的工具

LLVM 有一系列以 Sanitizer 結尾的偵錯工具(ASAN/TSAN/MSAN/DFSAN/LSAN)[1],每個工具各司其職,在此我們介紹其中的 ASAN[2]

Memory Corruption 一直是個非常難除錯非常讓人苦惱的問題, ASAN 正是一個幫助大家處理這樣問題的工具, ASAN 內建於 LLVM 3.1 和 gcc 4.8[3],其可偵測出以下的問題:

  • Use after free (dangling pointer dereference)
  • Heap buffer overflow
  • Stack buffer overflow
  • Global buffer overflow
  • Use after return

對 ASAN 演算法有興趣的夥伴可以參考官網頁面說明

使用方式 (Using ASAN)

1.    編譯時要加上使用 ASAN 的 flag (-fsanitize=address)
2.    執行檔中就直接插入了記憶體偵錯的程式碼
3.    直接跑執行檔,如果有偵測到錯誤會在輸出訊息中看到結果

以下是一個使用 ASAN 的範例

(compile)
% cat tests/use-after-free.c
#include 
int main() {
char *x = (char*)malloc(10 * sizeof(char*));
free(x);
return x[5];
}

% ../clang_build_Linux/Release+Asserts/bin/clang -fsanitize=address -O1 -fno-omit-frame-pointer -g tests/use-after-free.c

[其它的 flag ]
-O1 : 是為了得到較佳的效能
-fno-omit-frame-pointer: 是為了在錯誤訊息中能得到正確的 stack trace

(Execute and Output)
% ./a.out
==9901==ERROR: AddressSanitizer: heap-use-after-free on address 0x60700000dfb5 at 
pc 0x45917b bp 0x7fff4490c700 sp 0x7fff4490c6f8
READ of size 1 at 0x60700000dfb5 thread T0
    #0 0x45917a in main use-after-free.c:5
    #1 0x7fce9f25e76c in __libc_start_main /build/buildd/eglibc-2.15/csu/libc-start.c:226
    #2 0x459074 in _start (a.out+0x459074)
0x60700000dfb5 is located 5 bytes inside of 80-byte region [0x60700000dfb0,0x60700000e000)
freed by thread T0 here:
    #0 0x4441ee in __interceptor_free projects/compiler-rt/lib/asan/asan_malloc_linux.cc:64
    #1 0x45914a in main use-after-free.c:4
    #2 0x7fce9f25e76c in __libc_start_main /build/buildd/eglibc-2.15/csu/libc-start.c:226
previously allocated by thread T0 here:
    #0 0x44436e in __interceptor_malloc projects/compiler-rt/lib/asan/asan_malloc_linux.cc:74
    #1 0x45913f in main use-after-free.c:3
    #2 0x7fce9f25e76c in __libc_start_main /build/buildd/eglibc-2.15/csu/libc-start.c:226
SUMMARY: AddressSanitizer: heap-use-after-free use-after-free.c:5 main

從結果我們可以看出 ASAN 有偵測到 heap-use-after-free 的錯誤,接著提供了 pc/bp/sp 這幾個有用暫存器的值,再來是發生錯誤的 call stack 以及何處去 allocate 何處已經被 free 的 call stack,最後有一個這次偵測錯誤的總結。

ASAN on Android[4]

Android 在 Jerry Bean 支援了 Address-sanitizer。

開啟方式非常簡單,只要在 Android.mk 裡加上

“LOCAL_ADDRESS_SANITIZE := true”

執行包含新編譯的執行檔前,需要把 asanwrapper 以及 libasan_preload.so 分別放到 system/bin/ 以及 system/lib/,最後是要將執行產生的錯誤訊息利用一個 python 進行解析。

(用 b2g-info 當作範例)
1. rebuild b2g-info as b2g-info__asan
  (add "LOCAL_ADDRESS_SANITIZER := true" in Android.mk)
2. Push “asanwrapper” and “b2g-info__asan” into “system/bin/”
   Push “libasan_preload.so” into “system/lib/”
3. adb shell "asanwrapper /system/bin/b2g-info" > err.txt
4. modify err.txt (remove /system/bin and /system/lib)
5. copy "b2g-info__asan" and "libc.so" from "out/target/product/mako/symbols/"
6. cat err.txt 2>&1 | ~/workspace/Nexus-4/external/compiler-rt/lib/asan/scripts/asan_symbolize.py --demangle
   #0 0xb6f809c7 in main /home/xxx/workspace/Nexus-4/gonk-misc/b2g-info/b2g-info.cpp:342:0
   #1 0xb5954b4f in __libc_init /home/xxx/workspace/Nexus-4/bionic/libc/bionic/libc_init_dynamic.cpp:112:

ASAN on firefox [5]

下載(Download Executable file with ASAN)
我們可以直接從下面連結取得有 ASAN 功能的 firefox:
1. mozilla-central optimized builds (recommended for testing)
2. mozilla-central debug builds (recommended for debugging if the optimized builds don’t do the job)

自行編譯(Build by myself)
必須要先建立包含下面內容的 .mozconfig,再進行編譯,我們就可以編譯出帶有 ASAN 的執行檔。

mk_add_options MOZ_OBJDIR=@TOPSRCDIR@/objdir-ff-asan
mk_add_options MOZ_MAKE_FLAGS=-j12

# Enable ASan specific code and build workarounds
ac_add_options --enable-address-sanitizer

# Ensure you set this to your LLVM_HOME path
export LLVM_HOME="/path/to/your/llvm"

# Set CC/CXX based on LLVM_HOME
export CC="$LLVM_HOME/build/bin/clang"
export CXX="$LLVM_HOME/build/bin/clang++"

# Add ASan to our compiler flags
export CFLAGS="-fsanitize=address -Dxmalloc=myxmalloc"
export CXXFLAGS="-fsanitize=address -Dxmalloc=myxmalloc"

# Additionally, we need the ASan flag during linking. Normally, our C/CXXFLAGS would
# be used during linking as well but there is at least one place in our build where
# our CFLAGS are not added during linking.
# Note: The use of this flag causes Clang to automatically link the ASan runtime :)
export LDFLAGS="-fsanitize=address"

# Avoid using ASan flags when building host tools like nsinstall
export HOST_CFLAGS=" "
export HOST_CXXFLAGS=" "
export HOST_LDFLAGS=" "

# These three are required by ASan
ac_add_options --disable-jemalloc
ac_add_options --disable-crashreporter
ac_add_options --disable-elf-hack

# Keep symbols to symbolize ASan traces later
export MOZ_DEBUG_SYMBOLS=1
ac_add_options --enable-debug-symbols
ac_add_options --disable-install-strip

# Settings for a debug+opt build
ac_add_options --enable-optimize
ac_add_options --enable-debug

# Settings for an opt build
#ac_add_options --enable-optimize
#ac_add_options --disable-debug

結語

以上就是對於 ASAN 簡單的介紹, ASAN 跟所有偵錯的工具一樣都要付出一些代價(更大的記憶體使用量、較差的效能),然而確實可以利用它找出一些用人眼或人腦會忽略的程式錯誤甚至是一些潛在的問題,因此需要善用這樣的工具讓我們的生活更好臭蟲更少(笑)。

參考資料

[1] http://clang.llvm.org/docs/
[2] https://code.google.com/p/address-sanitizer/wiki/AddressSanitizer
[3] http://gcc.gnu.org/gcc-4.8/changes.html
[4] https://code.google.com/p/address-sanitizer/wiki/Android
[5] https://developer.mozilla.org/en-US/docs/Building_Firefox_with_Address_Sanitizer

今日排行榜