Linux中的常用內存問題檢測工具

原文地址: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等工具。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章