Linux中的常用記憶體問題檢測工具
C/C++等底層語言在提供強大功能及效能的同時,其靈活的記憶體訪問也帶來了各種糾結的問題。如果crash的地方正是記憶體使用錯誤的地方,說明你人品好。如果crash的地方記憶體明顯不是consistent的,或者記憶體管理資訊都已被破壞,並且還是隨機出現的,那就比較麻煩了。當然,祼看code打log是一個辦法,但其效率不是太高,尤其是在執行成本高或重現概率低的情況下。另外,靜態檢查也是一類方法,有很多工具(lint, cppcheck, klockwork, splint, o, etc.)。但缺點是誤報很多,不適合針對性問題。另外好點的一般還要錢。最後,就是動態檢查工具。下面介紹幾個Linux平臺下主要的執行時記憶體檢查工具。絕大多數都是開源免費且支援x86和ARM平臺的。
首先,比較常見的記憶體問題有下面幾種: • memory overrun:寫記憶體越界 • double free:同一塊記憶體釋放兩次 • use after free:記憶體釋放後使用 • wild free:釋放記憶體的引數為非法值 • access uninitialized memory:訪問未初始化記憶體 • read invalid memory:讀取非法記憶體,本質上也屬於記憶體越界 • memory leak:記憶體洩露 • use after return:caller訪問一個指標,該指標指向callee的棧內記憶體 • stack overflow:棧溢位
針對上面的問題,主要有以下幾種方法: 1. 為了檢測記憶體非法使用,需要hook記憶體分配和操作函式。hook的方法可以是用C-preprocessor,也可以是在連結庫中直接定義(因為Glibc中的malloc/free等函式都是weak symbol),或是用LD_PRELOAD。另外,通過hook strcpy(),memmove()等函式可以檢測它們是否引起buffer overflow。 2. 為了檢查記憶體的非法訪問,需要對程式的記憶體進行bookkeeping,然後截獲每次訪存操作並檢測是否合法。bookkeeping的方法大同小異,主要思想是用shadow memory來驗證某塊記憶體的合法性。至於instrumentation的方法各種各樣。有run-time的,比如通過把程式執行在虛擬機器中或是通過binary translator來執行;或是compile-time的,在編譯時就在訪存指令時就加入檢查操作。另外也可以通過在分配記憶體前後加設為不可訪問的guard page,這樣可以利用硬體(MMU)來觸發SIGSEGV,從而提高速度。 3. 為了檢測棧的問題,一般在stack上設定canary,即在函式呼叫時在棧上寫magic number或是隨機值,然後在函式返回時檢查是否被改寫。另外可以通過mprotect()在stack的頂端設定guard page,這樣棧溢位會導致SIGSEGV而不至於破壞資料。
以上方法有些強於功能,有些勝在效能,有些則十分方便易用,總之各有千秋。以下是幾種常用工具在Linux x86_64平臺的實驗結果,注意其它平臺可能結果有差異。另外也可能由於版本過老,編譯環境差異,姿勢不對,總之各種原因造成遺漏,如有請諒解~
Tool\Problem | memory overrun | double free | use after free | wild free | access uninited | read invalid memory | memory leak | use after return | stack overflow |
---|---|---|---|---|---|---|---|---|---|
Memory checking tools in Glibc | Yes | Yes | Yes | Yes(if use memcpy, strcpy, etc) | |||||
TCMalloc(Gperftools) | Yes | ||||||||
Valgrind | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
Address Sanitizer(ASan) | Yes | Yes | Yes | Yes | (Memory Sanitizer) | Yes | Yes | Yes | Yes |
Memwatch | Yes | Yes | Yes | ||||||
Dr.Memory | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | |
Electric Fence | Yes | Yes | Yes | Yes | |||||
Dmalloc | Yes | Yes | Yes | Yes | Yes |
下面簡單介紹一下這些工具以及基本用法。更詳細用法請參見各自manual。
Memory checking tools in Glibc
Glibc中自帶了一些Heap consistency checking機制。
MALLOC_CHECK_
用mallopt()的M_CHECK_ACTION可以設定記憶體檢測行為,設MALLOC_CHECK_環境變數效果也是一樣的。從Glibc 2.3.4開始,預設為3。即打印出錯資訊,stack trace和memory mapping,再退出程式。設定LIBC_FATAL_STDERR_=1可以將這些資訊輸出到stderr。比如執行以下有double free的程式: $ MALLOC_CHECK_=3 ./bug 會列印如下資訊然後退出:
*** Error in `./bug': free(): invalid pointer: 0x00000000010d6010 ***
======= Backtrace: =========
/lib/x86_64-linux-gnu/libc.so.6(+0x7338f)[0x7f367073238f]
/lib/x86_64-linux-gnu/libc.so.6(+0x81fb6)[0x7f3670740fb6]
./bug[0x400845]
./bug[0x400c36]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf5)[0x7f36706e0ec5]
./bug[0x400729]
======= Memory map: ========
00400000-00402000 r-xp 00000000 08:01 2893041 /home/jzj/code/bug
00601000-00602000 r--p 00001000 08:01 2893041 /home/jzj/code/bug
00602000-00603000 rw-p 00002000 08:01 2893041 /home/jzj/code/bug
010d6000-010f7000 rw-p 00000000 00:00 0 [heap]
7f36704a8000-7f36704be000 r-xp 00000000 08:01 4203676 /lib/x86_64-linux-gnu/libgcc_s.so.1
7f36704be000-7f36706bd000 ---p 00016000 08:01 4203676 /lib/x86_64-linux-gnu/libgcc_s.so.1
7f36706bd000-7f36706be000 r--p 00015000 08:01 4203676 /lib/x86_64-linux-gnu/libgcc_s.so.1
7f36706be000-7f36706bf000 rw-p 00016000 08:01 4203676 /lib/x86_64-linux-gnu/libgcc_s.so.1
…
Aborted (core dumped)
mcheck
mcheck是Glibc中的堆記憶體一致性檢查機制。使用時只要加上標頭檔案:
#include <mcheck>
再在要開始檢查的地方加上:
if (mcheck(NULL) != 0) {
fprintf(stderr, "mcheck() failed\n");
exit(EXIT_FAILURE);
}
…
編譯時加-lmcheck然後執行即可: $ g++ -Wall -g problem.cpp -o bug -lmcheck
_FORTIFY_SOURCE
巨集_FORTIFY_SOURCE提供輕量級的buffer overflow檢測。設定後會呼叫Glibc裡帶_chk字尾的函式,做一些執行時檢查。主要檢查各種字串緩衝區溢位和記憶體操作。比如memmove, memcpy, memset, strcpy, strcat, vsprintf等。注意一些平臺上編譯時要加-O1或以上優化。這樣就可以檢查出因為那些記憶體操作函式導致的緩衝溢位問題: $ g++ -Wall -g -O2 -D_FORTIFY_SOURCE=2 problem.cpp -o bug
*** buffer overflow detected ***: ./bug terminated
======= Backtrace: =========
/lib/x86_64-linux-gnu/libc.so.6(+0x7338f)[0x7f9976e1638f]
/lib/x86_64-linux-gnu/libc.so.6(__fortify_fail+0x5c)[0x7f9976eadc9c]
/lib/x86_64-linux-gnu/libc.so.6(+0x109b60)[0x7f9976eacb60]
mtrace
mtrace可以用於檢查malloc/free是否正確配對。用時用mtrace()和muntrace()表示開始和結束記憶體分配trace(如果檢測到結束結尾的話可以不用muntrace())。但這是簡單地記錄沒有free對應的malloc,可能會有一些false alarm。
#include <mcheck.h>
mtrace();
// …
muntrace();
然後編譯: $ g++ -Wall -g problem.cpp -o bug 執行時先設輸出的log檔案: $ export MALLOC_TRACE=output.log 用mtrace命令將輸出檔案變得可讀: $ mtrace ./bug $MALLOC_TRACE 就可以得到哪些地方的記憶體申請還沒有被free掉。
Memory not freed:
Address Size Caller
0x00000000008d4520 0x400 at /home/jzj/code/problem.cpp:73
Gperftools
Gperftools(Google Performance Tools)為一組工具集,包括了thread-caching malloc(TCMalloc)和CPU profiler等元件。TCMalloc和Glibc中的ptmalloc相比更快,並可以有效減少多執行緒之間的競爭,因為它會為每個執行緒單獨分配執行緒本地的Cache。這裡先只關注它的記憶體相關元件。通過tcmalloc可以做heap-checking和heap-profiling。
如果懶得build,Ubuntu可以如下安裝: $ sudo apt-get install libgoogle-perftool-dev google-perftools 然後編譯時加-ltcmalloc,注意一定要放最後連結,如: $ g++ -Wall -g problem.cpp -g -o bug -ltcmalloc 編譯時不連結的話就也可以用LD_PRELOAD: $ export LD_PRELOAD=”/usr/lib/libtcmalloc.so” 執行的時候執行: $ HEAPCHECK=normal ./bug 就可以報出記憶體洩露:
Have memory regions w/o callers: might report false leaks
Leak check _main_ detected leaks of 1024 bytes in 1 objects
The 1 largest leaks:
*** WARNING: Cannot convert addresses to symbols in output below.
*** Reason: Cannot find 'pprof' (is PPROF_PATH set correctly?)
*** If you cannot fix this, try running pprof directly.
Leak of 1024 bytes in 1 objects allocated from:
@ 400ba3
@ 400de0
@ 7fe1be24bec5
@ 400899
@ 0
更詳細的資訊可以用google-pprof獲得,如:
$ google-pprof ./bug "/tmp/bug.1353._main_-end.heap" --inuse_objects --lines --heapcheck --edgefraction=1e-10 --nodefraction=1e-10 --gv
Valgrind
Valgrind是Valgrind core和Valgrind工具外掛的集合,除了用於檢查記憶體錯誤,還可以用來分析函式呼叫,快取使用,多執行緒競爭,堆疊使用等問題。這裡只關注memcheck工具,因為太常用 ,它預設就是開啟的。其原理是讓程式跑在一個虛擬機器上,因此速度會慢幾十倍。好在現實中很多程式是IO bound的,所以很多時候沒有慢到忍無可忍的地步。好處是它不需要重新編譯目標程式。它會通過hash表記錄每個heap block,同時通過shadow memory記錄這些記憶體區域的資訊。這樣就可以在每次訪存時檢查其合法性。
執行時可根據需要加配置引數,如:
$ valgrind --tool=memcheck --error-limit=no --track-origins=yes --trace-children=yes --track-fds=yes ./bug
如memory overrun就會報以下錯誤:
==1735== Invalid write of size 1
==1735== at 0x4008A7: overrun() (problem.cpp:26)
==1735== by 0x400C2B: main (problem.cpp:127)
==1735== Address 0x51fc460 is not stack'd, malloc'd or (recently) free'd
==1735==
use after free檢測結果:
==1739== Invalid write of size 1
==1739== at 0x4C2E51C: __GI_strncpy (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==1739== by 0x40098B: use_after_free() (problem.cpp:46)
==1739== by 0x400C3F: main (problem.cpp:133)
==1739== Address 0x51fc040 is 0 bytes inside a block of size 1,024 free'd
==1739== at 0x4C2BDEC: free (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==1739== by 0x400975: use_after_free() (problem.cpp:45)
==1739== by 0x400C3F: main (problem.cpp:133)
==1739==
==1739== Invalid write of size 1
==1739== at 0x4C2E5AC: __GI_strncpy (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==1739== by 0x40098B: use_after_free() (problem.cpp:46)
==1739== by 0x400C3F: main (problem.cpp:133)
==1739== Address 0x51fc045 is 5 bytes inside a block of size 1,024 free'd
==1739== at 0x4C2BDEC: free (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==1739== by 0x400975: use_after_free() (problem.cpp:45)
==1739== by 0x400C3F: main (problem.cpp:133)
access uninitialized memory結果:
==1742== Conditional jump or move depends on uninitialised value(s)
==1742== at 0x4EB17F1: _IO_file_overflow@@GLIBC_2.2.5 (fileops.c:867)
==1742== by 0x4E819CF: vfprintf (vfprintf.c:1661)
==1742== by 0x4E8B498: printf (printf.c:33)
==1742== by 0x400AA6: access_uninit() (problem.cpp:72)
==1742== by 0x400C5A: main (problem.cpp:142)
==1742== Uninitialised value was created by a heap allocation
==1742== at 0x4C2AB80: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==1742== by 0x400A87: access_uninit() (problem.cpp:71)
==1742== by 0x400C5A: main (problem.cpp:142)
像memory overrun和use after free這類問題比較難搞是因為出錯的時候往往不是第一現場。用Valgrind就比較容易抓到第一現場。如果一個物件A被釋放後,同一塊記憶體再次被申請為物件B,但程式中還是通過指向物件A的dangling pointer進行訪問,會覆蓋已有資料或者讀出錯誤資料。但這種情況Valgrind檢查不出來,因為Valgrind不會做語義上的分析。但是Valgrind可以配置記憶體分配策略,通過設定空閒記憶體佇列大小和優先順序讓被釋放的記憶體不馬上被重用。從而增大抓到此類問題的概率。
對於棧中記憶體,Memcheck只會做未初始化資料訪問的檢測,而不會做棧或全域性陣列中的越界檢測。這是由SGCheck來完成的,它與memcheck功能互補。使用SGCheck只需在valgrind後加上–tool=exp-sgcheck引數即可。
Address sanitizer (ASan)
早先是LLVM中的特性,後被加入GCC 4.8。在GCC 4.9後加入對ARM平臺的支援。因此用時不需要第三方庫,通過在編譯時指定flag即可開啟開關。它是 Mudflap的替代品(Mudflap從GCC 4.9開始不再支援,指定了也不做事)。ASan在編譯時在訪存操作中插入額外指令,同時通過Shadow memory來記錄和檢測記憶體的有效性。slowdown官方稱為2x左右。
使用時只要在CFLAGS中加上如下flag。注意如果連結so,只有可執行檔案需要加flag。 $ g++ -Wall -g problem.cpp -o bug -fsanitize=address -fno-omit-frame-pointer 直接執行,檢測出錯誤時會報出類似以下錯誤:
==22543==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x61900000fea0 at pc 0x400f22 bp 0x7ffe3c21be90 sp 0x7ffe3c21be88
WRITE of size 1 at 0x61900000fea0 thread T0
#0 0x400f21 in overrun() /home/jzj/code/problem.cpp:26
#1 0x401731 in main /home/jzj/code/problem.cpp:127
#2 0x7fb2a46b8ec4 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x21ec4)
#3 0x400d08 (/home/jzj/code/bug+0x400d08)
==26753==ERROR: AddressSanitizer: attempting double-free on 0x61900000fa80 in thread T0:
#0 0x7f591b4ba5c7 in __interceptor_free (/usr/lib/x86_64-linux-gnu/libasan.so.1+0x545c7)
#1 0x400e46 in double_free() /home/jzj/code/problem.cpp:17
#2 0x40173b in main /home/jzj/code/problem.cpp:130
#3 0x7f591b0c2ec4 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x21ec4)
#4 0x400d08 (/home/jzj/code/bug+0x400d08)
檢測一些特定問題需要加上專門的選項,比如要檢查訪問指向已被釋放的棧空間需要加上: ASAN_OPTIONS=detect_stack_use_after_return=1 如果要檢測memory leak需要加上: ASAN_OPTIONS=detect_leaks=1
Address-sanitizer是Sanitizer系工具中的一員。有一部分功能是在其餘工具裡,比如memory leak檢測在LeakSanitizer中,uninitialized memory read檢測在MemorySanitizer中。data race檢測在ThreadSanitizer中。它們最初都是LLVM中的特性,後被移植到GCC,所以用GCC的話最好用4.9,至少也是4.8以後版本。
AddressSanitizer不能檢測讀未初始化記憶體,而這MemorySanitizer(MSan)能做到。它包含compiler instrumentation模組和run-time的庫。目前只支援Linux x86_64平臺。使用時需在編譯選項加-fsanitize=memory -fPIE -pie,為了得到更詳細的資訊,最好加上-fno-omit-frame-pointer和-fsanitize-memory-track-origins。它實現了Valgrind的部分功能,但由於使用了compile-time instrumentation,所以速度更快。可惜目前只在LLVM上有,在GCC上還沒有,暫且略過。
Memwatch
Memwatch是一個輕量級的記憶體問題檢測工具。主要用於檢測記憶體分配釋放相關問題及記憶體越界訪問問題。通過C preprocessor,Memwatch替換所有 ANSI C的記憶體分配 函式,從而記錄分配行為。注意它不保證是執行緒安全的。效率上,大塊分配不受影響,小塊分配會受影響,因此它沒法使用原分配函式中的memory pool。最壞情況下會有3-5x的slowdown。它可以比較方便地模擬記憶體受限情況。對於未初始化記憶體訪問,和已釋放記憶體訪問,Memwatch會poison相應記憶體(分配出來寫0xFE,釋放記憶體寫0xFD)從而在出錯時方便除錯。
#include "memwatch.h"
然後加下面巨集編譯: $ gcc -DMEMWATCH -DMW_STDIO test.c memwatch.c -o test 預設結果輸出在memwatch.log。比如程式如果有double free的話會輸出:
Modes: __STDC__ 64-bit mwDWORD==(unsigned int)
mwROUNDALLOC==8 sizeof(mwData)==56 mwDataSize==56
double-free: <3> test.c(17), 0x25745e0 was freed from test.c(16)
Stopped at Sun Jun 14 10:57:15 2015
Memory usage statistics (global):
N)umber of allocations made: 1
L)argest memory usage : 1024
T)otal of all alloc() calls: 1024
U)nfreed bytes totals : 0
Memory leak的輸出:
Modes: __STDC__ 64-bit mwDWORD==(unsigned int)
mwROUNDALLOC==8 sizeof(mwData)==56 mwDataSize==56
Stopped at Sun Jun 14 10:56:22 2015
unfreed: <1> test.c(63), 1024 bytes at 0x195f5e0 {FE FE FE FE FE FE FE FE FE FE FE FE FE FE FE FE ................}
Memory usage statistics (global):
N)umber of allocations made: 1
L)argest memory usage : 1024
T)otal of all alloc() calls: 1024
U)nfreed bytes totals : 1024
Electric Fence
Electric Fence主要用於追蹤buffer overflow的讀和寫。它利用硬體來抓住越界訪問的指令。其原理是為每一次記憶體申請額外申請一個page或一組page,然後把這些buffer範圍外的page設為不可讀寫。這樣,如果程式訪問這些區域,由於頁表中這個額外page的許可權是不可讀寫,會產生段錯誤。那些被free()釋放的記憶體也會被設為不可訪問,因此訪問也會產生段錯誤。因為讀寫許可權以頁為單位,所以如果多的頁放在申請記憶體區域後,可防止overflow。如果要防止underflow,就得用環境變數EF_PROTECT_BELOW在區域前加保護頁。因為Electric Fence至少需要丙個頁來滿足記憶體分配申請,因此記憶體使用會非常大,好處是它利用了硬體來捕獲非法訪問,因此速度快。也算是空間換時間吧。
目前支援Window, Linux平臺,語言支援C/C++。限制包括無法檢測使用未初始化記憶體,memory leak等。同時它不是執行緒安全的。Ubuntu上懶得編譯可以安裝現成的: $ sudo apt-get install electric-fence
它是以庫的方式需要被連結到程式中: $ g++ -Wall -g problem.cpp -o bug -lefence 或者用LD_PRELOAD,不過記得不要同時連結其它的malloc debugger庫。 $ export LD_PRELOAD=libefence.so.0.0 另外,EF_PROTECT_BELOW,EF_PROTECT_FREE,EF_ALLOW_MALLOC_0和EF_FILL這些環境變數都是用來控制其行為的。可以參見manual:http://linux.die.net/man/3/efence
比如memory overrun和double free就可以得到如下結果:
Electric Fence 2.2 Copyright (C) 1987-1999 Bruce Perens <bruce@perens.com>
Segmentation fault (core dumped)
Electric Fence 2.2 Copyright (C) 1987-1999 Bruce Perens <bruce@perens.com>
ElectricFence Aborting: free(7fc1c17c8c00): address not from malloc().
Illegal instruction (core dumped)
它無法在log中打出詳細資訊,但如果執行前打開了coredump: $ ulimit -c unlimited 就可以gdb開啟coredump來分析了: $ gdb ./bug -c core
注意因為多數平臺在分配時遇到block size不是word size整數倍時會通過加padding byte進行word alignment。如果是在padded area中出現overrun則無法檢測。這裡可以通過在程式中設定EN_ALIGNMENT=1來防止byte padding,從而更容易檢測off by one的問題。
DUMA(http://duma.sourceforge.net/)從Electric Fence中fork出來並加入一些其它特性,比如leak detection,Windows支援等。
Dmalloc
比較經典的記憶體檢測工具,雖然N年沒更新了。dmalloc通過在分配區域增加padding magic number的做法來檢測非法訪問,因此它能夠檢測到問題但不能檢測出哪條指令出的錯。Dmalloc只能檢測越界寫,但不能檢測越界讀。另外,Dmalloc只檢測堆上用malloc系函式(而不是sbrk()或mmap())分配的記憶體,而無法對棧記憶體和靜態記憶體進行檢測。 本質上它也是通過hook malloc(), realloc(), calloc(),free()等記憶體管理函式,還有strcat(), strcpy()等記憶體操作函式,來檢測記憶體問題。它支援x86, ARM平臺,語言上支援C/C++,並且支援多執行緒。
使用時可以先從官網下載原始碼包(http://dmalloc.com/releases/),然後編譯安裝: $ tar zxvf dmalloc-5.5.2.tgz $ cd dmalloc-5.5.2 $ ./configure $ make && make install
少量修改原始碼。只需要加上下面的標頭檔案:
#ifdef DMALLOC
#include "dmalloc.h"
#endif
然後編譯時CFLAGS加上 -DDMALLOC -DDMALLOC_FUNC_CHECK,如: $ g++ -Wall -g -DDMALLOC -DDMALLOC_FUNC_CHECK problem.cpp -o bug -ldmalloc
dmalloc的配置選項可以通過設定環境變數DMALLOC_OPTIONS來實現,例如: $ export DMALLOC_OPTIONS=log=logfile,check-fence,check-blank,check-shutdown,check-heap,check-funcs,log-stats,log-non-free,print-messages,log-nonfree-space 這些用法可參見: http://dmalloc.com/docs/latest/online/dmalloc_26.html http://dmalloc.com/docs/latest/online/dmalloc_27.html 也可以用dmalloc這個命令來設定。直接dmalloc -v可用於檢視當前設定。
發生錯誤時會給出類似以下輸出:
1434270937: 2: error details: checking user pointer
1434270937: 2: pointer '0x7fc235336808' from 'unknown' prev access 'problem.cpp:35'
1434270937: 2: ERROR: _dmalloc_chunk_heap_check: free space has been overwritten (err 67)
1434270937: 2: error details: checking pointer admin
1434270937: 2: pointer '0x7fc235336808' from 'problem.cpp:37' prev access 'problem.cpp:35'
1434270937: 2: ERROR: free: free space has been overwritten (err 67)
1434271030: 3: error details: finding address in heap
1434271030: 3: pointer '0x7f0a7e29d808' from 'problem.cpp:27' prev access 'unknown'
1434271030: 3: ERROR: free: tried to free previously freed pointer (err 61)
另外Dmalloc還提供一些函式,如dmalloc_mark(),dmalloc_log_changed()和dmalloc_log_unfreed()等來列印記憶體資訊和分析記憶體變化: http://dmalloc.com/docs/5.3.0/online/dmalloc_13.html
Dr. Memory
重量級記憶體監測工具之一,用於檢測如未初始化記憶體訪問,越界訪問,已釋放記憶體訪問,double free,memory leak以及Windows上的handle leak, GDI API usage error等。它支援Windows, Linux和Mac作業系統, IA-32和AMD64平臺,和其它基於binary instrumentation的工具一樣,它不需要改目標程式的binary。有個缺點是目前只針對x86上的32位程式。貌似目前正在往ARM上port。其優點是對程式的正常執行影響小,和Valgrind相比,效能更好。官網為http://www.drmemory.org/。Dr. Memory基於DynamioRIO Binary Translator。原始程式碼不會直接執行,而是會經過translation後生成code cache,這些code cache會呼叫shared instrumentation來做記憶體檢測。
Dr. Memory提供各平臺的包下載。 https://github.com/DynamoRIO/drmemory/wiki/Downloads 下載後即可直接使用。首先編譯要檢測的測試程式: $ g++ -m32 -g -Wall problem.cpp -o bug -fno-inline -fno-omit-frame-pointer (在64位host上編譯32位程式需要安裝libc6-dev-i386和g++-multilib) 然後把Dr.Memory的bin加入PATH,如: $ export PATH=/home/jzj/tools/DrMemory-Linux-1.8.0-8/bin:$PATH 之後就可以使用Dr.Memory啟動目標程式: \$ drmemory – ./bug 更多用法參見 drmemory -help或http://drmemory.org/docs/page_options.html。
像遇到double-free和heap overflow問題的話就會給出類似下面結果:
~~Dr.M~~
~~Dr.M~~ Error #1: INVALID HEAP ARGUMENT to free 0x08ceb0e8
~~Dr.M~~ # 0 replace_free [/work/drmemory_package/common/alloc_replace.c:2503]
~~Dr.M~~ # 1 double_free [/home/jzj/code/problem.cpp:23]
~~Dr.M~~ # 2 main [/home/jzj/code/problem.cpp:157]
~~Dr.M~~ Note: @0:00:00.127 in thread 26159
~~Dr.M~~ Note: memory was previously freed here:
~~Dr.M~~ Note: # 0 replace_free [/work/drmemory_package/common/alloc_replace.c:2503]
~~Dr.M~~ Note: # 1 double_free [/home/jzj/code/problem.cpp:22]
~~Dr.M~~ Note: # 2 main [/home/jzj/code/problem.cpp:157]
~~Dr.M~~
~~Dr.M~~ Error #1: UNADDRESSABLE ACCESS beyond heap bounds: writing 0x0988f508-0x0988f509 1 byte(s)
~~Dr.M~~ # 0 overrun [/home/jzj/code/problem.cpp:32]
~~Dr.M~~ # 1 main [/home/jzj/code/problem.cpp:154]
~~Dr.M~~ Note: @0:00:00.099 in thread 26191
~~Dr.M~~ Note: prev lower malloc: 0x0988f0e8-0x0988f4e8
~~Dr.M~~ Note: instruction: mov $0x6a -> (%eax)
Stack protection
前面的工具大多用於堆記憶體檢錯,對於棧記憶體GCC本身提供了一些檢錯機制。加上-fstack-protector後,GCC會多加指令來檢查buffer/stack overflow。原理是為函式加guard variable。在函式進入時初始化,函式退出時檢查。相關的flag有-fstack-protector-strong -fstack-protector -fstack-protector-all等。使用例子: $ g++ -Wall -O2 -U_FORTIFY_SOURCE -fstack-protector-all problem.cpp -o bug 執行時會檢測到stack overflow:
*** stack smashing detected ***: ./bug terminated
Aborted (core dumped)
對於執行緒的棧可以參考pthread_attr_setguardsize()。
Rational purify & Insure++
Rational purity是IBM的商業化產品,要收費,所以木有用過,精神上支援。和Valgrind很像,也基於binary instrumentation,適用於不同平臺。另一個工具Insure++基於compile-time和binary instrumentation,可以檢測use-after-free,out-of-bounds,wild free和memory leak等記憶體問題。但也是要收費的,也精神上支援。。。。。。
大體來說,遇到詭異的記憶體問題,先可以試下Glibc和GCC裡自帶的檢測機制,因為enable起來方便。如果檢測不出來,那如果toolchain版本較新且有編譯環境,可以先嚐試ASan,因為其功能強大,且效率高。接下來,如果程式是I/O bound或slowdown可以接受,可以用Valgrind和Dr.Memory。它們功能強大且無需重新編譯,但速度較慢,且後者不支援64位程式和ARM平臺。然後可以根據實際情況和具體需要考慮Memwatch,Dmalloc和Electric Fence等工具。