使用 gdb 进行基本调试
编译
在编译时,使用 -g
选项进行编译
$ gcc -g program.c -o programname
启动调试
在 gdb 中启动程序
$ gdb programname
(gdb) run arg1 "arg2" ...
在 gdb 中重启程序
(gdb) kill
Kill the program being debugged? (y or n) y
(gdb) run
退出 gdb
(gdb) quit
The program is running. Exit anyway? (y or n)
调试已经正在运行的程序,需要使用到该程序的 PID,假设运行的程序 PID 为 20289,则使用 gdb 进行调试
$ gdb
(gdb) attach 20829
或者,简单直接调用
$ gdb att 20289
也可以直接使用 gdb program pid
的形式,比如
$ gdb hello 20829
单步调试
单步执行
(gdb) n
单步执行,进入函数
(gdb) s
跳出函数
(gdb) finish
执行跳至指定行号
(gdb) until [num]
查看代码
查看当前代码的运行位置
(gdb) where
#0 event_base_loop (base=0x602010, flags=0) at event.c:466
#1 0x00007ffff7bbb5c7 in event_base_dispatch (event_base=0x602010) at event.c:405
#2 0x0000000000400858 in main () at test.c:20
查看源代码
(gdb) l
上述命令默认每次显示 10 行源代码,可以设置源码单次显示的行数
(gdb) set listsize 20
(gdb) show listsize
Number of source lines gdb will list by default is 20.
列出指定行附近的源代码
(gdb) l main.cpp:8
列出指定函数附近的源代码
(gdb) l printnum
列出指定行之间的源代码
(gdb) l 3,15
执行 shell 命令
在 gdb 的命令行界面可以执行外部的 shell 命令
(gdb) shell ls
a.out test.c
断点
断点的设置原理:在程序中设置断点,就是先将该位置的原来的指令保存,然后向该位置写入 int 3
指令,当执行到 int 3
的时候,发生软中断,内核会给子进程发出 SIGTRAP
信号,当然这个信号会被转发给父进程。然后用保存的指令替换 int 3
,等待恢复运行。
断点的实现原理:就是在指定的位置插入断点指令,当被调试的程序运行到断点的时候,产生 SIGTRAP
信号,该信号被 gdb 捕获并进行断点命中判定,当 gdb 判断出这次 SIGTRAP
是断点命中之后就会转入等待用户输入进行下一步处理,否则继续。
查看断点
(gdb) info b
Num Type Disp Enb Address What
4 breakpoint keep y 0x000000000040053f in main at test.c:6
设置行号断点
(gdb) break test.c:8
设置 C 函数断点
(gdb) break func1
设置 C++ 函数断点
(gdb) break TestClass::testFunc(int)
设置临时断点,临时断点只会断住一次,然后断点会自动被移除掉
(gdb) tbreak 8
设置条件断点
b 11 if i == 3
修改条件断点的条件,假设断点的号为 4
condition 4 i == 0
去使能断点
(gdb) disable 2
(gdb) info breakpoints
Num Type Disp Enb Address What
2 breakpoint keep n 0x080483c3 in func2 at test.c:5
3 breakpoint keep y 0x080483da in func1 at test.c:10
跳过断点,假设在某个地方,我们知道可能出错,前面 30 次都没有问题,在这里我们设置了断点,但是我们想跳过前面 30 次,可以使用 ignore
命令,如下:第一个参数表示断点编号,第二个表示跳过次数。
(gdb) ignore 2 5
Will ignore next 5 crossings of breakpoint 2.
保存断点
(gdb) save breakpoints file-name-to-save
恢复保存的断点
(gdb) source file-name-to-save
堆栈
查看调用栈
(gdb) bt
#0 func2 (x=30) at test.c:5
#1 0x80483e6 in func1 (a=30) at test.c:10
#2 0x8048414 in main (argc=1, argv=0xbffffaf4) at test.c:19
#3 0x40037f5c in __libc_start_main () from /lib/libc.so.6
(gdb)
选择栈帧,可以简写为 f 2
(gdb) frame 2
#2 0x8048414 in main (argc=1, argv=0xbffffaf4) at test.c:19
19 x = func1(x);
(gdb)
向上或者向下切换函数堆栈
(gdb) up 2
(gdb) down 2
查看栈帧
(gdb) info frame
Stack level 2, frame at 0xbffffa8c:
eip = 0x8048414 in main (test.c:19); saved eip 0x40037f5c
called by frame at 0xbffffac8, caller of frame at 0xbffffa5c
source language c.
Arglist at 0xbffffa8c, args: argc=1, argv=0xbffffaf4
Locals at 0xbffffa8c, Previous frame's sp is 0x0
Saved registers:
ebp at 0xbffffa8c, eip at 0xbffffa90
(gdb) info locals
x = 30
s = 0x8048484 "Hello World!\n"
(gdb) info args
argc = 1
argv = (char **) 0xbffffaf4
变量
最常见的就是使用 print 打印变量内容,其中 print 可以简写为 p
(gdb) p a
可以在变量名之前加上函数名来区分不同的同名变量
(gdb) p 'main'::b
可以按照特定的格式打印变量
-
x
按照十六进制格式打印变量 -
d
按照十进制格式打印变量 -
o
按照八进制格式打印变量 -
t
按照二进制格式打印变量 -
c
按照字符格式打印变量 -
f
按照浮点数格式打印变量
(gdb) p/x mask
$16 = 0x1
显示动态数组的内容,比如动态数组内存申请为 int *array = (int *) malloc (len * sizeof (int));
(gdb) p *array@len
自动显示变量,希望在程序被断住时,就显示某个变量的值,可以使用 display
命令
(gdb) display e
查看哪些变量设置了自动显示
(gdb) info display
清除自动显示变量
(gdb) del display [num]
去使能自动显示变量
(gdb) disable display [num]
使用 gdb 的高阶调试方法
查看内存
查看内存的命令格式为 x/[n][f][u] [address]
n
表示显示内存长度,默认值为 1
f
表示显示格式,如同上面打印变量定义
-
x
按十六进制格式显示变量 -
d
按十进制格式显示变量 -
u
按十六进制格式显示无符号整型 -
o
按八进制格式显示变量 -
t
按二进制格式显示变量 -
a
按十六进制格式显示变量 -
c
按字符格式显示变量 -
f
按浮点数格式显示变量
u
表示每次读取的字节数,默认是 4bytes
-
b
表示单字节 -
h
表示双字节 -
w
表示四字节 -
g
表示八字节
以字符串形式查看
char *s = "hello world"; // 源码
(gdb) x/s s
0x4005a0: "hello world"
以字符形式查看
(gdb) x/c s
0x4005a0: 104 'h'
以二进制形式查看
(gdb) x/t s
0x4005a0: 01101000
查看寄存器
(gdb) info registers
rax 0x4004f0 4195568
rbx 0x0 0
rcx 0x400510 4195600
rdx 0x7fffffffe598 140737488348568
rsi 0x7fffffffe588 140737488348552
rdi 0x1 1
rbp 0x7fffffffe4a0 0x7fffffffe4a0
rsp 0x7fffffffe4a0 0x7fffffffe4a0
r8 0x7ffff7dd6e80 140737351872128
r9 0x0 0
r10 0x7fffffffe2f0 140737488347888
r11 0x7ffff7a3ca40 140737348094528
r12 0x400400 4195328
r13 0x7fffffffe580 140737488348544
r14 0x0 0
r15 0x0 0
rip 0x400503 0x400503 <main+19>
eflags 0x246 [ PF ZF IF ]
cs 0x33 51
ss 0x2b 43
ds 0x0 0
es 0x0 0
fs 0x0 0
gs 0x0 0
查看汇编代码
(gdb) disassemble main
Dump of assembler code for function main:
0x00000000004004f0 <+0>: push %rbp
0x00000000004004f1 <+1>: mov %rsp,%rbp
0x00000000004004f4 <+4>: mov %edi,-0x14(%rbp)
0x00000000004004f7 <+7>: mov %rsi,-0x20(%rbp)
0x00000000004004fb <+11>: movq $0x4005a0,-0x8(%rbp)
=> 0x0000000000400503 <+19>: pop %rbp
0x0000000000400504 <+20>: retq
End of assembler dump.
信号
gdb 可以在调试程序的时候处理任何一种信号,即设置 gdb 需要处理哪种信号,并设置在 gdb 接收到信号后,立即停止程序,忽略信号等操作。
设置信号的格式为 handle <signal> <keyword>
keyword
可以为
-
nostop
接收到信号后,gdb 不停止程序,但是会打印消息,通知用户收到了某个信号 -
stop
接收到信号后,gdb 停止程序 -
noignore
接收到信号后,gdb 不处理信号,将其交给调试程序处理 -
ignore
接收到信号后,gdb 不让调试程序处理这个信号
多线程调试
查看当前进程的所有线程
(gdb) info threads
切换线程栈
(gdb) thread [线程号]
打印所有的线程堆栈
(gdb) thread apply all bt
打印指定线程堆栈
(gdb) thread apply 5 bt # 5为线程id
用 gdb 调试多线程程序时,一旦程序断住,所有的线程都处于暂停状态。此时当你调试其中一个线程时(比如执行“step”,“next”命令),所有的线程都会同时执行,有时候我们希望执行流一直在某个线程执行,而不是切换到其他线程。
我们可以使用命令 set scheduler-locking on/off/step
set scheduler-locking on
可以用来锁定当前线程,只观察这个线程的运行情况, 当锁定这个线程时, 其他线程就处于了暂停状态。也就是说你在当前线程执行 next、step、until、finish、return 命令时,其他线程是不会运行的。
set scheduler-locking off
用于关闭锁定当前线程。
set scheduler-locking step
也是用来锁定当前线程,当且仅当使用 next 或 step 命令做单步调试时会锁定当前线程。如果你使用 until、finish、return 等线程内调试命令,但是它们不是单步命令,所以其他线程还是有机会运行的。相比较 on 选项值,step 选项值给为单步调试提供了更加精细化的控制。
网友评论