GDB调试学习总结
命令概览
命令名称 | 命令缩写 | 命令说明 | 备注 |
---|---|---|---|
run | r | 运行一个程序 | |
break | b | 添加断点 | -b xx.cpp:line |
delete | del | 删除断点 | |
list | l | 显示源代码 | |
continue | c | 让暂停的程序继续运行 | |
next | n | 运行到下一行源代码,不进入函数内部 | |
step | s | 运行到下一行源代码,进入函数内部 | |
p | 打印变量或者寄存器的值 | ||
until | u | 运行到指定行停止下来 | |
jump | j | 将当前程序执行流跳转到指定行或地址 | |
backtrace | bt | 查看当前线程的调用堆栈 | |
thread | thread | 切换到指定线程 | |
frame | f | 切换到当前调用线程的指定堆栈,具体堆栈通过堆栈序号指定 | |
info | info | 查看断点/线程等信息 | |
ptype | ptype | 查看变量类型 | |
disassemble | dis | 查看汇编代码 | |
watch | watch | 监视某一个变量或内存地址的值是否发生变化 | |
finish | fin | 结束当前调用函数,返回到上一层函数调用处 | |
return | return | 结束当前调用函数并返回指定值,到上一层函数调用处 |
调试方法
直接调试
gdb filename
开发过程中,编译生成目标二进制文件后,可以直接gdb filename
开始调试,输入run
或r
运行程序。比如下图所示:
启动后可以通过netstat -nltp | grep 6379
来查看程序已经运行起来了
附加进程调试
gdb attach pid
如果当程序已经启动后想调试,但是不想重启程序。或者当程序在测试过程中,不能重启,一旦重启则会丢失当前的状态信息。这种情况下就使用附加进程进行调试。- 获取进程pid
- 生成附加进程
提示 “Attaching to process 8380 时即已成功将 GDB 附加到目标进程,结束调试后输入detach
离开且不对当前进程有影响,让程序和gdb调试器分离,如下图:
- 获取进程pid
core文件调试
coredump即核心转储,是进程运行崩溃瞬间的一个内存快照。操作系统在程序发生异常而异常在进程内部又没有被捕获的情况下,会把进程此刻内存、寄存器状态、运行堆栈等信息转储保存到core文件,该文件是个二进制文件,可以使用gdb进行分析。
产生原因
- 内存访问越界
- 多线程程序使用了线程不安全的函数
- 多线程读写数据未加锁保护,对于被多个线程同时访问的全局数据,要加锁保护否则会coredump
- 非法指针
- 堆栈溢出
查看系统产生core文件后存放的位置
1
2
3
4
5
6
7[xw@null coredump]$ cat /proc/sys/kernel/core_pattern
/data/coredump/core_%e_%t
- 表示产生的core文件都以core_%e_%t格式保存到/data/coredump/目录下
-e 程序文件名
-t 时间戳
-p 进程ID
-s 使进程崩溃的信号signal修改core文件的存储位置
1
echo "/data/coredump/core_%e_%t" > /proc/sys/kernel/core_pattern
修改core文件大小
(1) ulimit -c 查看参数core file size的值,如果为0表示不会生成core文件,一般修改为unlimited1
2[xw@null ~]$ ulimit -c
0(2) 修改core文件大小
1
2
3[xw@null ~]$ ulimit -c unlimited
[xw@null ~]$ ulimit -c
unlimitedreadelf -h corefile
查看core文件头
gdb调试core文件
1
2gdb 执行文件 core文件
where/bt 查看堆栈信息进一步分析程序参考链接
命令详解
run
1 | [xxxx@gzqc-172_24_21_96-null src]$ gdb redis-server |
此时只是附加了一个调试文件,程序还未启动,输入run
命令即可运行程序。
按Ctrl+C
可以中断gdb
,再次输入r
后输入y
重启程序
continue
按Ctrl+C
可以中断gdb
,输入c
可以使程序继续运行
1 | ^C |
break
break
用于添加断点,添加完成后重启程序即可触发,停在断点处break func
在函数名入口处添加断点break line
在当前文件行号line处添加断点break filename:line
在文件filename行号line处添加断点
redis-server端口
6379
,端口号肯定是通过bind()
函数创建的,搜索文件位于anet.c 401
行。
- 在
anet.c:401
处添加断点后重启触发断点1
,continue
后触发断点2
。如下所示:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15(gdb) b anet.c:401
(gdb) info b
Num Type Disp Enb Address What
1 breakpoint keep y 0x0000000000442962 in main at server.c:6151
2 breakpoint keep y 0x00000000004340e2 in anetListen at anet.c:401
(gdb) r
Starting program: /data/home/xxxx/redis-6.2.3/src/redis-server
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
Breakpoint 1, main (argc=1, argv=0x7fffffffdf58) at server.c:6151
6151 char config_from_stdin = 0;
(gdb) c
Breakpoint 2, anetListen (err=0xbcee50 <server+752> "", s=10, sa=0xdf79a0, len=16, backlog=511) at anet.c:401
401 if (bind(s,sa,len) == -1) { - 分别在
anet.c 404 410 412
处添加断点,用于判断从该函数从何处返回。如下图所示: continue
可以看到触发断点5
。如下图所示:
- 在
backtrace 和 frame
backtrace
用于查看堆栈信息。frame
用于切换到某个堆栈处。上面的gdb程序中断在anet.c:412
处bt
查看此时的堆栈信息共有6层堆栈#0 ~ #5
,如下图所示:frame 堆栈编号
切换堆栈信息,如下图所示:
可以得到调用关系main()
函数在6322行调用initServer()
函数initServer()
函数在3195行调用listenToPort()
函数listenToPort()
函数在3052行调用anetTcpServer()
函数anetTcpServer()
函数在468行调用_anetTcpServer()
函数_anetTcpServer()
函数在450行调用anetListen()
函数- 此时断点正停在
anetListen()
函数中
info break、enable、disable、delete
info b
查看断点个数
enable num
启用编号为num的断点,不加参数表示全部启用disable num
禁用编号为num的断点,不加参数表示全部禁用delete num
删除编号为num的断点,不加参数表示全部删除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(gdb) info b
Num Type Disp Enb Address What
1 breakpoint keep y 0x0000000000442962 in main at server.c:6151
breakpoint already hit 1 time
2 breakpoint keep y 0x00000000004340e2 in anetListen at anet.c:401
breakpoint already hit 2 times
3 breakpoint keep y 0x000000000043412c in anetListen at anet.c:404
4 breakpoint keep y 0x0000000000434178 in anetListen at anet.c:410
5 breakpoint keep y 0x000000000043417f in anetListen at anet.c:412
breakpoint already hit 1 time
(gdb) disable 4
(gdb) info b
Num Type Disp Enb Address What
1 breakpoint keep y 0x0000000000442962 in main at server.c:6151
breakpoint already hit 1 time
2 breakpoint keep y 0x00000000004340e2 in anetListen at anet.c:401
breakpoint already hit 2 times
3 breakpoint keep y 0x000000000043412c in anetListen at anet.c:404
4 breakpoint keep n 0x0000000000434178 in anetListen at anet.c:410
5 breakpoint keep y 0x000000000043417f in anetListen at anet.c:412
breakpoint already hit 1 time
(gdb) delete 3
(gdb) info b
Num Type Disp Enb Address What
1 breakpoint keep y 0x0000000000442962 in main at server.c:6151
breakpoint already hit 1 time
2 breakpoint keep y 0x00000000004340e2 in anetListen at anet.c:401
breakpoint already hit 2 times
4 breakpoint keep n 0x0000000000434178 in anetListen at anet.c:410
5 breakpoint keep y 0x000000000043417f in anetListen at anet.c:412
breakpoint already hit 1 time
(gdb) disable
(gdb) info b
Num Type Disp Enb Address What
1 breakpoint keep n 0x0000000000442962 in main at server.c:6151
breakpoint already hit 1 time
2 breakpoint keep n 0x00000000004340e2 in anetListen at anet.c:401
breakpoint already hit 2 times
4 breakpoint keep n 0x0000000000434178 in anetListen at anet.c:410
5 breakpoint keep n 0x000000000043417f in anetListen at anet.c:412
breakpoint already hit 1 time
(gdb) enable
(gdb) info b
Num Type Disp Enb Address What
1 breakpoint keep y 0x0000000000442962 in main at server.c:6151
breakpoint already hit 1 time
2 breakpoint keep y 0x00000000004340e2 in anetListen at anet.c:401
breakpoint already hit 2 times
4 breakpoint keep y 0x0000000000434178 in anetListen at anet.c:410
5 breakpoint keep y 0x000000000043417f in anetListen at anet.c:412
breakpoint already hit 1 time
(gdb) delete
Delete all breakpoints? (y or n) y
(gdb) info b
No breakpoints or watchpoints.
list
list
用于显示源码,默认显示10行。如图所示:
print、ptype
print
用于查看变量的值,ptype
用于查看变量类型。如下所示:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18(gdb) f 4
#4 0x000000000043b304 in initServer () at server.c:3195
3195 listenToPort(server.port,&server.ipfd) == C_ERR) {
(gdb) p server.port
$1 = 6379
(gdb) ptype server
type = struct redisServer {
pid_t pid;
pthread_t main_thread_id;
char *configfile;
char *executable;
char **exec_argv;
int dynamic_hz;
int config_hz;
mode_t umask;
....}
(gdb) ptype server.port
type = int
info 和 thread
info thread
查看redis-server
启动后一共产生5
个线程,1
个主线程和4
个工作线程。主线程阻塞在epoll_wait()
处,其他工作线程阻塞在pthread_cond_wait()
处。*
表示的是当前gdb
作用于哪个线程上,而不是*
就代表指向主线程。
bt
查看当前线程的调用堆栈,如下图所示:
堆栈#4上显示的调用main()
函数表示当前线程即主线程。
thread num
切换到其他线程
next 和 step
next
单步步过(step over),遇到函数直接跳过不会进入函数内部。(回车键默认是将最近的一条命令执行)step
单步步入(step into),遇到函数进入函数内部。
until 和 jump
until
可以指定程序运行到某一行停下来。jump
可以指定程序执行流跳转到指定位置执行,若跳转的地方没有断点则会继续执行,可以用于测试异常分支代码。
finish 和 return
finish
命令会执行函数到正常退出该函数。return
命令是立即结束执行当前函数并返回,即使当前函数还有剩余的代码未执行完毕,也不会执行。
disassemble
disassemble
命令查看汇编代码,一般用于高级调试选哟查看某段代码的汇编指令,或调试没有调式信息的发版程序。
set args 和 show args
set args
用于传递命令行参数。做法:gdb
附加程序,在run
之前,使用set args 参数内容
来传递命令行参数。show args
用于查看命令行参数是否设置成功。
watch
用于监视一个变量或者一段内存,当其值发生变化时,gdb
会中断下来。被监视的变量或内存会产生一个watch point
(观察点)。
watch命令时通过添加硬件断点来实现监视数据变化,格式:
watch 变量名/内存地址
。整形变量
1
2int i;
watch i指针类型
1
2
3char *p;
watch p
watch *p数组/内存区间(这里是对buf的128个数据监视,不是采用硬件中断,是用软中断实现)
1
2char buf[10];
watch buf程序例子,可见
watch
的变量a
的值在发生变化时会中断1
2
3
4
5
6
7
8
9
10
11
12#include <iostream>
using namespace std;
int main() {
int a = 1;
while (a <= 5)
{
a = a*2;
}
cout << a << endl;
return 0;
}
多进程/线程调试
调试父子进程
gdb
调试父进程,等子进程fork
完成后,使用gdb attach pid
进入子进程,需要重新开启一个窗口用于调试。gdb
调试器提供选项follow-fork
,可以使用show follow-fork mode
查看当前值,也可以通过set follow-fork mode
来设置是当一个进程fork
出新的子进程时,gdb
时继续调试父进程还是子进程(取值child
),默认是父进程(取值parent
)
1
2
3
4
5
6(gdb) show follow-fork mode
Debugger response to a program call of fork or vfork is "parent".
(gdb) set follow-fork child
(gdb) show follow-fork mode
Debugger response to a program call of fork or vfork is "child".
(gdb)spp 线程
- 在
tool
目录下将服务停止 - 进入
bin
目录1
2
3
4
5./spp_xxx_proxy ../etc/spp_proxy.xml
gdb --args ./spp_xxx_worker ../etc/spp_worker1.xml
set follow-fork-mode child
b 设置断点
r 运行程序
- 在