內存異常經常導致程序出現莫名其妙的錯誤,往往很難查證,本文介紹在linux下的各種常見內存異常的查證工具和方法。
1 訪問空指針/未初始化指針/重復釋放內存
對于像訪問空指針、未初始化指針(非法地址),重復釋放內存等內存異常,linux默認會拋異常。
比如下面代碼有空指針訪問,編譯運行后會coredump
int?main()
{int?*p=0;*p=6;return?0;
}
對于此類問題,我們只要在gcc編譯程序時加入-g選項,同時在運行時能夠生成coredump文件,利用gdb就可以定位到具體的問題代碼行。
1.1 開啟coredump
**ulimit -c ?unlimited** //unlimited表示不限制coredump文件大小,也可指定一個具體值來限制文件最大長度。
1.2 定制core文件名
默認的coredump文件名為core,如果想自己定制core文件名,可以運行如下命令:
**echo "./core-%e-%p-%t" > /proc/sys/kernel/core_pattern**?
可以在core_pattern模板中使用變量還很多,見下面的列表:
%% 單個%字符
%p 所dump進程的進程ID
%u 所dump進程的實際用戶ID
%g 所dump進程的實際組ID
%s 導致本次core dump的信號
%t core dump的時間 (由1970年1月1日計起的秒數)
%h 主機名
%e 程序文件名
1.3 使用gdb定位代碼行
通過gdb即可定位出錯代碼行
root@ubuntu:/home/zte/test#?gcc?null.cc?-g root@ubuntu:/home/zte/test#?./a.out? Segmentation?fault?(core?dumped) root@ubuntu:/home/zte/test#?gdb?a.out?core ....... Core?was?generated?by?`./null'. Program?terminated?with?signal?SIGSEGV,?Segmentation?fault. #0??0x00000000004004fd?in?main?()?at?null.cc:4 4????*p=6;
2、函數棧溢出
局部變量的寫越界可能會破壞函數棧導致程序出現各種異常行為,但是OS默認不會在越界的第一現場coredump,因此導致問題查證非常困難。
幸運的是我們可以通過gcc的編譯選項-fstack-protector 和 -fstack-protector-all在函數棧被破壞的函數返回時拋異常,從而可以很方便地定位問題所在函數。
代碼示例
int?main()
{int?a=5;int?*p=&a;p[3]=6;return?0;
}
上面代碼會破壞函數棧,如果我們用gcc直接編譯運行,不會拋異常。但是加了編譯參數-fstack-protector 和 -fstack-protector-all后,再運行就會拋異常。下面是具體命令執行結果。
root@ubuntu:/home/zte/test#?gcc?t.c root@ubuntu:/home/zte/test#?./a.out? root@ubuntu:/home/zte/test#?gcc?t.c?-fstack-protector?-fstack-protector-all?-g root@ubuntu:/home/zte/test#?./a.out? ***?stack?smashing?detected?***:?./a.out?terminated Aborted?(core?dumped) ``` 可以進一步用gdb的bt命令定位出問題的函數。 ``` root@ubuntu:/home/zte/test#?gdb?a.out?core 。。。。。。。。 Core?was?generated?by?`./a.out'. Program?terminated?with?signal?SIGABRT,?Aborted. #0??0x00007f6bcfab5c37?in?__GI_raise?(sig=sig@entry=6)?at?../nptl/sysdeps/unix/sysv/linux/raise.c:56 56../nptl/sysdeps/unix/sysv/linux/raise.c:?No?such?file?or?directory. (gdb)?bt #0??0x00007f6bcfab5c37?in?__GI_raise?(sig=sig@entry=6)?at?../nptl/sysdeps/unix/sysv/linux/raise.c:56 #1??0x00007f6bcfab9028?in?__GI_abort?()?at?abort.c:89 #2??0x00007f6bcfaf22a4?in?__libc_message?(do_abort=do_abort@entry=1,?fmt=fmt@entry=0x7f6bcfc01d70?"***?%s?***:?%s?terminated\n")at?../sysdeps/posix/libc_fatal.c:175 #3??0x00007f6bcfb8d83c?in?__GI___fortify_fail?(msg=<optimized?out>,?msg@entry=0x7f6bcfc01d58?"stack?smashing?detected")at?fortify_fail.c:38 #4??0x00007f6bcfb8d7e0?in?__stack_chk_fail?()?at?stack_chk_fail.c:28 #5??0x00000000004005aa?in?main?()?at?t.c:7 (gdb)?q
3 越界讀寫動態分配內存/讀寫已釋放動態分配內存
動態分配內存讀寫越界、讀寫已釋放動態分配內存系統往往不會拋異常,我們可以使用electric-fence來使得讀寫越界內存/已釋放內存后立刻拋異常,加速問題定位。
3.1 安裝Electric fence
sudo apt-get install electric-fence
3.2 使用Electric fence
下面是越界寫代碼
#include?<stdlib.h>
int?main()
{int?*p?=?(int*)malloc(sizeof(int));p[1]?=?6;return?0;
}
如果使用gcc直接編譯運行,不會拋異常。
我們可以加上參數 -lefence -g編譯后運行,就會拋異常。通過gdb的bt打印即可定位到問題代碼行。
root@ubuntu:/home/zte/test#?gcc?malloc_read_free.cc?-lefence?-g root@ubuntu:/home/zte/test#?./a.out?Electric?Fence?2.2?Copyright?(C)?1987-1999?Bruce?Perens?<bruce@perens.com> Segmentation?fault?(core?dumped)autogen.sh
4 內存泄漏
C/C++程序經常被內存泄漏問題困擾,本文介紹使用gperftools來快速定位內存泄漏問題。
4.1 安裝gperftools工具
4.1.1 安裝automake
sudo apt-get install automake
4.1.2 編譯安裝libunwind
從https://github.com/libunwind/libunwind/releases下載最新版本的libunwind源碼包
解壓到/usr/local/src目錄
cd 解壓源碼目錄
./autogen.sh
./configure
make -j6
make install
4.1.3 編譯安裝gperftools
從https://github.com/gperftools/gperftools/releases下載最新版本的gperftools源碼包
解壓到/usr/local/src目錄
cd 解壓源碼目錄
./autogen.sh
./configure
make -j6
make install
4.2 內存泄漏檢測
下面是一段簡單的內存泄漏源碼
int?main()
{int?*p?=?(int*)malloc(sizeof(int));return?0;
}
編譯代碼、運行工具檢察內存泄漏,注意設置下
root@ubuntu:/home/zte/#?gcc?leak.cc?-g root@ubuntu:/home/zte/#?env?HEAPCHECK=normal?LD_PRELOAD=/usr/local/lib/libtcmalloc.so?./a.out? WARNING:?Perftools?heap?leak?checker?is?active?--?Performance?may?suffer Have?memory?regions?w/o?callers:?might?report?false?leaks Leak?check?_main_?detected?leaks?of?4?bytes?in?1?objects The?1?largest?leaks: ***?WARNING:?Cannot?convert?addresses?to?symbols?in?output?below. ***?Reason:?Cannot?run?'pprof'?(is?PPROF_PATH?set?correctly?) ***?If?you?cannot?fix?this,?try?running?pprof?directly. Leak?of?4?bytes?in?1?objects?allocated?from: @?40053f? @?7f334da06f45? @?400469? If?the?preceding?stack?traces?are?not?enough?to?find?the?leaks,?try?running?THIS?shell?command: pprof?./a.out?"/tmp/a.out.8497._main_-end.heap"?--inuse_objects?--lines?--heapcheck??--edgefraction=1e-10?--nodefraction=1e-10?--gv If?you?are?still?puzzled?about?why?the?leaks?are?there,?try?rerunning?this?program?with?HEAP_CHECK_TEST_POINTER_ALIGNMENT=1?and/or?with?HEAP_CHECK_MAX_POINTER_OFFSET=-1 If?the?leak?report?occurs?in?a?small?fraction?of?runs,?try?running?with?TCMALLOC_MAX_FREE_QUEUE_SIZE?of?few?hundred?MB?or?with?TCMALLOC_RECLAIM_MEMORY=false,?it?might?help?find?leaks?more?repeatabl Exiting?with?error?code?(instead?of?crashing)?because?of?whole-program?memory?leaks
上面的關鍵的輸入信息是:
Leak of 4 bytes in 1 objects allocated from:
@ 40053f ? //內存分配的指令地址
@ 7f334da06f45?
@ 400469?
由于工具沒有直接輸出問題代碼行,我們通過反匯編來定位代碼行:
objdump -S a.out ?//反匯編程序
截取匯編代碼如下:
int?main()
{40052d:55???????????????????push???%rbp40052e:48?89?e5?????????????mov????%rsp,%rbp400531:48?83?ec?10??????????sub????$0x10,%rspint?*p?=?(int*)malloc(sizeof(int));400535:bf?04?00?00?00???????mov????$0x4,%edi40053a:e8?f1?fe?ff?ff???????callq??400430?<malloc@plt>40053f:48?89?45?f8??????????mov????%rax,-0x8(%rbp)return?0;400543:b8?00?00?00?00???????mov????$0x0,%eax
我們注意到40053f就是對應代碼行int *p = (int*)malloc(sizeof(int));
至此,內存泄漏的元兇被揪出來了,呵呵。
轉載于:https://blog.51cto.com/wulingdong/1983537