日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > linux >内容正文

linux

Linux 下的 AddressSanitizer

發布時間:2024/4/11 linux 47 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Linux 下的 AddressSanitizer 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

AddressSanitizer 是一個性能非常好的 C/C++ 內存錯誤探測工具。它由編譯器的插樁模塊(目前,LLVM 通過)和替換了 malloc 函數的運行時庫組成。這個工具可以探測如下這些類型的錯誤:

  • 對堆,棧和全局內存的訪問越界(堆緩沖區溢出,棧緩沖區溢出,和全局緩沖區溢出)
  • UAP(Use-after-free,懸掛指針的解引用,或者說野指針)
  • Use-after-return(無效的棧上內存,運行時標記 ASAN_OPTIONS=detect_stack_use_after_return=1)
  • Use-After-Scope(作用域外訪問,clang 標記 -fsanitize-address-use-after-scope )
  • 內存的重復釋放
  • 初始化順序的 bug
  • 內存泄漏

這個工具非常快。通常情況下,內存問題探測這類調試工具的引入,會導致原有應用程序運行性能的大幅下降,比如大名鼎鼎的 valgrind 據說會導致應用程序性能下降到正常情況的十幾分之一,但引入 AddressSanitizer 只會減慢運行速度的一半。

AddressSanitizer 的使用

自 LLVM 的版本 3.1 和 GCC 的版本 4.8 開始,AddressSanitizer 就是它們的一部分。如果需要的話,也可以從源碼編譯 AddressSanitizerHowToBuild。

查看自己的 LLVM 版本和 GCC 版本來確認是否內置了對 AddressSanitizer 的支持:

$ clang --version clang version 3.9.1-4ubuntu3~16.04.2 (tags/RELEASE_391/rc2) Target: x86_64-pc-linux-gnu Thread model: posix InstalledDir: /usr/bin$ gcc --version gcc (Ubuntu 5.4.0-6ubuntu1~16.04.10) 5.4.0 20160609 Copyright (C) 2015 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

我本地的工具雖然版本比較老,但對 AddressSanitizer 還是支持的。

看一下前面提到的 AddressSanitizer 的運行時庫:

