內(nèi)存異常經(jīng)常導(dǎo)致程序出現(xiàn)莫名其妙的錯(cuò)誤,往往很難查證,本文介紹在linux下的各種常見內(nèi)存異常的查證工具和方法。
創(chuàng)新互聯(lián)公司長(zhǎng)期為千余家客戶提供的網(wǎng)站建設(shè)服務(wù),團(tuán)隊(duì)從業(yè)經(jīng)驗(yàn)10年,關(guān)注不同地域、不同群體,并針對(duì)不同對(duì)象提供差異化的產(chǎn)品和服務(wù);打造開放共贏平臺(tái),與合作伙伴共同營(yíng)造健康的互聯(lián)網(wǎng)生態(tài)環(huán)境。為察雅企業(yè)提供專業(yè)的成都做網(wǎng)站、成都網(wǎng)站制作,察雅網(wǎng)站改版等技術(shù)服務(wù)。擁有十多年豐富建站經(jīng)驗(yàn)和眾多成功案例,為您定制開發(fā)。1 訪問空指針/未初始化指針/重復(fù)釋放內(nèi)存
對(duì)于像訪問空指針、未初始化指針(非法地址),重復(fù)釋放內(nèi)存等內(nèi)存異常,linux默認(rèn)會(huì)拋異常。
比如下面代碼有空指針訪問,編譯運(yùn)行后會(huì)coredump
int main() { int *p=0; *p=6; return 0; }
對(duì)于此類問題,我們只要在gcc編譯程序時(shí)加入-g選項(xiàng),同時(shí)在運(yùn)行時(shí)能夠生成coredump文件,利用gdb就可以定位到具體的問題代碼行。
1.1 開啟coredump
**ulimit -c unlimited** //unlimited表示不限制coredump文件大小,也可指定一個(gè)具體值來(lái)限制文件大長(zhǎng)度。
1.2 定制core文件名
默認(rèn)的coredump文件名為core,如果想自己定制core文件名,可以運(yùn)行如下命令:
**echo "./core-%e-%p-%t" > /proc/sys/kernel/core_pattern**
可以在core_pattern模板中使用變量還很多,見下面的列表:
%% 單個(gè)%字符
%p 所dump進(jìn)程的進(jìn)程ID
%u 所dump進(jìn)程的實(shí)際用戶ID
%g 所dump進(jìn)程的實(shí)際組ID
%s 導(dǎo)致本次core dump的信號(hào)
%t core dump的時(shí)間 (由1970年1月1日計(jì)起的秒數(shù))
%h 主機(jī)名
%e 程序文件名
1.3 使用gdb定位代碼行
通過gdb即可定位出錯(cuò)代碼行
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、函數(shù)棧溢出
局部變量的寫越界可能會(huì)破壞函數(shù)棧導(dǎo)致程序出現(xiàn)各種異常行為,但是OS默認(rèn)不會(huì)在越界的第一現(xiàn)場(chǎng)coredump,因此導(dǎo)致問題查證非常困難。
幸運(yùn)的是我們可以通過gcc的編譯選項(xiàng)-fstack-protector 和 -fstack-protector-all在函數(shù)棧被破壞的函數(shù)返回時(shí)拋異常,從而可以很方便地定位問題所在函數(shù)。
代碼示例
int main() { int a=5; int *p=&a; p[3]=6; return 0; }
上面代碼會(huì)破壞函數(shù)棧,如果我們用gcc直接編譯運(yùn)行,不會(huì)拋異常。但是加了編譯參數(shù)-fstack-protector 和 -fstack-protector-all后,再運(yùn)行就會(huì)拋異常。下面是具體命令執(zhí)行結(jié)果。
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) ``` 可以進(jìn)一步用gdb的bt命令定位出問題的函數(shù)。 ``` 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=, 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 越界讀寫動(dòng)態(tài)分配內(nèi)存/讀寫已釋放動(dòng)態(tài)分配內(nèi)存
動(dòng)態(tài)分配內(nèi)存讀寫越界、讀寫已釋放動(dòng)態(tài)分配內(nèi)存系統(tǒng)往往不會(huì)拋異常,我們可以使用electric-fence來(lái)使得讀寫越界內(nèi)存/已釋放內(nèi)存后立刻拋異常,加速問題定位。
3.1 安裝Electric fence
sudo apt-get install electric-fence
3.2 使用Electric fence
下面是越界寫代碼
#includeint main() { int *p = (int*)malloc(sizeof(int)); p[1] = 6; return 0; }
如果使用gcc直接編譯運(yùn)行,不會(huì)拋異常。
我們可以加上參數(shù) -lefence -g編譯后運(yùn)行,就會(huì)拋異常。通過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 PerensSegmentation fault (core dumped)autogen.sh
4 內(nèi)存泄漏
C/C++程序經(jīng)常被內(nèi)存泄漏問題困擾,本文介紹使用gperftools來(lái)快速定位內(nèi)存泄漏問題。
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 內(nèi)存泄漏檢測(cè)
下面是一段簡(jiǎn)單的內(nèi)存泄漏源碼
int main() { int *p = (int*)malloc(sizeof(int)); return 0; }
編譯代碼、運(yùn)行工具檢察內(nèi)存泄漏,注意設(shè)置下
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
上面的關(guān)鍵的輸入信息是:
Leak of 4 bytes in 1 objects allocated from:
@ 40053f //內(nèi)存分配的指令地址
@ 7f334da06f45
@ 400469
由于工具沒有直接輸出問題代碼行,我們通過反匯編來(lái)定位代碼行:
objdump -S a.out //反匯編程序
截取匯編代碼如下:
int main() { 40052d:55 push %rbp 40052e:48 89 e5 mov %rsp,%rbp 400531:48 83 ec 10 sub $0x10,%rsp int *p = (int*)malloc(sizeof(int)); 400535:bf 04 00 00 00 mov $0x4,%edi 40053a:e8 f1 fe ff ff callq 40043040053f:48 89 45 f8 mov %rax,-0x8(%rbp) return 0; 400543:b8 00 00 00 00 mov $0x0,%eax
我們注意到40053f就是對(duì)應(yīng)代碼行int *p = (int*)malloc(sizeof(int));
至此,內(nèi)存泄漏的元兇被揪出來(lái)了,呵呵。
另外有需要云服務(wù)器可以了解下創(chuàng)新互聯(lián)scvps.cn,海內(nèi)外云服務(wù)器15元起步,三天無(wú)理由+7*72小時(shí)售后在線,公司持有idc許可證,提供“云服務(wù)器、裸金屬服務(wù)器、高防服務(wù)器、香港服務(wù)器、美國(guó)服務(wù)器、虛擬主機(jī)、免備案服務(wù)器”等云主機(jī)租用服務(wù)以及企業(yè)上云的綜合解決方案,具有“安全穩(wěn)定、簡(jiǎn)單易用、服務(wù)可用性高、性價(jià)比高”等特點(diǎn)與優(yōu)勢(shì),專為企業(yè)上云打造定制,能夠滿足用戶豐富、多元化的應(yīng)用場(chǎng)景需求。