linux中内存泄漏的检测(五)记录内存泄漏的代码
2017-08-10 18:55
351 查看
到目前为止,先后通过wrap malloc、new函数重载和计算指针内存大小的方法,基本上满足了对内存泄漏检测的需要。
如果发现了内存泄漏,那么就要找到内存泄漏的地方并且修正它了。
茫茫代码,如何去找?如果能根据未释放的内存找到申请它的地方就好了。
我们今天就是要做这个事情。
想要根据内存地址查出申请者的信息,那么在一开始申请的时候就要建立地址与申请者之间的映射。
1.内存地址
内存地址,是一个unsigned long型的数值,用
2.申请者信息
申请者的信息比较复杂,不是一个类型可以搞定的。它包括哪些内容呢?
在C情况下,主要是需要知道谁调用了
由此可见,仅仅知道是谁调用了
整个栈包含了很多内容,在这里,我们只记录栈的深度(int)和每一层的符号名(char **)。符号名在整个程序中是唯一的(不管C还是C++)且相对位置是确定的(动态库除外),当程序结束时再根据符号名反推出调用者的文件名和行号。
为什么不直接获取文件名和行号?
因为求符号名的实现比较简单。
3.映射方式
说到映射,首先想到的是map、hash这样的东西。
但需要说明的是,这里是
这有什么关系呢?想象一下,在由于某个动态申请内存的操作来到了这个函数,而在这个函数里又不小心申请了一次内存,会怎样呢?在
所以,在这个函数里能使用的,只能使用栈空间或者全局空间,如果一定要使用堆空间,也必须显示地使用
怎么办呢?为了避免节外生枝,我这里使用了最简单但是有点笨的方法——数组。
2
3
4
5
6
7
1
2
3
4
5
6
7
4.怎样获取栈中的符号?
gcc给我们提相应的函数,按照要求调用就行。
2
3
4
5
1
2
3
4
5
backtrace函数用于获取栈的深度(
需要注意的是,backtrace_symbols返回的是符号的数组,这个数组的空间是由
为什么这里
源代码:
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
编译命令:
1
运行:
1
方法分析:
优点:
(1)在程序运行结束时,打印程序内存泄漏情况以及导致泄漏发生的代码所在的文件及行号
(2)C/C++都适用
(3)需要修改产品源代码即可实现功能
(4)对一起链接的所有.o和静态库都有效
缺点:
(1)对动态库不适用
(2)求堆栈信息和求文件名行号是两个操作,不能一次性解决问题
如果发现了内存泄漏,那么就要找到内存泄漏的地方并且修正它了。
茫茫代码,如何去找?如果能根据未释放的内存找到申请它的地方就好了。
我们今天就是要做这个事情。
想要根据内存地址查出申请者的信息,那么在一开始申请的时候就要建立地址与申请者之间的映射。
1.内存地址
内存地址,是一个unsigned long型的数值,用
void *来存储也可以。为了避免类型转换,我使用了
void *。
2.申请者信息
申请者的信息比较复杂,不是一个类型可以搞定的。它包括哪些内容呢?
在C情况下,主要是需要知道谁调用了
__wrap_malloc。但在C++情况下,调用
__wrap_malloc的一定是new,这没有什么意义,还需要知道是谁调用了new。再进一步说,new有可能是在构造函数中被调用的,那么很有可能我们真正需要知道的是谁调用了构造函数。
由此可见,仅仅知道是谁调用了
__wrap_malloc不够的,我们需要的是整个栈信息。
整个栈包含了很多内容,在这里,我们只记录栈的深度(int)和每一层的符号名(char **)。符号名在整个程序中是唯一的(不管C还是C++)且相对位置是确定的(动态库除外),当程序结束时再根据符号名反推出调用者的文件名和行号。
为什么不直接获取文件名和行号?
因为求符号名的实现比较简单。
3.映射方式
说到映射,首先想到的是map、hash这样的东西。
但需要说明的是,这里是
__wrap_malloc函数,是每次程序动态分配空间时必然会走到的地方。
这有什么关系呢?想象一下,在由于某个动态申请内存的操作来到了这个函数,而在这个函数里又不小心申请了一次内存,会怎样呢?在
-Wl,--wrap,malloc的作用下又来到了这里,于是开启了“鸡生蛋、蛋生鸡”的死循环中,直到——stack overflow。
所以,在这个函数里能使用的,只能使用栈空间或者全局空间,如果一定要使用堆空间,也必须显示地使用
__real_malloc代替new或者malloc。由于在map、hash中会不可避免地使用动态内存空间的情况,还是放弃吧。
怎么办呢?为了避免节外生枝,我这里使用了最简单但是有点笨的方法——数组。
struct memory_record { void * addr; size_t count; int depth; char **symbols; }mc[1000];1
2
3
4
5
6
7
1
2
3
4
5
6
7
4.怎样获取栈中的符号?
gcc给我们提相应的函数,按照要求调用就行。
char* stack[20] = {0}; mc[i].depth = backtrace(reinterpret_cast<void ** >(stack), sizeof(stack)/sizeof(stack[0])); if (mc[i].depth){ mc[i].symbols = backtrace_symbols(reinterpret_cast<void**>(stack), mc[i].depth); }1
2
3
4
5
1
2
3
4
5
backtrace函数用于获取栈的深度(
depth),以及每一层栈地址(
stack)。
backtrace_symbols函数根据栈地址返回符号名(
symbols)。
需要注意的是,backtrace_symbols返回的是符号的数组,这个数组的空间是由
backtrace_symbols分配的,但需要调用者释放。
为什么这里
backtrace_symbols分配了内存却没有引起stack overflow呢?以下是我的猜测:
backtrace_symbols函数和wrap机制都是GNU提供的,属性亲戚关系。既然是亲戚,那么大家通融一下,让
backtrace_symbols绕过wrap机制直接使用内存也是有可能的。
源代码:
#include <iostream>1
using namespace std;
#include "string.h"
#include <stdio.h>
#include <malloc.h>
#include <execinfo.h>
#if(defined(_X86_) && !defined(__x86_64))
#define _ALLOCA_S_MARKER_SIZE 4
#elif defined(__ia64__) || defined(__x86_64)
#define _ALLOCA_S_MARKER_SIZE 8
#endif
size_t count = 0;
int backtrace(void **buffer, int size);
struct memory_record { void * addr; size_t count; int depth; char **symbols; }mc[1000];
extern "C"
{
void* __real_malloc(int c);
void * __wrap_malloc(size_t size)
{
void *p = __real_malloc(size);
size_t w = *((size_t*)((char*)p - _ALLOCA_S_MARKER_SIZE));
cout<<"malloc "<<p<<endl;
for(int i = 0; i < 1000; i++)
{
if(mc[i].count == 0)
{
count += w;
mc[i].addr = p;
mc[i].count = w;
char* stack[20] = {0};
mc[i].depth = backtrace(reinterpret_cast<void**>(stack), sizeof(stack)/sizeof(stack[0]));
if (mc[i].depth){
mc[i].symbols = backtrace_symbols(reinterpret_cast<void**>(stack), mc[i].depth);
}
break;
}
}
return p;
}
void __real_free(void *ptr);
void __wrap_free(void *ptr)
{
cout<<"free "<<ptr<<endl;
size_t w = *((size_t*)((char*)ptr - _ALLOCA_S_MARKER_SIZE));
for(int i = 0; i < 1000; i++)
{
if(mc[i].addr == ptr)
{
mc[i].count -= w;
count -= w;
if(mc[i].symbols)
__real_free(mc[i].symbols);
break;
}
}
__real_free(ptr);
}
}
void *operator new(size_t size)
{
return malloc(size);
}
void operator delete(void *ptr)
{
free(ptr);
}
void print_leaked_memory()
{
if(count != 0)
cout<<"memory leak!"<<endl;
for(int i = 0; i < 1000; i++)
{
if(mc[i].count != 0)
{
cout<<mc[i].addr<<' '<<mc[i].count<<endl;
if (mc[i].symbols){
for(size_t j = 0; j < mc[i].depth; j++){
printf("===[%d]:%s\n", (j+1), mc[i].symbols[j]);
}
}
__real_free(mc[i].symbols);
}
}
}
class A
{
int *p1;
public:
A(){p1 = new int;}
~A(){delete p1;}
};
int main(void)
{
memset(mc, 0, sizeof(mc));
count = 0;
int *p1 = new int(4);
int *p2 = new int(5);
delete p1;
print_leaked_memory();
return 0;
}
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
编译命令:
g++ -o test test.cpp -g -Wl,--wrap,malloc -Wl,--wrap,free1
1
运行:
./test | grep "===" | cut -d"[" -f3 | tr -d "]" | addr2line -e test1
1
方法分析:
优点:
(1)在程序运行结束时,打印程序内存泄漏情况以及导致泄漏发生的代码所在的文件及行号
(2)C/C++都适用
(3)需要修改产品源代码即可实现功能
(4)对一起链接的所有.o和静态库都有效
缺点:
(1)对动态库不适用
(2)求堆栈信息和求文件名行号是两个操作,不能一次性解决问题
相关文章推荐
- linux中内存泄漏的检测(五)记录内存泄漏的代码
- linux中内存泄漏的检测(四)记录泄漏的大小
- linux中内存泄漏的检测(四)记录泄漏的大小
- Linux C/C++ 内存泄漏检测工具:Valgrind
- Linux使用valgrind来检测程序的内存泄漏
- Java代码质量检测工具记录
- 2017-02问题记录总结:linux下网络收发包性能测试代码、.bin和.elf文件格式区别
- Linux 内存泄漏检测工具 Valgrind
- 对代码进行内存泄漏检测的工具——Valgrind
- valgrind——Linux下内存泄漏检测工具
- 检查C++中的内存泄漏-通过添加代码来检测
- Linux代码性能检测利器(三)-控制分析器opcontrol使用说明
- linux中用C语言实现的自动在文件末尾不断添加记录的完整代码
- Linux C/C++ 内存泄漏检测工具:Valgrind
- Linux下c++程序内存泄漏检测代码范例
- linux上检测代码性能以及内存泄露
- linux网络代码流程简单记录
- android内存泄漏检测工具leakCanary--代码工具备忘录
- C++中基于Crt的内存泄漏检测(重载new和delete,记录在Map里)
- java Jfree,文件中代码检测: 完成每周记录,并实现折线图和饼状图