Linux下內(nèi)存問(wèn)題排查利器
大家好,今天給大家分享一下Linux下如何排除內(nèi)存泄漏問(wèn)題。工作中,作為一個(gè)程序員,內(nèi)存問(wèn)題是我們經(jīng)常遇到也是容易引起程序崩潰的常見(jiàn)問(wèn)題,嚴(yán)重的后果會(huì)直接導(dǎo)致你的程序宕機(jī)從而帶來(lái)災(zāi)難性的后果。
1. 內(nèi)存泄漏
內(nèi)存泄漏(Memory Leak)是指程序中已動(dòng)態(tài)分配的堆內(nèi)存由于某種原因程序未釋放或無(wú)法釋放,造成系統(tǒng)內(nèi)存的浪費(fèi),導(dǎo)致程序運(yùn)行速度減慢甚至系統(tǒng)崩潰等嚴(yán)重后果。
特點(diǎn)
隱蔽性 因?yàn)閮?nèi)存泄漏的產(chǎn)生原因是內(nèi)存塊未被釋放,屬于遺漏型缺陷而不是過(guò)錯(cuò)型缺陷
積累性 內(nèi)存泄漏通常不會(huì)直接產(chǎn)生可觀察的錯(cuò)誤癥狀,而是逐漸積累,降低系統(tǒng)整體性能,極端的情況下可能使系統(tǒng)崩潰。最直觀的問(wèn)題就是為什么我們的程序開(kāi)始運(yùn)行好好的,過(guò)段時(shí)間就異常退出。
內(nèi)存泄漏并非指內(nèi)存在物理上的消失,而是應(yīng)用程序分配某段內(nèi)存后,由于使用錯(cuò)誤,導(dǎo)致在釋放該段內(nèi)存之前就失去了對(duì)該段內(nèi)存的控制,從而造成了內(nèi)存未釋放而浪費(fèi)掉。
產(chǎn)生的原因
我們?cè)谶M(jìn)行程序開(kāi)發(fā)的過(guò)程使用動(dòng)態(tài)存儲(chǔ)變量時(shí),不可避免地面對(duì)內(nèi)存管理的問(wèn)題。程序中動(dòng)態(tài)分配的存儲(chǔ)空間,在程序執(zhí)行完畢后需要進(jìn)行釋放。沒(méi)有釋放動(dòng)態(tài)分配的存儲(chǔ)空間而造成內(nèi)存泄漏,是使用動(dòng)態(tài)存儲(chǔ)變量的主要問(wèn)題。一般情況下,作為開(kāi)發(fā)人員會(huì)經(jīng)常使用系統(tǒng)提供的內(nèi)存管理基本函數(shù),如
malloc、realloc、calloc、free等,完成動(dòng)態(tài)存儲(chǔ)變量存儲(chǔ)空間的分配和釋放。但是,當(dāng)開(kāi)發(fā)程序中使用動(dòng)態(tài)存儲(chǔ)變量較多和頻繁使用函數(shù)調(diào)用時(shí),就會(huì)經(jīng)常發(fā)生內(nèi)存管理錯(cuò)誤。
2. 如何排查內(nèi)存泄漏
我們平時(shí)開(kāi)發(fā)過(guò)程中不可避免的會(huì)遇到內(nèi)存泄漏問(wèn)題,這是常見(jiàn)的問(wèn)題。既然發(fā)生了內(nèi)存泄漏,我們就要排查內(nèi)存泄漏的問(wèn)題。想必大家也經(jīng)常會(huì)用到以下排查內(nèi)存問(wèn)題的工具,如下:
memwatch mtrace dmalloc ccmalloc valgrind debug_new
今天木榮不是介紹上面的排查工具,而是向大家介紹另一個(gè)內(nèi)存泄漏排查工具:AddressSanitizer(ASan)。它支持 Linux、OS、Android等多種平臺(tái),不止可以檢測(cè)內(nèi)存泄漏,它是一個(gè)內(nèi)存錯(cuò)誤檢測(cè)工具,可以檢測(cè)很多常見(jiàn)的內(nèi)存問(wèn)題。
常見(jiàn)的內(nèi)存問(wèn)題檢測(cè):
內(nèi)存泄漏 越界訪問(wèn) 使用了釋放的內(nèi)存
3. AddressSanitizer(ASan)工具
Address Sanitizer(ASan) 是一個(gè)快速的內(nèi)存錯(cuò)誤檢測(cè)工具。它非常快,只拖慢程序兩倍左右(比起Valgrind快多了)。它包括一個(gè)編譯器instrumentation模塊和一個(gè)提供malloc()/free()替代項(xiàng)的運(yùn)行時(shí)庫(kù)。從gcc 4.8開(kāi)始,AddressSanitizer成為gcc的一部分。當(dāng)然,要獲得更好的體驗(yàn),最好使用4.9及以上版本,因?yàn)間cc 4.8的AddressSanitizer還不完善,最大的缺點(diǎn)是沒(méi)有符號(hào)信息。
使用方法:
用-fsanitize=address選項(xiàng)編譯和鏈接你的程序。 用-fno-omit-frame-pointer編譯,以得到更容易理解stack trace。 可選擇-O1或者更高的優(yōu)化級(jí)別編譯
gcc -fsanitize=address -o main -g main.c
內(nèi)存泄漏
#include <stdlib.h>
void Fun()
{
char *pM = malloc(10);
}
int main(int argc, char *argv[])
{
Fun();
return 0;
}
編譯輸出