$ locate asan /usr/lib/gcc/x86_64-linux-gnu/5/libasan.a /usr/lib/gcc/x86_64-linux-gnu/5/libasan.so /usr/lib/gcc/x86_64-linux-gnu/5/libasan_preinit.o /usr/lib/gcc/x86_64-linux-gnu/5/32/libasan.a /usr/lib/gcc/x86_64-linux-gnu/5/32/libasan.so /usr/lib/gcc/x86_64-linux-gnu/5/32/libasan_preinit.o /usr/lib/gcc/x86_64-linux-gnu/5/include/sanitizer/asan_interface.h /usr/lib/gcc/x86_64-linux-gnu/5/x32/libasan.a /usr/lib/gcc/x86_64-linux-gnu/5/x32/libasan.so /usr/lib/gcc/x86_64-linux-gnu/5/x32/libasan_preinit.o /usr/lib/gcc/x86_64-linux-gnu/7/libasan.a /usr/lib/gcc/x86_64-linux-gnu/7/libasan.so /usr/lib/gcc/x86_64-linux-gnu/7/libasan_preinit.o /usr/lib/gcc/x86_64-linux-gnu/7/include/sanitizer/asan_interface.h /usr/lib/gcc-cross/arm-linux-gnueabihf/5/libasan.a /usr/lib/gcc-cross/arm-linux-gnueabihf/5/libasan.so /usr/lib/gcc-cross/arm-linux-gnueabihf/5/libasan_preinit.o /usr/lib/gcc-cross/arm-linux-gnueabihf/5/include/sanitizer/asan_interface.h /usr/lib/gcc-cross/arm-linux-gnueabihf/5/sf/libasan.a /usr/lib/gcc-cross/arm-linux-gnueabihf/5/sf/libasan.so /usr/lib/gcc-cross/arm-linux-gnueabihf/5/sf/libasan_preinit.o /usr/lib/llvm-3.9/lib/clang/3.9.1/asan_blacklist.txt /usr/lib/llvm-3.9/lib/clang/3.9.1/include/sanitizer/asan_interface.h /usr/lib/llvm-3.9/lib/clang/3.9.1/lib/linux/libclang_rt.asan-i386.a /usr/lib/llvm-3.9/lib/clang/3.9.1/lib/linux/libclang_rt.asan-i386.so /usr/lib/llvm-3.9/lib/clang/3.9.1/lib/linux/libclang_rt.asan-i686.a /usr/lib/llvm-3.9/lib/clang/3.9.1/lib/linux/libclang_rt.asan-i686.so /usr/lib/llvm-3.9/lib/clang/3.9.1/lib/linux/libclang_rt.asan-preinit-i386.a /usr/lib/llvm-3.9/lib/clang/3.9.1/lib/linux/libclang_rt.asan-preinit-i686.a /usr/lib/llvm-3.9/lib/clang/3.9.1/lib/linux/libclang_rt.asan-preinit-x86_64.a /usr/lib/llvm-3.9/lib/clang/3.9.1/lib/linux/libclang_rt.asan-x86_64.a /usr/lib/llvm-3.9/lib/clang/3.9.1/lib/linux/libclang_rt.asan-x86_64.a.syms /usr/lib/llvm-3.9/lib/clang/3.9.1/lib/linux/libclang_rt.asan-x86_64.so /usr/lib/llvm-3.9/lib/clang/3.9.1/lib/linux/libclang_rt.asan_cxx-i386.a /usr/lib/llvm-3.9/lib/clang/3.9.1/lib/linux/libclang_rt.asan_cxx-i686.a /usr/lib/llvm-3.9/lib/clang/3.9.1/lib/linux/libclang_rt.asan_cxx-x86_64.a /usr/lib/llvm-3.9/lib/clang/3.9.1/lib/linux/libclang_rt.asan_cxx-x86_64.a.syms /usr/lib/llvm-5.0/lib/clang/5.0.2/asan_blacklist.txt /usr/lib/llvm-5.0/lib/clang/5.0.2/include/sanitizer/asan_interface.h /usr/lib/llvm-5.0/lib/clang/5.0.2/lib/linux/libclang_rt.asan-i386.a /usr/lib/llvm-5.0/lib/clang/5.0.2/lib/linux/libclang_rt.asan-i386.so /usr/lib/llvm-5.0/lib/clang/5.0.2/lib/linux/libclang_rt.asan-i686.a /usr/lib/llvm-5.0/lib/clang/5.0.2/lib/linux/libclang_rt.asan-i686.so /usr/lib/llvm-5.0/lib/clang/5.0.2/lib/linux/libclang_rt.asan-preinit-i386.a /usr/lib/llvm-5.0/lib/clang/5.0.2/lib/linux/libclang_rt.asan-preinit-i686.a /usr/lib/llvm-5.0/lib/clang/5.0.2/lib/linux/libclang_rt.asan-preinit-x86_64.a /usr/lib/llvm-5.0/lib/clang/5.0.2/lib/linux/libclang_rt.asan-x86_64.a /usr/lib/llvm-5.0/lib/clang/5.0.2/lib/linux/libclang_rt.asan-x86_64.a.syms /usr/lib/llvm-5.0/lib/clang/5.0.2/lib/linux/libclang_rt.asan-x86_64.so /usr/lib/llvm-5.0/lib/clang/5.0.2/lib/linux/libclang_rt.asan_cxx-i386.a /usr/lib/llvm-5.0/lib/clang/5.0.2/lib/linux/libclang_rt.asan_cxx-i686.a /usr/lib/llvm-5.0/lib/clang/5.0.2/lib/linux/libclang_rt.asan_cxx-x86_64.a /usr/lib/llvm-5.0/lib/clang/5.0.2/lib/linux/libclang_rt.asan_cxx-x86_64.a.syms /usr/lib/x86_64-linux-gnu/libasan.so.2 /usr/lib/x86_64-linux-gnu/libasan.so.2.0.0 /usr/lib/x86_64-linux-gnu/libasan.so.4 /usr/lib/x86_64-linux-gnu/libasan.so.4.0.0 /usr/lib32/libasan.so.2 /usr/lib32/libasan.so.2.0.0 /usr/libx32/libasan.so.2 /usr/libx32/libasan.so.2.0.0

為了使用 AddressSanitizer,需要在使用 GCC 或 Clang 編譯鏈接程序時加上 -fsanitize=address 開關。為了獲得合理的性能,可以加上 -O1 或更高。為了在錯誤信息中獲得更友好的棧追蹤信息可以加上 -fno-omit-frame-pointer。為了獲得完美的棧追蹤信息,還可以禁用內聯(使用 -O1)和尾調用消除(-fno-optimize-sibling-calls)

下面是一段存在內存訪問錯誤的代碼:

// main.cpp int main(int argc, char **argv) {int *array = new int[100];delete [] array;return array[argc]; // BOOM }

使用 GCC 編譯并運行:

