gdb调试与valgrind检测内存泄漏

使用gdb调试代码

示例代码:test.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// test.c
#include<stdio.h>

int sumN(int n)
{
int sum = 0,i;
for(i = 0;i < n;i++){
sum += i;
}
return sum;
}

int main(void)
{
int i;
long result = 0;
for(i = 0;i<=100;i++){
result += i;
}
printf("result[1-100] = %d\n",result);
printf("result[1-200] = %d\n",sumN(200));
return 0;
}
1
2
3
4
5
6
7
8
9
10
[root@localhost memleaktest]# ls
test.c
# 一般编译指令,目标文件为test
[root@localhost memleaktest]# gcc test.c -o test
# 需要使用gdb进行调试程序,因此在编译的时候需要加上-g
[root@localhost memleaktest]# gcc -g test.c -o test
# 执行程序,查看输出
[root@localhost memleaktest]# ./test
result[1-100] = 5050
result[1-200] = 19900

gdb的跟踪调试功能

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
# 使用gdb调试
[root@localhost memleaktest]# gdb ./test

GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-120.el7
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-redhat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /mnt/memleaktest/test...done. # 符号表已经读取

# 设置断点
# 将断点设置在函数入口 `break 函数名`
(gdb) break sumN
Breakpoint 1 at 0x400534: file test.c, line 5. // 断点设置在了文件第五行

# 在某一行设置断点 `b 行号`
(gdb) b 10
Breakpoint 2 at 0x400559: file test.c, line 10.

# 设置断点后执行程序 run或者r
(gdb) r
Starting program: /mnt/memleaktest/./test
result[1-100] = 5050

Breakpoint 1, sumN (n=200) at test.c:5
5 int sum = 0,i; # 停留在第一个断点处