內(nèi)存越界
堆棧內(nèi)存越界
#include <stdlib.h>
#include <string.h>
void Fun()
{
char *pM = malloc(10);
}
int main(int argc, char *argv[])
{
//Fun();
int *array = malloc(10*sizeof(int));
if(array)
{
memset(array, 0, 10*sizeof(int));
}
int res = array[10];
free(array);
return 0;
}
運(yùn)行輸出
ubuntu@ubuntu:~/workspace_ex/Linux/ASan$ gcc -fsanitize=address -o main -g main.c
ubuntu@ubuntu:~/workspace_ex/Linux/ASan$ ls
main main.c
ubuntu@ubuntu:~/workspace_ex/Linux/ASan$ ./main
=================================================================
==3234==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x60400000dff8 at pc 0x000000400854 bp 0x7ffccc9253d0 sp 0x7ffccc9253c0
READ of size 4 at 0x60400000dff8 thread T0
#0 0x400853 in main /home/ubuntu/workspace_ex/Linux/ASan/main.c:16
#1 0x7fafc87a883f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2083f)
#2 0x4006f8 in _start (/home/ubuntu/workspace_ex/Linux/ASan/main+0x4006f8)
0x60400000dff8 is located 0 bytes to the right of 40-byte region [0x60400000dfd0,0x60400000dff8)
allocated by thread T0 here:
#0 0x7fafc8bea602 in malloc (/usr/lib/x86_64-linux-gnu/libasan.so.2+0x98602)
#1 0x4007f7 in main /home/ubuntu/workspace_ex/Linux/ASan/main.c:11
#2 0x7fafc87a883f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2083f)
SUMMARY: AddressSanitizer: heap-buffer-overflow /home/ubuntu/workspace_ex/Linux/ASan/main.c:16 main
Shadow bytes around the buggy address:
0x0c087fff9ba0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c087fff9bb0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c087fff9bc0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c087fff9bd0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c087fff9be0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
=>0x0c087fff9bf0: fa fa fa fa fa fa fa fa fa fa 00 00 00 00 00[fa]
0x0c087fff9c00: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c087fff9c10: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c087fff9c20: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c087fff9c30: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c087fff9c40: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Heap right redzone: fb
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack partial redzone: f4
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
==3234==ABORTING
全局內(nèi)存越界
#include <stdlib.h>
#include <string.h>
int g_nArray[10] = {0};
void Fun()
{
char *pM = malloc(10);
}
int main(int argc, char *argv[])
{
//Fun();
/*
int *array = malloc(10*sizeof(int));
if(array)
{
memset(array, 0, 10*sizeof(int));
}
int res = array[10];
free(array);
*/
int nIndex = g_nArray[10];
return 0;
}
運(yùn)行輸出
ubuntu@ubuntu:~/workspace_ex/Linux/ASan$ gcc -fsanitize=address -o main -g main.c
ubuntu@ubuntu:~/workspace_ex/Linux/ASan$ ls
main main.c
ubuntu@ubuntu:~/workspace_ex/Linux/ASan$ ./main
=================================================================
==4196==ERROR: AddressSanitizer: global-buffer-overflow on address 0x000000601128 at pc 0x00000040083e bp 0x7ffc8a332540 sp 0x7ffc8a332530
READ of size 4 at 0x000000601128 thread T0
#0 0x40083d in main /home/ubuntu/workspace_ex/Linux/ASan/main.c:26
#1 0x7fda216a583f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2083f)
#2 0x400718 in _start (/home/ubuntu/workspace_ex/Linux/ASan/main+0x400718)
0x000000601128 is located 0 bytes to the right of global variable 'g_nArray' defined in 'main.c:4:5' (0x601100) of size 40
SUMMARY: AddressSanitizer: global-buffer-overflow /home/ubuntu/workspace_ex/Linux/ASan/main.c:26 main
Shadow bytes around the buggy address:
0x0000800b81d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0000800b81e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0000800b81f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0000800b8200: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0000800b8210: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x0000800b8220: 00 00 00 00 00[f9]f9 f9 f9 f9 f9 f9 00 00 00 00
0x0000800b8230: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0000800b8240: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0000800b8250: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0000800b8260: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0000800b8270: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Heap right redzone: fb
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack partial redzone: f4
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
==4196==ABORTING
ubuntu@ubuntu:~/workspace_ex/Linux/ASan$
使用釋放的內(nèi)存
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
int g_nArray[10] = {0};
int *p;
void Fun()
{
char *pM = malloc(10);
}
void Fun1()
{
int nVar = 0;
p = &nVar;
}
int main(int argc, char *argv[])
{
//Fun();
int *array = malloc(10*sizeof(int));
if(array)
{
memset(array, 0, 10*sizeof(int));
}
int res = array[10];
free(array);
array[0] = 1;
return 0;
}
運(yùn)行輸出
ubuntu@ubuntu:~/workspace_ex/Linux/ASan$ gcc -fsanitize=address -o main -g main.c
ubuntu@ubuntu:~/workspace_ex/Linux/ASan$ ./main
=================================================================
==4954==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x60400000dff8 at pc 0x000000400b68 bp 0x7ffefbe3b170 sp 0x7ffefbe3b160
READ of size 4 at 0x60400000dff8 thread T0
#0 0x400b67 in main /home/ubuntu/workspace_ex/Linux/ASan/main.c:28
#1 0x7f1bbb78983f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2083f)
#2 0x400928 in _start (/home/ubuntu/workspace_ex/Linux/ASan/main+0x400928)
0x60400000dff8 is located 0 bytes to the right of 40-byte region [0x60400000dfd0,0x60400000dff8)
allocated by thread T0 here:
#0 0x7f1bbbbcb602 in malloc (/usr/lib/x86_64-linux-gnu/libasan.so.2+0x98602)
#1 0x400b0b in main /home/ubuntu/workspace_ex/Linux/ASan/main.c:23
#2 0x7f1bbb78983f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2083f)
SUMMARY: AddressSanitizer: heap-buffer-overflow /home/ubuntu/workspace_ex/Linux/ASan/main.c:28 main
Shadow bytes around the buggy address:
0x0c087fff9ba0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c087fff9bb0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c087fff9bc0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c087fff9bd0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c087fff9be0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
=>0x0c087fff9bf0: fa fa fa fa fa fa fa fa fa fa 00 00 00 00 00[fa]
0x0c087fff9c00: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c087fff9c10: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c087fff9c20: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c087fff9c30: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c087fff9c40: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Heap right redzone: fb
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack partial redzone: f4
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
==4954==ABORTING
ubuntu@ubuntu:~/workspace_ex/Linux/ASan$
AddressSanitizer能檢測(cè)的錯(cuò)誤類(lèi)型
| 錯(cuò)誤類(lèi)型 | 錯(cuò)誤描述 |
|---|---|
| (heap) Use after free | 訪問(wèn)堆上已被釋放的內(nèi)存 |
| Heap buffer overflow | 堆上緩沖區(qū)訪問(wèn)溢出 |
| Stack buffer overflow | 棧上緩沖區(qū)訪問(wèn)溢出 |
| Global buffer overflow | 全局緩沖區(qū)訪問(wèn)溢出 |
| Use after return | 訪問(wèn)棧上已被釋放的內(nèi)存 |
| Use after scope | 棧對(duì)象使用超過(guò)定義范圍 |
| Initialization order bugs | 初始化命令錯(cuò)誤 |
| Memory leaks | 內(nèi)存泄漏 |
更多細(xì)節(jié)請(qǐng)?jiān)L問(wèn)官網(wǎng)
具體可以看 google 的官方文檔:https://github.com/google/sanitizers/wiki/AddressSanitizer
結(jié)束語(yǔ)
ASan是個(gè)很好的檢測(cè)內(nèi)存問(wèn)題的工具,不需要配置環(huán)境,使用還方便,編譯時(shí)只需要-fsanitize=address -g 就可以, 運(yùn)行程序時(shí)候可以選擇添加對(duì)應(yīng)的 ASAN_OPTIONS 環(huán)境變量就可以檢測(cè)出很多內(nèi)存問(wèn)題。如果哪里不清楚可以查看官方說(shuō)明,歡迎交流學(xué)習(xí)。