$ gcc -fsanitize=address -fno-omit-frame-pointer -O1 -g -o main main.cpp $ ./main ================================================================= ==5385==ERROR: AddressSanitizer: heap-use-after-free on address 0x61400000fe44 at pc 0x0000004007d4 bp 0x7ffddf0bafb0 sp 0x7ffddf0bafa0 READ of size 4 at 0x61400000fe44 thread T0#0 0x4007d3 in main addresssanitizer_demo/main.cpp:4#1 0x7f297fc2282f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)#2 0x4006b8 in _start (addresssanitizer_demo/main+0x4006b8)0x61400000fe44 is located 4 bytes inside of 400-byte region [0x61400000fe40,0x61400000ffd0) freed by thread T0 here:#0 0x7f2980065caa in operator delete[](void*) (/usr/lib/x86_64-linux-gnu/libasan.so.2+0x99caa)#1 0x4007a8 in main /home/hanpfei0306/data/MyProjects/addresssanitizer_demo/main.cpp:3#2 0x7f297fc2282f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)previously allocated by thread T0 here:#0 0x7f29800656b2 in operator new[](unsigned long) (/usr/lib/x86_64-linux-gnu/libasan.so.2+0x996b2)#1 0x400798 in main /home/hanpfei0306/data/MyProjects/addresssanitizer_demo/main.cpp:2#2 0x7f297fc2282f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)SUMMARY: AddressSanitizer: heap-use-after-free addresssanitizer_demo/main.cpp:4 main Shadow bytes around the buggy address:0x0c287fff9f70: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa0x0c287fff9f80: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa0x0c287fff9f90: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa0x0c287fff9fa0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa0x0c287fff9fb0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa =>0x0c287fff9fc0: fa fa fa fa fa fa fa fa[fd]fd fd fd fd fd fd fd0x0c287fff9fd0: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd0x0c287fff9fe0: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd0x0c287fff9ff0: fd fd fd fd fd fd fd fd fd fd fa fa fa fa fa fa0x0c287fffa000: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa0x0c287fffa010: 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: 00Partially addressable: 01 02 03 04 05 06 07 Heap left redzone: faHeap right redzone: fbFreed heap region: fdStack left redzone: f1Stack mid redzone: f2Stack right redzone: f3Stack partial redzone: f4Stack after return: f5Stack use after scope: f8Global redzone: f9Global init order: f6Poisoned by user: f7Container overflow: fcArray cookie: acIntra object redzone: bbASan internal: fe ==5385==ABORTING

AddressSanitizer 在探測到內存錯誤之后,向 stderr 打印了錯誤信息并以非 0 值返回碼退出。AddressSanitizer 在發現第一個錯誤時退出程序。這主要是基于如下的設計:

  • 這種方法允許 AddressSanitizer 產生更快和更小的生成碼(總共 ~5%)。
  • 解決 bug 變得無法避免。AddressSanitizer 不產生誤報。一旦內存崩潰發生,則程序進入不一致的狀態,這可能導致令人費解的結果和潛在的誤導性的后續報告。

如果進程運行在沙盒中且運行在 OS X 10.10 或更早的版本上,則需要設置DYLD_INSERT_LIBRARIES 環境變量并把它指向由編譯器打包用于構建可執行文件的 ASan 庫。(可以搜索名字中包含 asan 的動態鏈接庫來找到這個庫。)如果沒有設置環境變量,則進程將試圖重新執行。同時記住,當把可執行文件移動到另一臺機器時,ASan 庫也需要復制過去。

編譯時如果遺漏了 -g 參數,導致可執行文件中缺乏調試信息,則探測到內存錯誤時,AddressSanitizer 吐出來的錯誤信息中,無法顯示具體的出錯的代碼行,就像下面這樣:

