原文地址:http://blog.csdn.net/jinzhuojun/article/details/46659155
C/C++等底層語言在提供強(qiáng)大功能及性能的同時,其靈活的內(nèi)存訪問也帶來了各種糾結(jié)的問題。如果crash的地方正是內(nèi)存使用錯誤的地方,說明你人品好。如果crash的地方內(nèi)存明顯不是consistent的,或者內(nèi)存管理信息都已被破壞,并且還是隨機(jī)出現(xiàn)的,那就比較麻煩了。當(dāng)然,祼看code打log是一個辦法,但其效率不是太高,尤其是在運(yùn)行成本高或重現(xiàn)概率低的情況下。另外,靜態(tài)檢查也是一類方法,有很多工具(lint, cppcheck, klockwork, splint, o, etc.)。但缺點是誤報很多,不適合針對性問題。另外好點的一般還要錢。最后,就是動態(tài)檢查工具。下面介紹幾個Linux平臺下主要的運(yùn)行時內(nèi)存檢查工具。絕大多數(shù)都是開源免費(fèi)且支持x86和ARM平臺的。
首先,比較常見的內(nèi)存問題有下面幾種:
· memory overrun:寫內(nèi)存越界
· double free:同一塊內(nèi)存釋放兩次
· use after free:內(nèi)存釋放后使用
· wild free:釋放內(nèi)存的參數(shù)為非法值
· access uninitialized memory:訪問未初始化內(nèi)存
· read invalid memory:讀取非法內(nèi)存,本質(zhì)上也屬于內(nèi)存越界
· memory leak:內(nèi)存泄露
· use after return:caller訪問一個指針,該指針指向callee的棧內(nèi)內(nèi)存
· stack overflow:棧溢出
針對上面的問題,主要有以下幾種方法:
1. 為了檢測內(nèi)存非法使用,需要hook內(nèi)存分配和操作函數(shù)。hook的方法可以是用C-preprocessor,也可以是在鏈接庫中直接定義(因為Glibc中的malloc/free等函數(shù)都是weak symbol),或是用LD_PRELOAD。另外,通過hook strcpy(),memmove()等函數(shù)可以檢測它們是否引起buffer overflow。
2. 為了檢查內(nèi)存的非法訪問,需要對程序的內(nèi)存進(jìn)行bookkeeping,然后截獲每次訪存操作并檢測是否合法。bookkeeping的方法大同小異,主要思想是用shadow memory來驗證某塊內(nèi)存的合法性。至于instrumentation的方法各種各樣。有run-time的,比如通過把程序運(yùn)行在虛擬機(jī)中或是通過binary translator來運(yùn)行;或是compile-time的,在編譯時就在訪存指令時就加入檢查操作。另外也可以通過在分配內(nèi)存前后加設(shè)為不可訪問的guard page,這樣可以利用硬件(MMU)來觸發(fā)SIGSEGV,從而提高速度。
3. 為了檢測棧的問題,一般在stack上設(shè)置canary,即在函數(shù)調(diào)用時在棧上寫magic number或是隨機(jī)值,然后在函數(shù)返回時檢查是否被改寫。另外可以通過mprotect()在stack的頂端設(shè)置guard page,這樣棧溢出會導(dǎo)致SIGSEGV而不至于破壞數(shù)據(jù)。
以上方法有些強(qiáng)于功能,有些勝在性能,有些則十分方便易用,總之各有千秋。以下是幾種常用工具在Linux x86_64平臺的實驗結(jié)果,注意其它平臺可能結(jié)果有差異。另外也可能由于版本過老,編譯環(huán)境差異,姿勢不對,總之各種原因造成遺漏,如有請諒解~
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 |
|
|
下面簡單介紹一下這些工具以及基本用法。更詳細(xì)用法請參見各自manual。
Memory checking tools in Glibc
Glibc中自帶了一些Heap consistency checking機(jī)制。
MALLOC_CHECK_
用mallopt()的M_CHECK_ACTION可以設(shè)置內(nèi)存檢測行為,設(shè)MALLOC_CHECK_環(huán)境變量效果也是一樣的。從Glibc 2.3.4開始,默認(rèn)為3。即打印出錯信息,stack trace和memory mapping,再退出程序。設(shè)置LIBC_FATAL_STDERR_=1可以將這些信息輸出到stderr。比如運(yùn)行以下有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)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
mcheck
mcheck是Glibc中的堆內(nèi)存一致性檢查機(jī)制。使用時只要加上頭文件:
#include <mcheck>
再在要開始檢查的地方加上:
if (mcheck(NULL) != 0) {
fprintf(stderr, "mcheck() failed\n");
exit(EXIT_FAILURE);
}
…
編譯時加-lmcheck然后運(yùn)行即可:
$ g++ -Wall -g problem.cpp -o bug -lmcheck
_FORTIFY_SOURCE
宏_FORTIFY_SOURCE提供輕量級的buffer overflow檢測。設(shè)置后會調(diào)用Glibc里帶_chk后綴的函數(shù),做一些運(yùn)行時檢查。主要檢查各種字符串緩沖區(qū)溢出和內(nèi)存操作。比如memmove, memcpy, memset, strcpy, strcat, vsprintf等。注意一些平臺上編譯時要加-O1或以上優(yōu)化。這樣就可以檢查出因為那些內(nèi)存操作函數(shù)導(dǎo)致的緩沖溢出問題:
$ 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()表示開始和結(jié)束內(nèi)存分配trace(如果檢測到結(jié)束結(jié)尾的話可以不用muntrace())。但這是簡單地記錄沒有free對應(yīng)的malloc,可能會有一些false alarm。
#include <mcheck.h>
mtrace();
// …
muntrace();
然后編譯:
$ g++ -Wall -g problem.cpp -o bug
運(yùn)行時先設(shè)輸出的log文件:
$ export MALLOC_TRACE=output.log
用mtrace命令將輸出文件變得可讀:
$ mtrace ./bug $MALLOC_TRACE
就可以得到哪些地方的內(nèi)存申請還沒有被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相比更快,并可以有效減少多線程之間的競爭,因為它會為每個線程單獨(dú)分配線程本地的Cache。這里先只關(guān)注它的內(nèi)存相關(guān)組件。通過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”
運(yùn)行的時候執(zhí)行:
$ HEAPCHECK=normal ./bug
就可以報出內(nèi)存泄露:
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生成內(nèi)存快照,然后執(zhí)行完要檢查部分后調(diào)用assert(checker.NoLeaks())。具體用法見:http://goog-perftools./doc/heap_checker.html
更詳細(xì)的信息可以用google-pprof獲得,如:
$ google-pprof ./bug "/tmp/bug.1353._main_-end.heap" --inuse_objects --lines --heapcheck --edgefraction=1e-10 --nodefraction=1e-10 --gv
關(guān)于Tcmalloc更多的配置信息可以參見:http://gperftools./svn/trunk/doc/tcmalloc.html
Valgrind
Valgrind是Valgrind core和Valgrind工具插件的集合,除了用于檢查內(nèi)存錯誤,還可以用來分析函數(shù)調(diào)用,緩存使用,多線程競爭,堆棧使用等問題。這里只關(guān)注memcheck工具,因為太常用 ,它默認(rèn)就是打開的。其原理是讓程序跑在一個虛擬機(jī)上,因此速度會慢幾十倍。好在現(xiàn)實中很多程序是IO bound的,所以很多時候沒有慢到忍無可忍的地步。好處是它不需要重新編譯目標(biāo)程序。它會通過hash表記錄每個heap block,同時通過shadow memory記錄這些內(nèi)存區(qū)域的信息。這樣就可以在每次訪存時檢查其合法性。
運(yùn)行時可根據(jù)需要加配置參數(shù),如:
$ 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檢測結(jié)果:
==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)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
access uninitialized memory結(jié)果:
==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這類問題比較難搞是因為出錯的時候往往不是第一現(xiàn)場。用Valgrind就比較容易抓到第一現(xiàn)場。如果一個對象A被釋放后,同一塊內(nèi)存再次被申請為對象B,但程序中還是通過指向?qū)ο驛的dangling pointer進(jìn)行訪問,會覆蓋已有數(shù)據(jù)或者讀出錯誤數(shù)據(jù)。但這種情況Valgrind檢查不出來,因為Valgrind不會做語義上的分析。但是Valgrind可以配置內(nèi)存分配策略,通過設(shè)置空閑內(nèi)存隊列大小和優(yōu)先級讓被釋放的內(nèi)存不馬上被重用。從而增大抓到此類問題的概率。
對于棧中內(nèi)存,Memcheck只會做未初始化數(shù)據(jù)訪問的檢測,而不會做?;蛉?jǐn)?shù)組中的越界檢測。這是由SGCheck來完成的,它與memcheck功能互補(bǔ)。使用SGCheck只需在valgrind后加上–tool=exp-sgcheck參數(shù)即可。
另外memcheck還提供一系列參數(shù)可以調(diào)整檢測策略,具體可參見Valgrind User Manual或者http:///docs/manual/mc-manual.html
Address sanitizer (ASan)
早先是LLVM中的特性,后被加入GCC 4.8。在GCC 4.9后加入對ARM平臺的支持。因此用時不需要第三方庫,通過在編譯時指定flag即可打開開關(guān)。它是 Mudflap的替代品(Mudflap從GCC 4.9開始不再支持,指定了也不做事)。ASan在編譯時在訪存操作中插入額外指令,同時通過Shadow memory來記錄和檢測內(nèi)存的有效性。slowdown官方稱為2x左右。
使用時只要在CFLAGS中加上如下flag。注意如果鏈接so,只有可執(zhí)行文件需要加flag。
$ g++ -Wall -g problem.cpp -o bug -fsanitize=address -fno-omit-frame-pointer
直接運(yùn)行,檢測出錯誤時會報出類似以下錯誤:
==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)
檢測一些特定問題需要加上專門的選項,比如要檢查訪問指向已被釋放的??臻g需要加上:
ASAN_OPTIONS=detect_stack_use_after_return=1
如果要檢測memory leak需要加上:
ASAN_OPTIONS=detect_leaks=1
各種參數(shù)配置請參見: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不能檢測讀未初始化內(nèi)存,而這MemorySanitizer(MSan)能做到。它包含compiler instrumentation模塊和run-time的庫。目前只支持Linux x86_64平臺。使用時需在編譯選項加-fsanitize=memory -fPIE -pie,為了得到更詳細(xì)的信息,最好加上-fno-omit-frame-pointer和-fsanitize-memory-track-origins。它實現(xiàn)了Valgrind的部分功能,但由于使用了compile-time instrumentation,所以速度更快。可惜目前只在LLVM上有,在GCC上還沒有,暫且略過。
Memwatch
Memwatch是一個輕量級的內(nèi)存問題檢測工具。主要用于檢測內(nèi)存分配釋放相關(guān)問題及內(nèi)存越界訪問問題。通過C preprocessor,Memwatch替換所有 ANSI C的內(nèi)存分配 函數(shù),從而記錄分配行為。注意它不保證是線程安全的。效率上,大塊分配不受影響,小塊分配會受影響,因此它沒法使用原分配函數(shù)中的memory pool。最壞情況下會有3-5x的slowdown。它可以比較方便地模擬內(nèi)存受限情況。對于未初始化內(nèi)存訪問,和已釋放內(nèi)存訪問,Memwatch會poison相應(yīng)內(nèi)存(分配出來寫0xFE,釋放內(nèi)存寫0xFD)從而在出錯時方便調(diào)試。
使用時需要修改源碼。該庫需要單獨(dú)下載:
http://www./sourcecode/memwatch/
然后在要檢查的代碼中包含頭文件:
#include "memwatch.h"
然后加下面宏編譯:
$ gcc -DMEMWATCH -DMW_STDIO test.c memwatch.c -o test
默認(rèn)結(jié)果輸出在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的讀和寫。它利用硬件來抓住越界訪問的指令。其原理是為每一次內(nèi)存申請額外申請一個page或一組page,然后把這些buffer范圍外的page設(shè)為不可讀寫。這樣,如果程序訪問這些區(qū)域,由于頁表中這個額外page的權(quán)限是不可讀寫,會產(chǎn)生段錯誤。那些被free()釋放的內(nèi)存也會被設(shè)為不可訪問,因此訪問也會產(chǎn)生段錯誤。因為讀寫權(quán)限以頁為單位,所以如果多的頁放在申請內(nèi)存區(qū)域后,可防止overflow。如果要防止underflow,就得用環(huán)境變量EF_PROTECT_BELOW在區(qū)域前加保護(hù)頁。因為Electric Fence至少需要丙個頁來滿足內(nèi)存分配申請,因此內(nèi)存使用會非常大,好處是它利用了硬件來捕獲非法訪問,因此速度快。也算是空間換時間吧。
目前支持Window, Linux平臺,語言支持C/C++。限制包括無法檢測使用未初始化內(nèi)存,memory leak等。同時它不是線程安全的。Ubuntu上懶得編譯可以安裝現(xiàn)成的:
$ 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這些環(huán)境變量都是用來控制其行為的??梢詤⒁妋anual:http://linux./man/3/efence
比如memory overrun和double free就可以得到如下結(jié)果:
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中打出詳細(xì)信息,但如果運(yùn)行前打開了coredump:
$ ulimit -c unlimited
就可以gdb打開coredump來分析了:
$ gdb ./bug -c core
注意因為多數(shù)平臺在分配時遇到block size不是word size整數(shù)倍時會通過加padding byte進(jìn)行word alignment。如果是在padded area中出現(xiàn)overrun則無法檢測。這里可以通過在程序中設(shè)置EN_ALIGNMENT=1來防止byte padding,從而更容易檢測off by one的問題。
DUMA(http://duma./)從Electric Fence中fork出來并加入一些其它特性,比如leak detection,Windows支持等。
Dmalloc
比較經(jīng)典的內(nèi)存檢測工具,雖然N年沒更新了。dmalloc通過在分配區(qū)域增加padding magic number的做法來檢測非法訪問,因此它能夠檢測到問題但不能檢測出哪條指令出的錯。Dmalloc只能檢測越界寫,但不能檢測越界讀。另外,Dmalloc只檢測堆上用malloc系函數(shù)(而不是sbrk()或mmap())分配的內(nèi)存,而無法對棧內(nèi)存和靜態(tài)內(nèi)存進(jìn)行檢測。 本質(zhì)上它也是通過hook malloc(), realloc(), calloc(),free()等內(nèi)存管理函數(shù),還有strcat(), strcpy()等內(nèi)存操作函數(shù),來檢測內(nèi)存問題。它支持x86, ARM平臺,語言上支持C/C++,并且支持多線程。
使用時可以先從官網(wǎng)下載源碼包(http:///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的配置選項可以通過設(shè)置環(huán)境變量DMALLOC_OPTIONS來實現(xiàn),例如:
$ 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:///docs/latest/online/dmalloc_26.html
http:///docs/latest/online/dmalloc_27.html
也可以用dmalloc這個命令來設(shè)置。直接dmalloc -v可用于查看當(dāng)前設(shè)置。
發(fā)生錯誤時會給出類似以下輸出:
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還提供一些函數(shù),如dmalloc_mark(),dmalloc_log_changed()和dmalloc_log_unfreed()等來打印內(nèi)存信息和分析內(nèi)存變化:
http:///docs/5.3.0/online/dmalloc_13.html
Dr. Memory
重量級內(nèi)存監(jiān)測工具之一,用于檢測如未初始化內(nèi)存訪問,越界訪問,已釋放內(nèi)存訪問,double free,memory leak以及Windows上的handle leak, GDI API usage error等。它支持Windows, Linux和Mac操作系統(tǒng), IA-32和AMD64平臺,和其它基于binary instrumentation的工具一樣,它不需要改目標(biāo)程序的binary。有個缺點是目前只針對x86上的32位程序。貌似目前正在往ARM上port。其優(yōu)點是對程序的正常執(zhí)行影響小,和Valgrind相比,性能更好。官網(wǎng)為http://www./。Dr. Memory基于DynamioRIO Binary Translator。原始代碼不會直接運(yùn)行,而是會經(jīng)過translation后生成code cache,這些code cache會調(diào)用shared instrumentation來做內(nèi)存檢測。
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啟動目標(biāo)程序:
\$ drmemory – ./bug
更多用法參見 drmemory -help或http:///docs/page_options.html。
像遇到double-free和heap overflow問題的話就會給出類似下面結(jié)果:
~~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)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
Stack protection
前面的工具大多用于堆內(nèi)存檢錯,對于棧內(nèi)存GCC本身提供了一些檢錯機(jī)制。加上-fstack-protector后,GCC會多加指令來檢查buffer/stack overflow。原理是為函數(shù)加guard variable。在函數(shù)進(jìn)入時初始化,函數(shù)退出時檢查。相關(guān)的flag有-fstack-protector-strong -fstack-protector -fstack-protector-all等。使用例子:
$ g++ -Wall -O2 -U_FORTIFY_SOURCE -fstack-protector-all problem.cpp -o bug
運(yùn)行時會檢測到stack overflow:
*** stack smashing detected ***: ./bug terminated
Aborted (core dumped)
對于線程的棧可以參考pthread_attr_setguardsize()。
Rational purify & Insure++
Rational purity是IBM的商業(yè)化產(chǎn)品,要收費(fèi),所以木有用過,精神上支持。和Valgrind很像,也基于binary instrumentation,適用于不同平臺。另一個工具Insure++基于compile-time和binary instrumentation,可以檢測use-after-free,out-of-bounds,wild free和memory leak等內(nèi)存問題。但也是要收費(fèi)的,也精神上支持。。。。。。
大體來說,遇到詭異的內(nèi)存問題,先可以試下Glibc和GCC里自帶的檢測機(jī)制,因為enable起來方便。如果檢測不出來,那如果toolchain版本較新且有編譯環(huán)境,可以先嘗試ASan,因為其功能強(qiáng)大,且效率高。接下來,如果程序是I/O bound或slowdown可以接受,可以用Valgrind和Dr.Memory。它們功能強(qiáng)大且無需重新編譯,但速度較慢,且后者不支持64位程序和ARM平臺。然后可以根據(jù)實際情況和具體需要考慮Memwatch,Dmalloc和Electric Fence等工具。
|