原文地址:http://blog.csdn.net/jinzhuojun/article/details/46659155
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
如果想只檢查某部分,可以用HeapProfileLeakChecker生成內存快照,然後執行完要檢查部分後調用assert(checker.NoLeaks())。具體用法見:http://goog-perftools.sourceforge.net/doc/heap_checker.html
更詳細的信息可以用google-pprof獲得,如:
$ google-pprof ./bug "/tmp/bug.1353._main_-end.heap" --inuse_objects --lines --heapcheck --edgefraction=1e-10 --nodefraction=1e-10 --gv
關於Tcmalloc更多的配置信息可以參見:http://gperftools.googlecode.com/svn/trunk/doc/tcmalloc.html
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參數即可。
另外memcheck還提供一系列參數可以調整檢測策略,具體可參見Valgrind User Manual或者http://valgrind.org/docs/manual/mc-manual.html
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
各種參數配置請參見:https://code.google.com/p/address-sanitizer/wiki/Flags
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)從而在出錯時方便調試。
使用時需要修改源碼。該庫需要單獨下載:
http://www.linkdata.se/sourcecode/memwatch/
然後在要檢查的代碼中包含頭文件:
#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等工具。