$ gcc -fsanitize=address -fno-omit-frame-pointer -O1 -o main main.cpp $ ./main ================================================================= ==5403==ERROR: AddressSanitizer: heap-use-after-free on address 0x61400000fe44 at pc 0x0000004007d4 bp 0x7ffd981fce20 sp 0x7ffd981fce10 READ of size 4 at 0x61400000fe44 thread T0#0 0x4007d3 in main (addresssanitizer_demo/main+0x4007d3)#1 0x7f9de8af482f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)#2 0x4006b8 in _start (addresssanitizer_demo/main+0x4006b8)0x61400000fe44 is located 4 bytes inside of 400-byte region [0x61400000fe40,0x61400000ffd0) freed by thread T0 here:#0 0x7f9de8f37caa in operator delete[](void*) (/usr/lib/x86_64-linux-gnu/libasan.so.2+0x99caa)#1 0x4007a8 in main (addresssanitizer_demo/main+0x4007a8)#2 0x7f9de8af482f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)previously allocated by thread T0 here:#0 0x7f9de8f376b2 in operator new[](unsigned long) (/usr/lib/x86_64-linux-gnu/libasan.so.2+0x996b2)#1 0x400798 in main (addresssanitizer_demo/main+0x400798)#2 0x7f9de8af482f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)SUMMARY: AddressSanitizer: heap-use-after-free ??:0 main Shadow bytes around the buggy address:0x0c287fff9f70: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa0x0c287fff9f80: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa0x0c287fff9f90: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa0x0c287fff9fa0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa0x0c287fff9fb0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa =>0x0c287fff9fc0: fa fa fa fa fa fa fa fa[fd]fd fd fd fd fd fd fd0x0c287fff9fd0: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd0x0c287fff9fe0: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd0x0c287fff9ff0: fd fd fd fd fd fd fd fd fd fd fa fa fa fa fa fa0x0c287fffa000: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa0x0c287fffa010: 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: 00Partially addressable: 01 02 03 04 05 06 07 Heap left redzone: faHeap right redzone: fbFreed heap region: fdStack left redzone: f1Stack mid redzone: f2Stack right redzone: f3Stack partial redzone: f4Stack after return: f5Stack use after scope: f8Global redzone: f9Global init order: f6Poisoned by user: f7Container overflow: fcArray cookie: acIntra object redzone: bbASan internal: fe ==5403==ABORTING

上面的 -g 選項也可以用 -ggdb 選項(盡可能的生成 gdb 的可以使用的調試信息)替換。

$ gcc -fsanitize=address -fno-omit-frame-pointer -O1 -ggdb -o main main.cpp $ ./main ================================================================= ==5495==ERROR: AddressSanitizer: heap-use-after-free on address 0x61400000fe44 at pc 0x0000004007d4 bp 0x7fff7014ca10 sp 0x7fff7014ca00 READ of size 4 at 0x61400000fe44 thread T0#0 0x4007d3 in main /home/hanpfei0306/data/MyProjects/addresssanitizer_demo/main.cpp:4#1 0x7fdddb19982f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)#2 0x4006b8 in _start (addresssanitizer_demo/main+0x4006b8)0x61400000fe44 is located 4 bytes inside of 400-byte region [0x61400000fe40,0x61400000ffd0) freed by thread T0 here:#0 0x7fdddb5dccaa in operator delete[](void*) (/usr/lib/x86_64-linux-gnu/libasan.so.2+0x99caa)#1 0x4007a8 in main /home/hanpfei0306/data/MyProjects/addresssanitizer_demo/main.cpp:3#2 0x7fdddb19982f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)previously allocated by thread T0 here:#0 0x7fdddb5dc6b2 in operator new[](unsigned long) (/usr/lib/x86_64-linux-gnu/libasan.so.2+0x996b2)#1 0x400798 in main /home/hanpfei0306/data/MyProjects/addresssanitizer_demo/main.cpp:2#2 0x7fdddb19982f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)SUMMARY: AddressSanitizer: heap-use-after-free /home/hanpfei0306/data/MyProjects/addresssanitizer_demo/main.cpp:4 main Shadow bytes around the buggy address:0x0c287fff9f70: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa0x0c287fff9f80: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa0x0c287fff9f90: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa0x0c287fff9fa0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa0x0c287fff9fb0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa =>0x0c287fff9fc0: fa fa fa fa fa fa fa fa[fd]fd fd fd fd fd fd fd0x0c287fff9fd0: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd0x0c287fff9fe0: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd0x0c287fff9ff0: fd fd fd fd fd fd fd fd fd fd fa fa fa fa fa fa0x0c287fffa000: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa0x0c287fffa010: 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: 00Partially addressable: 01 02 03 04 05 06 07 Heap left redzone: faHeap right redzone: fbFreed heap region: fdStack left redzone: f1Stack mid redzone: f2Stack right redzone: f3Stack partial redzone: f4Stack after return: f5Stack use after scope: f8Global redzone: f9Global init order: f6Poisoned by user: f7Container overflow: fcArray cookie: acIntra object redzone: bbASan internal: fe ==5495==ABORTING

使用 LLVM/Clang 編譯與上面使用 GCC 編譯基本相同:

