GDB 是由 GNU 软件系统社区提供的调试工具,同 GCC 配套组成了一套完整的开发环境,GDB 是 Linux 和许多类 Unix 系统中的标准开发环境
GDB调试的三种方式:
-
目标板直接使用GDB进行调试。
-
目标板使用gdbserver,主机使用xxx-linux-gdb作为客户端。
-
目标板使用ulimit -c unlimited
,生成core文件;然后主机使用xxx-linux-gdb ./test ./core
。
GDB 调试
构造测试程序如下main.c和sum.c如下:
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
|
// main.c:
#include <stdio.h>
#include <stdlib.h>
extern int sum(int value);
struct inout {
int value;
int result;
};
int main(int argc, char * argv[])
{
struct inout * io = (struct inout * ) malloc(sizeof(struct inout));
if (NULL == io) {
printf("Malloc failed.\n");
return -1;
}
if (argc != 2) {
printf("Wrong para!\n");
return -1;
}
io -> value = *argv[1] - '0';
io -> result = sum(io -> value);
printf("Your enter: %d, result:%d\n", io -> value, io -> result);
return 0;
}
// sum.c:
int sum(int value) {
int result = 0;
int i = 0;
for (i = 0; i < value; i++)
result += (i + 1);
return result;
}
|
然后gcc main.c sum.c -o main -g
, 得到main可执行文件, 输入gdb main
可进入调试.
下面介绍了gdb大部分功能,1.1 设置断点以及 1.3显示栈帧是常用功能;调试过程中可以需要1.6 单步执行,并且1.4 显示变量、1.5显示寄存器、1.8 监视点、1.9 改变变量的值。
如果进程已经运行中,需要1.11 attach到进程,或者1.10 生成转储文件进行分析。当然为了提高效率可以自定义1.13 初始化文件。
设置断点*
设置断点可以通过b或者break设置断点,断点的设置可以通过函数名、行号、文件名+函数名、文件名+行号以及偏移量、地址等进行设置。
格式为:
break 函数名
break 行号
break 文件名:函数名
break 文件名:行号
break +偏移量
break -偏移量
break *地址
查看断点,通过info break
查看断点列表。
1
2
3
4
5
6
7
8
|
(gdb) b 13
Breakpoint 1 at 0x11aa: file main.c, line 13.
(gdb) b sum.c:2
Breakpoint 2 at 0x123d: file sum.c, line 2.
(gdb) info b
Num Type Disp Enb Address What
1 breakpoint keep y 0x00000000000011aa in main at main.c:13
2 breakpoint keep y 0x000000000000123d in sum at sum.c:2
|
删除断点通过命令包括:
delete <断点id>:删除指定断点
delete:删除所有断点
clear
clear 函数名
clear 行号
clear 文件名:行号
clear 文件名:函数名
断点还可以条件断住
break 断点 if 条件;比如break sum if value==9,当输入的value为9的时候才会断住。
condition 断点编号:给指定断点删除触发条件
condition 断点编号 条件:给指定断点添加触发条件
断点还可以通过disable/enable
临时停用启用。
disable
disable 断点编号
disable display 显示编号
disable mem 内存区域
enable
enable 断点编号
enable once 断点编号:该断点只启用一次,程序运行到该断点并暂停后,该断点即被禁用。
enable delete 断点编号
enable display 显示编号
enable mem 内存区域
断点commands高级功能
大多数时候需要在断点处执行一系列动作,gdb提供了在断点处执行命令的高级功能commands。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
#include <stdio.h>
int total = 0;
int square(int i)
{
int result=0;
result = i*i;
return result;
}
int main(int argc, char **argv)
{
int i;
for(i=0; i<10; i++)
{
total += square(i);
}
return 0;
}
|
比如需要对如上程序square参数i为5的时候断点,并在此时打印栈、局部变量以及total的值
编写gdb.init如下:
1
2
3
4
5
6
7
8
9
10
|
set logging on gdb.log
b square if i == 5
commands
bt full
i locals
p total
print "Hit break when i == 5"
end
|
在gdb shell中source gdb.init
,然后r执行命令,结果如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
(gdb) source gdb.init
Breakpoint 1 at 0x1129: file commands.c, line 6.
(gdb) r
Starting program: /home/lwy/workspace/gdbtest/commands
Breakpoint 1, square (i=5) at commands.c:6
6 {
#0 square (i=5) at commands.c:6
result = 25
#1 0x000055555555516f in main (argc=1, argv=0x7fffffffe048) at commands.c:20
i = 6
result = 25
$1 = 55
$2 = "Hit break when i == 5"
|
可以看出断点在i==5的时候断住了,并且此时打印了正确的值。
运行
“gdb 命令
”之后,run可以在gdb下运行命令;如果命令需要参数则跟在run之后。
1
2
3
4
|
(gdb) run 9
Starting program: /home/lwy/workspace/gdbtest/main 9
Your enter: 9, result:45
[Inferior 1 (process 12362) exited normally]
|
如果需要断点在main()处,直接执行start
就可以。
1
2
3
4
5
|
(gdb) start
Temporary breakpoint 1 at 0x555555555189: file main.c, line 11.
Starting program: /home/lwy/workspace/gdbtest/main 9
Temporary breakpoint 1, main (argc=21845, argv=0x0) at main.c:11
|
显示栈帧*
如果遇到断点而暂停执行,或者coredump
可以显示栈帧。
通过bt可以显示栈帧,bt full
可以显示局部变量。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
(gdb) r 9
Starting program: /home/lwy/workspace/gdbtest/main 9
Breakpoint 2, sum (value=9) at sum.c:3
3 int i = 0;
(gdb) bt
#0 sum (value=9) at sum.c:3
#1 0x0000555555555204 in main (argc=2, argv=0x7fffffffe048) at main.c:24
(gdb) bt full
#0 sum (value=9) at sum.c:3
result = 0
i = 32767
#1 0x0000555555555204 in main (argc=2, argv=0x7fffffffe048) at main.c:24
io = 0x5555555592a0
|
命令格式如下:
bt
bt full:不仅显示backtrace,还显示局部变量
bt N:显示开头N个栈帧
bt full N
显示变量*
print 变量名
可以显示变量内容。
ptype 变量名
可以打印变量类型
如果需要一行监控多个变量,可以通过p {var1, var2, var3}
。
如果要跟踪自动显示,可以使用display {var1, var2, var3}
取消跟踪用 undisplay 编号
查看文件代码:list/l [文件名:][行号/函数名]
设置显示的行数:show list/listsize
, set list/listsize 行数
显示寄存器*
info reg
可以显示寄存器内容。
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
|
(gdb) i r
rax 0x9 9
rbx 0x555555555270 93824992236144
rcx 0x5555555592b0 93824992252592
rdx 0x9 9
rsi 0x0 0
rdi 0x9 9
rbp 0x7fffffffdf20 0x7fffffffdf20
rsp 0x7fffffffdf20 0x7fffffffdf20
r8 0x5555555592a0 93824992252576
r9 0x7ffff7dd1070 140737351848048
r10 0x7ffff7fb9be0 140737353849824
r11 0x7ffff7fb9be0 140737353849824
r12 0x5555555550a0 93824992235680
r13 0x7fffffffe040 140737488347200
r14 0x0 0
r15 0x0 0
rip 0x55555555524b 0x55555555524b <sum+25>
eflags 0x216 [ PF AF IF ]
cs 0x33 51
ss 0x2b 43
ds 0x0 0
es 0x0 0
fs 0x0 0
gs 0x0 0
|
在寄存器名之前加$可以显示寄存器内容,
p $寄存器:显示寄存器内容
p/x $寄存器:十六进制显示寄存器内容。
1
2
3
4
|
(gdb) p $ss
$2 = 43
(gdb) p/x $pc
$3 = 0x55555555524b
|
用x命令可以显示内容内容,x/格式 地址
。
x $pc:显示程序指针内容
x/i $pc:显示程序指针汇编。
x/10i $pc:显示程序指针之后10条指令。
x/128wx 0xfc207000:从0xfc20700开始以16进制打印128个word。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
(gdb) x $pc
0x55555555524b <sum+25>: 0x00fc45c7
(gdb) x/i $pc
=> 0x55555555524b <sum+25>: movl $0x0,-0x4(%rbp)
(gdb) x/10i $pc
=> 0x55555555524b <sum+25>: movl $0x0,-0x4(%rbp)
0x555555555252 <sum+32>: jmp 0x555555555261 <sum+47>
0x555555555254 <sum+34>: mov -0x4(%rbp),%eax
0x555555555257 <sum+37>: add $0x1,%eax
0x55555555525a <sum+40>: add %eax,-0x8(%rbp)
0x55555555525d <sum+43>: addl $0x1,-0x4(%rbp)
0x555555555261 <sum+47>: mov -0x4(%rbp),%eax
0x555555555264 <sum+50>: cmp -0x14(%rbp),%eax
0x555555555267 <sum+53>: jl 0x555555555254 <sum+34>
0x555555555269 <sum+55>: mov -0x8(%rbp),%eax
|
还可以通过disassemble
指令来反汇编。
disassemble
disassemble 程序计数器 :反汇编pc所在函数的整个函数。
disassemble addr-0x40,addr+0x40:反汇编addr前后0x40大小。
单步执行*
单步执行有两个命令next
和step
,缩写为n
和s
,两者的区别是next
遇到函数不会进入函数内部,step
会执行到函数内部。
finish
(跳出函数体)
如果需要逐条汇编指令执行,可以分别使用nexti
和stepi
。
继续执行*
调试时,使用continue
命令(缩写:c
)继续执行程序。程序遇到断电后再次暂停执行;如果没有断点,就会一直执行到结束。
continue:继续执行
continue 次数:继续执行一定次数。
监视点*
要想找到变量在何处被改变,可以使用watch
命令设置监视点watchpoint。
watch <表达式>:表达式发生变化时暂停运行
awatch <表达式>:表达式被访问、改变是暂停执行
rwatch <表达式>:表达式被访问时暂停执行
1
2
3
4
5
6
7
8
|
(gdb) watch i
Hardware watchpoint 3: i
(gdb) i b
Num Type Disp Enb Address What
2 breakpoint keep y 0x0000555555555244 in sum at sum.c:3
stop only if value==9
breakpoint already hit 1 time
3 hw watchpoint keep y
|
改变变量的值*
通过set variable <变量>=<表达式>
来修改变量的值。
简写set var 变量名=变量值
(循环中用的多)
until
(跳出循环)
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
|
(gdb) b main
Breakpoint 4 at 0x555555555189: file main.c, line 11.
(gdb) r 9
Starting program: /home/lwy/workspace/gdbtest/main 9
Breakpoint 4, main (argc=21845, argv=0x0) at main.c:11
11 {
(gdb) n
12 struct inout * io = (struct inout * ) malloc(sizeof(struct inout));
(gdb) n
13 if (NULL == io) {
(gdb) n
18 if (argc != 2) {
(gdb) n
23 io -> value = *argv[1] - '0';
(gdb) n
24 io -> result = sum(io -> value);
(gdb) print io->value
$4 = 9
(gdb) set variable io->value=10
(gdb) n
25 printf("Your enter: %d, result:%d\n", io -> value, io -> result);
(gdb) n
Your enter: 10, result:55
26 return 0;
|
set $r0=xxx
:设置r0寄存器的值为xxx。
生成内核转储文件*
通过generate-core-file
生成core.xxxx转储文件。
然后gdb ./main ./core.xxxx
查看恢复的现场。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
(gdb) generate-core-file
warning: target file /proc/14188/cmdline contained unexpected null characters
Saved corefile core.14188
lwy@lwysLaptop:~/workspace/gdbtest$ gdb ./main ./core.14188
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./main...
[New LWP 14188]
Core was generated by `/home/lwy/workspace/gdbtest/main 9'.
Program terminated with signal SIGTRAP, Trace/breakpoint trap.
#0 main (argc=2, argv=0x7fffffffe048) at main.c:26
26 return 0;
|
另一命令gcore
可以从命令行直接生成内核转储文件。
gcore pidof 命令
:无需停止正在执行的程序以获得转储文件。
attach到进程*
如果程序已经运行,或者是调试陷入死循环而无法返回控制台进程,可以使用attach
命令。
attach pid
通过ps aux可以查看进程的pid,然后使用bt查看栈帧。
以top为例操作步骤为:
-
ps -aux查看进程pid,为16974.
-
sudo gdb attach 16974,使用gdb 附着到top命令。
-
使用bt full查看,当前栈帧。此时使用print等查看信息。
-
还可以通过info proc查看进程信息。
反复执行
continue、step、stepi、next、nexti都可以指定重复执行的次数。
ignore 断点编号 次数
:可以忽略指定次数断点。
dump内存到指定文件
在gdb调试中可能需要将一段内存导出到文件中,可以借助dump
命令。
命令格式:
dump binary memory FILE START STOP
比如dump binary memory ./dump.bin 0x0 0x008000000
,将内存区间从0x0到0x00800000导出到dump.bin中。
多进程调试
使用 GDB 调试的时候,GDB 默认只能跟踪一个进程,可以在 fork 函数调用之前,通过指令设置 GDB 调试工具跟踪父进程或者是跟踪子进程,默认跟踪父进程。
设置调试父进程或者子进程:set follow-fork-mode [parent(默认)| child]
设置调试模式:set detach-on-fork [on | off]
默认为 on,表示调试当前进程的时候,其它的进程继续运行,如果为 off,调试当前进程的时候,其它进程被 GDB 挂起。
查看调试的进程:info inferiors
切换当前调试的进程:inferior id
使进程脱离 GDB 调试:detach inferiors id