Abel'Blog

我干了什么?究竟拿了时间换了什么?

0%

C++内存泄漏问题定位

简介

C++非常容易出现内存泄漏的问题。写一篇文章来总结这块的事情。

内存泄漏和内存野指针,是相伴随出现的。本质来自于堆内存处理的粗暴造成。

工具篇

Windows

dotmemory 只能检查.net工程。

vld Visual Leak Detector 代码比较古老,只能支持到vs2015版本。

NuMega BoundsChecker

DebugDial 这个工具是微软出品的。

参考这份文档:使用Debug Diagnostic Tool排除内存泄漏故障

Linux

gperftools 非侵入式的内存检查工具。可以通过LD_PRELOAD来设置

valgrand 非侵入式的内存检查工具。不过对于大项目,不算太友好;需要一台比较好的机器来跑。

clang Address Sanitizer 工具。

这里我们做了一个例子来演示,它如何检查服务器的内存泄露:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
[root@VM_0_13_centos cpp_leak_sample]# clang++ -D_GLIBCXX_DEBUG -g -o main -fsanitize=address -std=c++11 ./main.cpp
[root@VM_0_13_centos cpp_leak_sample]# ./main

=================================================================
==28593==ERROR: LeakSanitizer: detected memory leaks

Direct leak of 4 byte(s) in 2 object(s) allocated from:
#0 0x526a08 in operator new[](unsigned long) (/root/learn/cpp/cpp_leak_sample/main+0x526a08)
#1 0x52aeda in main /root/learn/cpp/cpp_leak_sample/./main.cpp:21:19
#2 0x7f1ddf091554 in __libc_start_main (/lib64/libc.so.6+0x22554)

SUMMARY: AddressSanitizer: 4 byte(s) leaked in 2 allocation(s).
[root@VM_0_13_centos cpp_leak_sample]# vim ./main.cpp
[root@VM_0_13_centos cpp_leak_sample]# clang++ -D_GLIBCXX_DEBUG -g -o main -fsanitize=address -std=c++11 ./main.cpp
[root@VM_0_13_centos cpp_leak_sample]# ./main

=================================================================
==3425==ERROR: LeakSanitizer: detected memory leaks

Direct leak of 4 byte(s) in 2 object(s) allocated from:
#0 0x526f88 in operator new[](unsigned long) (/root/learn/cpp/cpp_leak_sample/main+0x526f88)
#1 0x52b43f in leak_sample1() /root/learn/cpp/cpp_leak_sample/./main.cpp:24:19
#2 0x52b96a in main /root/learn/cpp/cpp_leak_sample/./main.cpp:63:5
#3 0x7f769f996554 in __libc_start_main (/lib64/libc.so.6+0x22554)

SUMMARY: AddressSanitizer: 4 byte(s) leaked in 2 allocation(s).

Clang Static Analyzer 静态分析工具。

clang控制error-warnning信息的选项

sanitizers的官方wiki

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
$ cat suppr.txt 
## This is a known leak.
leak:FooBar
$ cat lsan-suppressed.cc
##include <stdlib.h>

void FooBar() {
malloc(7);
}

void Baz() {
malloc(5);
}

int main() {
FooBar();
Baz();
return 0;
}
$ clang++ lsan-suppressed.cc -fsanitize=address
$ ASAN_OPTIONS=detect_leaks=1 LSAN_OPTIONS=suppressions=suppr.txt ./a.out

=================================================================
==26475==ERROR: LeakSanitizer: detected memory leaks

Direct leak of 5 byte(s) in 1 object(s) allocated from:
#0 0x44f2de in malloc /usr/home/hacker/llvm/projects/compiler-rt/lib/asan/asan_malloc_linux.cc:74
#1 0x464e86 in Baz() (/usr/home/hacker/a.out+0x464e86)
#2 0x464fb4 in main (/usr/home/hacker/a.out+0x464fb4)
#3 0x7f7e760b476c in __libc_start_main /build/buildd/eglibc-2.15/csu/libc-start.c:226

-----------------------------------------------------
Suppressions used:[design document](AddressSanitizerLeakSanitizerDesignDocument)
count bytes template
1 7 FooBar
-----------------------------------------------------

SUMMARY: AddressSanitizer: 5 byte(s) leaked in 1 allocation(s).

Jemalloc检查内存泄漏

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
## Jemalloc编译的时候,需要将调试信息开启,否则无法启用检查内存的功能;
./configure --enable-log --enable-debug --enable-prof-libunwind --enable-prof CXX=clang++ CC=clang

## 参考资料
## : Invalid conf pair: prof:true
## https://github.com/jemalloc/jemalloc/issues/175

## In all likelihood the jemalloc build you are using didn't have --enable-prof specified to the configurescript when it was built. You can verify this via the opt.stats_print mallctl to print out configuration information, or you can query config.prof directly.

## 编译出来的位置
/usr/local/lib/libjemalloc.so.2
/usr/local/lib/libjemalloc.so

## 在命令行里面启动服务器gs,增加一些参数,来开始检测内存泄漏
MALLOC_CONF=prof_leak:true,lg_prof_sample:0,prof_final:true \
LD_PRELOAD=/usr/local/lib/libjemalloc.so ./gs

export MALLOC_CONF="prof:true,lg_prof_sample:1,prof_accum:false,prof_prefix:jeprof.out"

sanitizers 的内存泄漏检查,是关闭服务器之后才能看到结果。
如果是运行期的内存泄漏问题,还是需要通过dump heap来做对比。
jemalloc/tcmalloc都能做这样的事情。
jeprof可以用来比较两个dump(显示增量部分):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
## /usr/local/jemalloc-5.1.0/bin/jeprof a.out --base=jeprof.34584.2.i2.heap jeprof.34584.3.i3.heap
Using local file a.out.
Using local file jeprof.34584.3.i3.heap.
Welcome to jeprof! For help, type 'help'.
(jeprof) top
Total: 1.6 MB
1.1 66.2% 66.2% 1.1 66.2% do_something_else
0.5 33.8% 100.0% 0.5 33.8% do_something
0.0 0.0% 100.0% 1.6 100.0% __libc_start_main
0.0 0.0% 100.0% 1.6 100.0% _start
0.0 0.0% 100.0% 1.6 100.0% main
(jeprof)

$ jeprof --show_bytes --pdf ./cpp_leak_sample jeprof.10395.1.i1.heap > a.pdf

最终的效果:

clsxxS.png

valgrind ./cpp_leak_sample —leak-check=full

1
2
3
4
5
6
7
8
9
10
leak 512 byte
==11344== Mismatched free() / delete / delete []
==11344== at 0x483CFBF: operator delete(void*) (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==11344== by 0x401428: leak_sample2() (cpp_leak_sample.cpp:69)
==11344== by 0x4014AA: main (cpp_leak_sample.cpp:84)
==11344== Address 0x4daa360 is 0 bytes inside a block of size 7 alloc'd
==11344== at 0x483C583: operator new[](unsigned long) (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==11344== by 0x401407: leak_sample2() (cpp_leak_sample.cpp:67)
==11344== by 0x4014AA: main (cpp_leak_sample.cpp:84)
==11344==

出现过的问题集合

分配了没有回收。很容易出现的问题,在于容器里面有指针,再清理的时候,没有将容器中的元素清理掉。

参考

[1] 五大内存泄露应对之策,好文必看!
[2] LLVM百度百科
[3] GoogleSanitizers
[4] Jemalloc
[5] 通过jemalloc检查heap