$ clang++ -fsanitize=address -fno-omit-frame-pointer -O1 -g -o test main.cpp $ ./test ================================================================= ==5508==ERROR: AddressSanitizer: heap-use-after-free on address 0x61400000fe44 at pc 0x00000050770f bp 0x7ffea20137b0 sp 0x7ffea20137a8 READ of size 4 at 0x61400000fe44 thread T0#0 0x50770e in main /home/hanpfei0306/data/MyProjects/addresssanitizer_demo/main.cpp:4:10#1 0x7fdeb802382f in __libc_start_main /build/glibc-Cl5G7W/glibc-2.23/csu/../csu/libc-start.c:291#2 0x4192b8 in _start (addresssanitizer_demo/test+0x4192b8)0x61400000fe44 is located 4 bytes inside of 400-byte region [0x61400000fe40,0x61400000ffd0) freed by thread T0 here:#0 0x504c20 in operator delete[](void*) (addresssanitizer_demo/test+0x504c20)#1 0x5076de in main /home/hanpfei0306/data/MyProjects/addresssanitizer_demo/main.cpp:3:3#2 0x7fdeb802382f in __libc_start_main /build/glibc-Cl5G7W/glibc-2.23/csu/../csu/libc-start.c:291previously allocated by thread T0 here:#0 0x5045a0 in operator new[](unsigned long) (addresssanitizer_demo/test+0x5045a0)#1 0x5076d3 in main /home/hanpfei0306/data/MyProjects/addresssanitizer_demo/main.cpp:2:16#2 0x7fdeb802382f in __libc_start_main /build/glibc-Cl5G7W/glibc-2.23/csu/../csu/libc-start.c:291SUMMARY: AddressSanitizer: heap-use-after-free /home/hanpfei0306/data/MyProjects/addresssanitizer_demo/main.cpp:4:10 in main Shadow bytes around the buggy address:0x0c287fff9f70: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa0x0c287fff9f80: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa0x0c287fff9f90: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa0x0c287fff9fa0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa0x0c287fff9fb0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa =>0x0c287fff9fc0: fa fa fa fa fa fa fa fa[fd]fd fd fd fd fd fd fd0x0c287fff9fd0: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd0x0c287fff9fe0: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd0x0c287fff9ff0: fd fd fd fd fd fd fd fd fd fd fa fa fa fa fa fa0x0c287fffa000: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa0x0c287fffa010: 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: 00Partially addressable: 01 02 03 04 05 06 07 Heap left redzone: faHeap right redzone: fbFreed heap region: fdStack left redzone: f1Stack mid redzone: f2Stack right redzone: f3Stack partial redzone: f4Stack after return: f5Stack use after scope: f8Global redzone: f9Global init order: f6Poisoned by user: f7Container overflow: fcArray cookie: acIntra object redzone: bbASan internal: feLeft alloca redzone: caRight alloca redzone: cb ==5508==ABORTING

可以通過 readelf 看一下 GCC 和 LLVM 生成的可執行文件有什么差別:

