valgrind 内存泄漏_应用 AddressSanitizer 发现程序内存错误
應用 AddressSanitizer 發現程序內存錯誤
作為 C/ C++ 工程師,在開發過程中會遇到各類問題,最常見便是內存使用問題,比如,越界,泄漏。過去常用的工具是 Valgrind,但使用 Valgrind 最大問題是它會極大地降低程序運行的速度,初步估計會降低 10 倍運行速度。而 Google 開發的 AddressSanitizer 這個工具很好地解決了 Valgrind 帶來性能損失問題,它非???#xff0c;只拖慢程序 2 倍速度。
AddressSanitizer 概述
AddressSanitizer 是一個基于編譯器的測試工具,可在運行時檢測 C/C++ 代碼中的多種內存錯誤。嚴格上來說,AddressSanitizer 是一個編譯器插件,它分為兩個模塊,一個是編譯器的 instrumentation 模塊,一個是用來替換 malloc/free 的動態庫。
Instrumentation 主要是針對在 llvm 編譯器級別對訪問內存的操作(store,load,alloc等),將它們進行處理。動態庫主要提供一些運行時的復雜的功能(比如 poison/unpoison shadow memory)以及將 malloc/free 等系統調用函數 hook 住。
AddressSanitizer 基本使用
根據 AddressSanitizer Wiki 可以檢測下面這些內存錯誤 - Use after free:訪問堆上已經被釋放的內存 - Heap buffer overflow:堆上緩沖區訪問溢出 - Stack buffer overflow:棧上緩沖區訪問溢出 - Global buffer overflow:全局緩沖區訪問溢出 - Use after return:訪問棧上已被釋放的內存 - Use after scope:棧對象使用超過定義范圍 - Initialization order bugs:初始化命令錯誤 - Memory leaks:內存泄漏
這里我只簡單地介紹下基本的使用,詳細的使用文檔可以看官方的編譯器使用文檔,比如 Clang 的文檔:https://clang.llvm.org/docs/AddressSanitizer.html
Use after free 實踐例子
下面這段代碼是一個很簡單的 Use after free 的例子:
//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最終我們會看到如下的輸出:
==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可以看到一目了然,非常清楚的告訴了我們在哪一行內存被釋放,而又在哪一行內存再次被使用。
還有一個是內存泄漏,比如下面的代碼,顯然 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 leaksDirect 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 的實現,更詳細的算法實現可以看《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 和其他內存檢測工具對比
下圖是 AddressSanitizer 與其他的一些內存檢測工具的對比:
參數說明:
- DBI: dynamic binary instrumentation(動態二進制插樁)
- CTI: compile-time instrumentation (編譯時插樁)
- UMR: uninitialized memory reads (讀取未初始化的內存)
- UAF: use-after-free (aka dangling pointer) (使用釋放后的內存)
- UAR: use-after-return (使用返回后的值)
- OOB: out-of-bounds (溢出)
- x86: includes 32- and 64-bit.
可以看到相比于 Valgrind,AddressSanitizer 只會拖慢程序 2 倍運行速度。當前 AddressSanitizer 支持 GCC 以及 Clang,其中 GCC 是從 4.8 開始支持,而 Clang 的話是從 3.1 開始支持。
AddressSanitizer 的使用注意事項
在 Nebula Graph 中開啟 AddressSanitizer
我們在 Nebula Graph 中也使用了 AddressSanitizer,它幫助我們發現了非常多的問題。而在 Nebula Graph 中開啟 AddressSanitizer 很簡單,只需要在 Cmake 的時候帶上打開 ENABLE_ASAN 這個 Option 就可以,比如:
Cmake -DENABLE_ASAN=On這里建議所有的開發者在開發完畢功能運行單元測試的時候都打開 AddressSanitizer 來運行單元測試,這樣可以發現很多不容易發現的內存問題,節省很多調試的時間。
附錄
- Nebula Graph:一個開源的分布式圖數據庫
- GitHub:https://github.com/vesoft-inc/nebula
- 官方博客:https://nebula-graph.io/cn/posts/
- 微博:weibo.com/nebulagraph
推薦閱讀
- Jepsen 測試框架在圖數據庫 Nebula Graph 中的實踐
總結
以上是生活随笔為你收集整理的valgrind 内存泄漏_应用 AddressSanitizer 发现程序内存错误的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: c++读出像素矩阵_Python传num
- 下一篇: android界面初始化设计,界面数据初