# next 或者 step 可以执行下一步
(gdb) next
6 for(i = 0;i < n;i++){

# 直接回车,可以自动执行上一次输入的命令
(gdb)
7 sum += i;
(gdb)
6 for(i = 0;i < n;i++){
(gdb)
7 sum += i;

# 查看变量的值 print 或者 p
(gdb) print sum
$1 = 0
(gdb) p i
$2 = 1

# setp和next的区别
# step可以进入函数调用的内部
# next遇到函数调用会直接返回函数调用之后的状态
(gdb) step
6 for(i = 0;i < n;i++){
(gdb)
7 sum += i;

# 在调试多进程时
# 多进程时一般调用fork,因此需要区分进程父进程和子进程
(gdb) set follow-fork-mode parent # 调试父进程
(gdb) set follow-fork-mode child # 调试子进程

# 多线程情况
# info threads #可以查看所有线程
(gdb) info threads
Id Target Id Frame
* 1 process 6403 "test" sumN (n=200) at test.c:7

# 切换线程:thread id
(gdb) thread 1
[Switching to thread 1 (process 6403)]
#0 sumN (n=200) at test.c:7
7 sum += i;

# 退出quit或者q
(gdb) q
A debugging session is active.

Inferior 1 [process 6403] will be killed.

Quit anyway? (y or n) y

使用gdb分析crash

示例代码:crash.c,改代码尝试修改常量字符串!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// crash.c
#include<stdio.h>

void core_test(void)
{
char * str = "test";
str[1] = 'T';
return;
}

int main(void)
{
int i = 10;
core_test();
return 0;
}
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
120
121
122
[root@localhost memleaktest]# ls
crash.c test test.c
[root@localhost memleaktest]# gcc -g crash.c -o crash
[root@localhost memleaktest]# ls
crash crash.c test test.c
[root@localhost memleaktest]#

# 执行程序
[root@localhost memleaktest]# ./crash
段错误(吐核)

# 不使用gdb时,可以做简要分析
[root@localhost memleaktest]# cd /var/log/
[root@localhost log]# ls
anaconda grubby_prune_debug spooler-20211024
audit lastlog spooler-20211116
boot.log maillog spooler-20220306
boot.log-20211008 maillog-20211022 tallylog
boot.log-20211010 maillog-20211024 tuned
boot.log-20211012 maillog-20211116 vmware-network.1.log
boot.log-20211022 maillog-20220306 vmware-network.2.log
boot.log-20211024 messages vmware-network.3.log
boot.log-20211116 messages-20211022 vmware-network.4.log
boot.log-20220306 messages-20211024 vmware-network.5.log
btmp messages-20211116 vmware-network.6.log
btmp-20220306 messages-20220306 vmware-network.7.log
chrony rabbitmq vmware-network.8.log
cron rhsm vmware-network.9.log
cron-20211022 secure vmware-network.log
cron-20211024 secure-20211022 vmware-vgauthsvc.log.0
cron-20211116 secure-20211024 vmware-vmsvc-root.log
cron-20220306 secure-20211116 vmware-vmtoolsd-root.log
dmesg secure-20220306 wtmp
dmesg.old spooler yum.log
firewalld spooler-20211022 yum.log-20220306

# 查看message的末尾几行
[root@localhost log]# tail messages
Apr 25 01:23:54 localhost systemd-logind: New session 4 of user root.
Apr 25 01:23:54 localhost systemd: Started Session 4 of user root.
Apr 25 01:25:07 localhost systemd-logind: New session 5 of user root.
Apr 25 01:25:07 localhost systemd: Started Session 5 of user root.
Apr 25 01:25:40 localhost systemd-logind: Removed session 1.
Apr 25 01:32:24 localhost kernel: crash[8272]: segfault at 4005c1 ip 0000000000400501 sp 00007ffee0f8b690 error 7 in crash[400000+1000]
# error 7 用二进程表示,意为 111;
# 第0位为0时,表示内存页找不到,为1时表示保护错误
# 第1位为0时,表示读内存发生错误,为1表示写内存发生错误
# 第2位为0时,表示错误发生在内核空间,为1表示发生在用户空间
Apr 25 01:32:24 localhost abrt-hook-ccpp: Process 8272 (crash) of user 0 killed by SIGSEGV - dumping core
Apr 25 01:32:26 localhost abrt-server: Executable '/mnt/memleaktest/crash' doesnt belong to any package and ProcessUnpackaged is set to 'no'
Apr 25 01:32:26 localhost abrt-server: 'post-create' on '/var/spool/abrt/ccpp-2022-04-25-01:32:24-8272' exited with 1
Apr 25 01:32:26 localhost abrt-server: Deleting problem directory '/var/spool/abrt/ccpp-2022-04-25-01:32:24-8272'


# 查看coredump 文件
[root@localhost log]# cd /mnt/memleaktest/
[root@localhost memleaktest]# ls
crash crash.c test test.c
# 有时不会产生coredump文件,查看 ulimit -a
[root@localhost memleaktest]# ulimit -a
core file size (blocks, -c) 0 # 大小为0,则不生成
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 7183
max locked memory (kbytes, -l) 64
max memory size (kbytes, -m) unlimited
open files (-n) 4096
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 7183
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited

# 打开
[root@localhost memleaktest]# ulimit -c unlimited

# 再次执行后,生成coredump文件
[root@localhost memleaktest]# ./crash
段错误(吐核)
[root@localhost memleaktest]# ls
core.9256 crash crash.c test test.c

# 使用gdb查看coredump
# gdb 程序名 coredump文件名
[root@localhost memleaktest]# gdb crash core.9256
GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-120.el7
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-redhat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /mnt/memleaktest/crash...done.
[New LWP 9256]
Core was generated by './crash'.
Program terminated with signal 11, Segmentation fault.
#0 0x0000000000400501 in core_test () at crash.c:6
6 str[1] = 'T';

# bt 或者 where 命令,可以查看函数调用过程
(gdb) bt
#0 0x0000000000400501 in core_test () at crash.c:6
#1 0x000000000040051b in main () at crash.c:13
(gdb) where
#0 0x0000000000400501 in core_test () at crash.c:6
#1 0x000000000040051b in main () at crash.c:13

# 在coredump文件中查看变量的值
# 每调用一个函数都会调用一个栈空间
(gdb) print i
No symbol "i" in current context. # i在main中
(gdb) f 1 # 跳到main层
#1 0x000000000040051b in main () at crash.c:13
13 core_test();
(gdb) p i # 正确查看
$1 = 10

内存泄露

什么是内存泄漏

  1. 进程的虚拟内存在不断增加
    内存泄漏1
  2. 原因:malloc/new申请内存后没有调用free/delete释放内存

内存泄漏的问题

  1. 占用虚拟内存
  2. 堆上的内存会不断地被占用

难点

  1. 如何判断是否有内存泄漏
  2. 如何判断代码的哪一行存在内存泄漏
  3. 肉眼难以发现
  4. 哪一个指针造成的内存泄漏

工具

  1. valgrand/mtrace
  2. 使用工具之前怀疑内存泄漏(使用工具的前提)

判断是否有内存泄漏

malloc一次+1,free一次-1;进程退出时,total != 0,则有内存泄漏

valgrind检测内存泄漏

示例代码:leak.c,改代码有内存泄漏,越界等问题!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// leak.c
#include<stdio.h>
#include<stdlib.h>

void leakfun(void)
{
int * pInt = (int *)malloc(10 * sizeof(int));
*(pInt + 10) = 0;
return;
}

int main(void)
{
leakfun();
return 0;
}
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
[root@localhost memleaktest]# ls
core.9256 crash crash.c leak.c test test.c
[root@localhost memleaktest]# gcc -g leak.c -o leak
[root@localhost memleaktest]# ./leak //执行程序无崩溃

[root@localhost memleaktest]# valgrind --tool=memcheck --leak-check=full --trace-children=yes --log-file=analysis.%p ./leak
# --trace-children=yes 多进程检测子进程
# --log-file=analysis.%p 分析结果以log形式保存,%p为进程号

[root@localhost memleaktest]# ls
analysis.10781 core.9256 crash crash.c leak leak.c test test.c

# 查看报告
[root@localhost memleaktest]# cat analysis.10781
==10781== Memcheck, a memory error detector
==10781== Copyright (C) 2002-2017, and GNU GPLd, by Julian Seward et al.
==10781== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==10781== Command: ./leak
==10781== Parent PID: 7555
==10781==
==10781== Invalid write of size 4 # 4字节无效的写(越界)
==10781== at 0x40054B: leakfun (leak.c:7)
==10781== by 0x40055C: main (leak.c:13)
==10781== Address 0x5205068 is 0 bytes after a block of size 40 allocd
==10781== at 0x4C29F73: malloc (vg_replace_malloc.c:309)
==10781== by 0x40053E: leakfun (leak.c:6)
==10781== by 0x40055C: main (leak.c:13)
==10781==
==10781==
==10781== HEAP SUMMARY:
==10781== in use at exit: 40 bytes in 1 blocks # 分配了40个字节
==10781== total heap usage: 1 allocs, 0 frees, 40 bytes allocated
==10781==
==10781== 40 bytes in 1 blocks are definitely lost in loss record 1 of 1 # 泄漏了40个字节
==10781== at 0x4C29F73: malloc (vg_replace_malloc.c:309)
==10781== by 0x40053E: leakfun (leak.c:6)
==10781== by 0x40055C: main (leak.c:13)
==10781==
==10781== LEAK SUMMARY:
==10781== definitely lost: 40 bytes in 1 blocks
==10781== indirectly lost: 0 bytes in 0 blocks
==10781== possibly lost: 0 bytes in 0 blocks
==10781== still reachable: 0 bytes in 0 blocks
==10781== suppressed: 0 bytes in 0 blocks
==10781==
==10781== For lists of detected and suppressed errors, rerun with: -s
==10781== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)

版权声明: 本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!
个人邮箱: wushiyu0314@163.com