# GCC 生成的可執行文件 $ readelf -s -W main | grep asan Symbol table '.dynsym' contains 15 entries:Num: Value Size Type Bind Vis Ndx Name1: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __asan_report_load410: 0000000000400650 0 FUNC GLOBAL DEFAULT UND __asan_init_v444: 0000000000000000 0 FILE LOCAL DEFAULT ABS asan_preinit.cc57: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __asan_report_load462: 0000000000400650 0 FUNC GLOBAL DEFAULT UND __asan_init_v469: 0000000000600dd0 8 OBJECT GLOBAL HIDDEN 19 __local_asan_preinit# LLVM 生成的可執行文件 $ readelf -s -W test | grep asan3: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __asan_default_options16: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __asan_on_error49: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __asan_default_suppressions66: 0000000000426740 466 FUNC GLOBAL DEFAULT 13 __asan_stack_malloc_070: 00000000004269b0 504 FUNC GLOBAL DEFAULT 13 __asan_stack_malloc_1 . . . . . .271: 0000000000428000 134 FUNC GLOBAL DEFAULT 13 __asan_stack_free_8272: 000000000042b1e0 44 FUNC GLOBAL DEFAULT 13 __asan_register_image_globals273: 00000000004d7550 44 FUNC GLOBAL DEFAULT 13 __asan_report_load4_noabort275: 00000000004282a0 134 FUNC GLOBAL DEFAULT 13 __asan_stack_free_9 . . . . . .635: 00000000004d7460 44 FUNC GLOBAL DEFAULT 13 __asan_report_load2636: 00000000004d74f0 44 FUNC GLOBAL DEFAULT 13 __asan_report_load4644: 00000000004d7580 44 FUNC GLOBAL DEFAULT 13 __asan_report_load8 . . . . . .37: 0000000000000000 0 FILE LOCAL DEFAULT ABS asan_allocator.cc.o38: 0000000000419370 254 FUNC LOCAL DEFAULT 13 _ZN6__asanL10RZSize2LogEj39: 0000000000421c90 214 FUNC LOCAL DEFAULT 13 _ZN11__sanitizer20SizeClassAllocator64ILm105553116266496ELm4398046511104ELm0ENS_12SizeClassMapILm17ELm128ELm16EEEN6__asan20AsanMapUnmapCallbackEE15DeallocateBatchEPNS_14AllocatorStatsEmPNS2_13TransferBatchE.isra.3240: 0000000000419470 1241 FUNC LOCAL DEFAULT 13 _ZN6__asan9AsanChunk8UsedSizeEb.part.191162: 0000000000422670 1101 FUNC WEAK HIDDEN 13 _ZN11__sanitizer28SizeClassAllocatorLocalCacheINS_20SizeClassAllocator64ILm105553116266496ELm4398046511104ELm0ENS_12SizeClassMapILm17ELm128ELm16EEEN6__asan20AsanMapUnmapCallbackEEEE6RefillEPS6_m1178: 00000000004d74f0 44 FUNC GLOBAL DEFAULT 13 __asan_report_load41194: 00000000004cff10 57 FUNC GLOBAL HIDDEN 13 _ZN6__asan10AsanTSDGetEv1196: 00000000004d6ea0 8 FUNC GLOBAL DEFAULT 13 __asan_get_report_sp1202: 00000000004a75e0 63 FUNC GLOBAL HIDDEN 13 _ZN6__asan13SetThreadNameEPKc1212: 00000000004d7b50 96 FUNC GLOBAL DEFAULT 13 __asan_load1_noabort . . . . . .2202: 00000000004d9780 96 FUNC GLOBAL DEFAULT 13 __asan_init2208: 000000000041a720 2468 FUNC GLOBAL HIDDEN 13 _ZN6__asan22FindHeapChunkByAddressEm2219: 00000000004d9800 7 FUNC GLOBAL HIDDEN 13 _ZN6__asan20GetMallocContextSizeEv . . . . . .3790: 0000000000419ac0 19 FUNC GLOBAL HIDDEN 13 _ZN6__asan13AsanChunkView11IsAllocatedEv3796: 00000000004d08e0 97 FUNC GLOBAL HIDDEN 13 _ZN6__asan25ThreadNameWithParenthesisEPNS_17AsanThreadContextEPcm

主要看兩個可執行文件中都有的符號 __asan_report_load4 和 __asan_init,可以看到 GCC 生成的可執行文件動態鏈接 ASan 庫,LLVM 靜態鏈接。

符號化輸出

AddressSanitizer 收集如下事件的調用棧:

  • malloc 和 malloc
  • 線程創建
  • 失敗

malloc 和 malloc 發生的相對頻繁,且它對于快速解開調用棧非常重要。AddressSanitizer 使用一個依賴幀指針的簡單的 unwinder。

如果不關心 malloc/free 調用棧,簡單地完全禁用(使用 malloc_context_size=0 運行時標記)unwinder。

每個棧幀需要被符號化(當然,如果二進制文件編譯時帶有調試信息)。給定一臺 PC,我們需要輸出:

#0xabcdf function_name file_name.cc:1234

AddressSanitizer 使用 Clang 包中的 llvm-symbolizer 符號化棧追蹤信息(注意理想的 llvm-symbolizer 版本必須與 ASan 運行時庫匹配)。為了使 AddressSanitizer 符號化它的輸出,需要設置 ASAN_SYMBOLIZER_PATH 環境變量指向 llvm-symbolizer 二進制文件,或確保 llvm-symbolizer 在 $PATH 中:

$ ASAN_SYMBOLIZER_PATH=/usr/local/bin/llvm-symbolizer ./a.out ==9442== ERROR: AddressSanitizer heap-use-after-free on address 0x7f7ddab8c084 at pc 0x403c8c bp 0x7fff87fb82d0 sp 0x7fff87fb82c8 READ of size 4 at 0x7f7ddab8c084 thread T0#0 0x403c8c in main example_UseAfterFree.cc:4#1 0x7f7ddabcac4d in __libc_start_main ??:0 0x7f7ddab8c084 is located 4 bytes inside of 400-byte region [0x7f7ddab8c080,0x7f7ddab8c210) freed by thread T0 here:#0 0x404704 in operator delete[](void*) ??:0#1 0x403c53 in main example_UseAfterFree.cc:4#2 0x7f7ddabcac4d in __libc_start_main ??:0 previously allocated by thread T0 here:#0 0x404544 in operator new[](unsigned long) ??:0#1 0x403c43 in main example_UseAfterFree.cc:2#2 0x7f7ddabcac4d in __libc_start_main ??:0 ==9442== ABORTING

llvm-symbolizer 符號化工具屬于 llvm 包,Ubuntu 下具體的安裝方法可以參考 LLVM Debian/Ubuntu nightly packages。

如果上面的方法不起作用,可以使用一個單獨的腳本來離線地符號化結果(在線的符號化可以通過設置 ASAN_OPTIONS=symbolize=0,或者設置一個空的 ASAN_SYMBOLIZER_PATH 環境變量($ export ASAN_SYMBOLIZER_PATH=)來強制禁用):

$ ASAN_OPTIONS=symbolize=0 ./a.out 2> log $ projects/compiler-rt/lib/asan/scripts/asan_symbolize.py / < log | c++filt ==9442== ERROR: AddressSanitizer heap-use-after-free on address 0x7f7ddab8c084 at pc 0x403c8c bp 0x7fff87fb82d0 sp 0x7fff87fb82c8 READ of size 4 at 0x7f7ddab8c084 thread T0#0 0x403c8c in main example_UseAfterFree.cc:4#1 0x7f7ddabcac4d in __libc_start_main ??:0 ...

這個腳本接收一個可選的參數 -- a file prefix。子串 .*prefix 將被從文件名中移除。

上面的 c++filt 用于解函數名的符號重組,這也可以通過給 asan_symbolize.py 腳本添加 -d 參數來完成。

在 OS X 上可能需要對二進制文件運行 dsymutil 以在 AddressSanitizer 報告中獲得 file:line 信息。

還可以引入自己的棧追蹤格式,使用 stack_trace_format 運行時標記完成。例如:

% ./a.out...#0 0x4b615d in main /home/you/use-after-free.cc:12:3... % ASAN_OPTIONS='stack_trace_format="[frame=%n, function=%f, location=%S]"' ./a.out...[frame=0, function=main, location=/home/you/use-after-free.cc:12:3]

AddressSanitizer 算法

簡單的版本

運行時庫替換 malloc 和 free 函數。把 malloc 分配的內存區域(紅色區域)附近放入一些特定的字節(使中毒)。把 free 的內存放入隔離區,并且也放入一些特定的字節(使中毒)。程序中的每次內存訪問由編譯器以下面的方式做一個轉換。
之前:

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

之后:

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

棘手的部分是如何把 IsPoisoned 實現的高效,且 ReportError 緊湊。同時,對某些訪問的插樁可能被證明是冗余的。

內存映射

虛擬地址空間被分割為 2 個互斥的類別:

  • 主應用內存(Mem):這種內存由常規的應用代碼使用。
  • 陰影內存(Shadow):這種內存包含陰影值(或元數據)。陰影和主應用程序內存之間存在對應關系。在主內存中 使中毒 一個字節意味著在對應的陰影區寫入一些特殊的值。

這兩種類別的內存應該以陰影內存(MemToShadow)可以被快速計算出來的方式進行組織。

編譯器執行的插樁如下:

shadow_address = MemToShadow(address); if (ShadowIsPoisoned(shadow_address)) {ReportError(address, kAccessSize, kIsWrite); }

映射

AddressSanitizer 把 8 個字節的應用內存映射為 1 個字節的陰影內存。

對于任何 8 字節對齊的應用內存只有 9 個不同的值:

  • qword 中的所有 8 字節是未中毒的(比如,可尋址)。陰影值為 0。
  • qword 中的所有 8 字節是中毒的(比如,不可尋址)。陰影值為負數。
  • 起始的 k 字節是未中毒的,其余的 8-k 字節是中毒的。陰影值為 k。這主要由 malloc 的行為保證,即 malloc 總是返回 8 字節對齊的內存塊。一個 qword 對齊的不同字節具有不同狀態的僅有的情況是 malloc 的區域的尾部。比如,如果我們調用 malloc(13),我們將擁有一個完整的未中毒的 qword 和一個開頭 5 字節未中毒的 qword。

插樁看起來像下面這樣:

byte *shadow_address = MemToShadow(address); byte shadow_value = *shadow_address; if (shadow_value) {if (SlowPathCheck(shadow_value, address, kAccessSize)) {ReportError(address, kAccessSize, kIsWrite);} } // Check the cases where we access first k bytes of the qword // and these k bytes are unpoisoned. bool SlowPathCheck(shadow_value, address, kAccessSize) {last_accessed_byte = (address & 7) + kAccessSize - 1;return (last_accessed_byte >= shadow_value); }

MemToShadow(ShadowAddr) 落入不可尋址的 ShadowGap 區域。因此,如果程序試圖直接訪問陰影區域中的內存位置,它將崩潰。

64-bit

Shadow = (Mem >> 3) + 0x7fff8000; [0x10007fff8000, 0x7fffffffffff]HighMem
[0x02008fff7000, 0x10007fff7fff]HighShadow
[0x00008fff7000, 0x02008fff6fff]ShadowGap
[0x00007fff8000, 0x00008fff6fff]LowShadow
[0x000000000000, 0x00007fff7fff]LowMem

32 bit

Shadow = (Mem >> 3) + 0x20000000; [0x40000000, 0xffffffff]HighMem
[0x28000000, 0x3fffffff]HighShadow
[0x24000000, 0x27ffffff]ShadowGap
[0x20000000, 0x23ffffff]LowShadow
[0x00000000, 0x1fffffff]LowMem

超緊湊的陰影區

使用更緊湊的陰影區內存也是可能的,比如:

Shadow = (Mem >> 7) | kOffset;

還在實驗中。

報告錯誤

ReportError 可以被實現為一個調用(當前默認就是這樣),但也有一些其它的,稍微更加高效和/或更加緊湊的方案。此刻默認的行為

  • 把失敗地址拷貝到 %rax (%eax)
  • 執行 ud2 (產生 SIGILL)
  • 在 ud2 之后的一個字節指令中編碼訪問類型和大小。整體上這 3 個指令需要 5-6 個字節的機器碼。

僅使用單個指令(比如 ud2)也是可能的,但這需要在運行時庫中有一個完整的反匯編器(或一些其它的 hacks)。

為了捕獲棧溢出,AddressSanitizer 插樁的代碼像這樣:

原始的代碼:

void foo() {char a[8];...return; }

插樁后的代碼:

void foo() {char redzone1[32]; // 32-byte alignedchar a[8]; // 32-byte alignedchar redzone2[24];char redzone3[32]; // 32-byte alignedint *shadow_base = MemToShadow(redzone1);shadow_base[0] = 0xffffffff; // poison redzone1shadow_base[1] = 0xffffff00; // poison redzone2, unpoison 'a'shadow_base[2] = 0xffffffff; // poison redzone3...shadow_base[0] = shadow_base[1] = shadow_base[2] = 0; // unpoison allreturn; }

插樁的代碼示例(x86_64)

# long load8(long *a) { return *a; } 0000000000000030 <load8>:30: 48 89 f8 mov %rdi,%rax33: 48 c1 e8 03 shr $0x3,%rax37: 80 b8 00 80 ff 7f 00 cmpb $0x0,0x7fff8000(%rax)3e: 75 04 jne 44 <load8+0x14>40: 48 8b 07 mov (%rdi),%rax <<<<<< original load43: c3 retq 44: 52 push %rdx45: e8 00 00 00 00 callq __asan_report_load8 # int load4(int *a) { return *a; } 0000000000000000 <load4>:0: 48 89 f8 mov %rdi,%rax3: 48 89 fa mov %rdi,%rdx6: 48 c1 e8 03 shr $0x3,%raxa: 83 e2 07 and $0x7,%edxd: 0f b6 80 00 80 ff 7f movzbl 0x7fff8000(%rax),%eax14: 83 c2 03 add $0x3,%edx17: 38 c2 cmp %al,%dl19: 7d 03 jge 1e <load4+0x1e>1b: 8b 07 mov (%rdi),%eax <<<<<< original load1d: c3 retq 1e: 84 c0 test %al,%al20: 74 f9 je 1b <load4+0x1b>22: 50 push %rax23: e8 00 00 00 00 callq __asan_report_load4

未對齊的訪問

當前緊湊的映射將不捕獲未對齊的部分越界訪問:

int *x = new int[2]; // 8 bytes: [0,7]. int *u = (int*)((char*)x + 6); *u = 1; // Access to range [6-9]

https://github.com/google/sanitizers/issues/100 中描述了一個可行的方案,但它付出了性能的代價。

運行時庫

Malloc

運行時庫替換 malloc/free,并提供錯誤報告函數,如 __asan_report_load8。

malloc 分配由紅區圍繞的請求數量的內存。陰影值對應的紅區被下毒,主內存區域的陰影值被清除。

free 用陰影值對整個區域下毒,并把內存塊放入一個隔離區(這樣在一定時間內這個內存塊將不會再次被 malloc 返回)。

參考文檔

  • Clang AddressSanitizer
  • AddressSanitizerAlgorithm
  • llvm-symbolizer
  • Address Sanitizer 用法
  • sanitizers
  • AddressSanitizerExampleHeapOutOfBounds
  • AddressSanitizerCallStack

總結

以上是生活随笔為你收集整理的Linux 下的 AddressSanitizer的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。