GDB基本使用方法

while(i > 0)

​ i++;

基本操作

调试类操作

生成可调试文件

在使用GCC编译时,必须添加-g选项,生成用于调试的符号表,才能使用GCC对可执行文件进行调试。

加载调试文件

1
(gdb) file prog1

输入参数

有些时候我们的程序需要用户输入参数,这种情况下我们可以将输入写入一个文件input.txt当中,然后执行下列语句

1
2
gdb helloworld
(gdb) run params ... < input.txt

单步执行

starti

starti是GDB8.1之后的一个命令,作用是在载入待调试的程序后,在第一个指令前设置一个断点,然后执行到该断点。相当于从第一条指令开始调试。

si

si命令将会执行一条汇编命令

1
2
(gdb) si
mov 0x8(%ebp), %esi # 下一条要执行的语句

需要注意的是,si打印的是下一条要执行的语句

n

n命令会一次执行一个函数,如果没有符号表,那么不能使用n命令。n x表示某一行x次

断点

设置断点

在指定的地址设置断点的语句如下,其中0x0010000c为程序执行的断点

1
2
(gdb) br * 0x0010000c
Breakpoint 1 at 0x10000c

其中,断点可以是一个实际内存地址,也可以是描述符(”mon_backtrace”或者”monitor.c:71”)。注意,如果使用描述符,那么在编译的时候gcc必须给定-g选项并重新编译。

1
2
(gdb) br monitor.c:71      #在monitor.c 71行设置断点
(gdb) br mem_init #在mem_init处设置断点

如果想要在特定条件下触发断点,可以使用条件断点,命令如下:

1
(gdb) br *0x0010000c if

这里总结一下断点的类型:

  • 特定内存地址
  • 描述符(例如某个具体函数)
  • 行号(某个文件的行号)
查看断点
1
info b
删除/禁用/使能断点
1
2
3
delete 1      # 删除第一个断点
enable 3
disable 2

监视点

watchpoints(监视点)和端点不同,当检测到某个表达式或变量的值发生改变时,将会停止运行

1
2
3
(gdb) watch <expression>   #如果指定表达式内容改变,则停止
(gdb) watch -l <address> #如果指定内存内容改变,则停止
(gdb) rwatch <expression> #如果指定表达式被读取,那么停止

继续执行类

继续直行至断点

继续执行命令为c,程序会执行至下一个断点处

1
2
3
4
(gdb) c
Continuing.
The target architecture is assumed to be i386
=> 0x10000c: mov %cr4,%eax
继续直行至当前函数返回
1
(gdb) finish

如果不在函数内,那么finish没有意义

继续执行至特定位置
1
2
(gdb) advance _start
(gdb) advance *0x0010000c

advance 将会执行代码至特定的符号或地址所在位置处

符号相关

切换符号文件
1
symbol-file obj/kern/kernel   # 这个命令将会加载kernel中的符号

显示类操作

反汇编

反汇编指令为x/N addr,该指令可以将addr开始的N个连续指令进行反汇编

1
2
3
4
5
6
7
8
9
10
11
(gdb) x/10 0x7c00
=> 0x7c00: cli
0x7c01: cld
0x7c02: xor %ax,%ax
0x7c04: mov %ax,%ds
0x7c06: mov %ax,%es
0x7c08: mov %ax,%ss
0x7c0a: in $0x64,%al
0x7c0c: test $0x2,%al
0x7c0e: jne 0x7c0a
0x7c10: mov $0xd1,%al

查看内存

x命令

使用x/Nxw addr可以以16进制(后面的x)查看从addr开始的N个word大小的内存中的值

1
2
3
(gdb) x/8x 0x00100000
0x100000: 0x1badb002 0x00000000 0xe4524ffe 0x7205c766
0x100010: 0x34000004 0x1000b812 0x220f0011 0xc0200fd8

有些情况下要看栈中内存的情况,由于栈是向下生长的,所以我们可以用

1
(gdb) x/-8x 0x00100000

反向打印内存。如果想查看当前内存,可以使用x/i $pc

p命令

相比于x命令,p命令能够检验一个c语言表达式,并用合适的类型对结果进行打印,例如我们要打印某个内存位置处特定类型的变量,可以使用下面的语句:

1
p *((struct Elf*)0x10000)

这个命令将会打印从0x10000开始的Elf类型的变量,其结果非常清晰,可以将结构体中的所有内容展示出来:

1
$1 = {e_magic = 0, e_elf = '\000',...}

info命令

info命令能够查看相关信息,功能比较全,所以单独列出

查看寄存器的值
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
// 查看所有寄存器
(gdb) info reg
eax 0x0 0
ecx 0x0 0
edx 0x663 1635
ebx 0x0 0
esp 0x0 0x0
ebp 0x0 0x0
esi 0x0 0
edi 0x0 0
eip 0xfff0 0xfff0
eflags 0x2 [ IOPL=0 ]
cs 0xf000 61440
ss 0x0 0
ds 0x0 0
es 0x0 0
fs 0x0 0
gs 0x0 0
fs_base 0x0 0
gs_base 0x0 0
k_gs_base 0x0 0
cr0 0x60000010 [ CD NW ET ]
cr2 0x0 0
cr3 0x0 [ PDBR=0 PCID=0 ]
cr4 0x0 [ ]
cr8 0x0 0
efer 0x0 [ ]

// 查看某个寄存器
(gdb) info reg eax
查看c语言类型
1
info types
查看变量
1
2
3
info variables # 打印全局变量
info locals # 打印局部变量
info args # 打印当前frame的参数(函数输入参数)
查看当前函数栈
1
2
3
4
5
6
7
8
9
(gdb) info frame
Stack level 0, frame at 0xb75f7390:
eip = 0x804877f in base::func() (testing.cpp:16); saved eip 0x804869a
called by frame at 0xb75f73b0
source language c++.
Arglist at 0xb75f7388, args: this=0x0
Locals at 0xb75f7388, Previous frame's sp is 0xb75f7390
Saved registers:
ebp at 0xb75f7388, eip at 0xb75f738c

其中:

  • stack level 0:表示frame的序号
  • frame at 0xb75f7390:表示栈的起始地址
  • eip = 0x804877f in base::func() (testing.cpp:16); saved eip 0x804869a
    • eip = 0x804877f表示下一条语句在0x804877fc处,在testing.cpp的16行
    • saved eip 0x804869a表示函数返回地址,即返回后执行0x804869a
  • called by frame at 0xb75f73b0:表示调用者的栈位于0xb75f73b0
  • Arglist at 0xb75f7388, args: this=0x0:表示参数的起始地址以及参数
  • Locals at 0xb75f7388:表示局部变量的位置
  • Previous frame’s sp is 0xb75f7390:表示前一个栈帧(调用者栈的栈顶或被调用者栈的栈底)的栈顶
  • 还有一些保存的寄存器的值

查看源码

使用list可以查看某个位置处的源码

1
(gdb) list 123   # 打印源文件123行开始的几行代码

查看函数调用栈

使用bt命令可以查看函数的调用栈

1
2
3
4
(gdb) bt
#0 readseg (pa=65536, count=4096,offset=0) at boot/main.c:73
#1 0x00007d26 in bootmain() at boot/main.c:44
#2 0x00007c4a in protcseg() at boot/boot.S:69

每调用一个函数,则压入现在所在的函数,同时前面的序号依次递增,最上面的就是当前所在的函数,其中,序号后面的0x00007d26为函数栈中函数的返回地址,即被保存的eip值。

查看具体变量

当程序中断时,我们可以使用p命令查看具体的变量,例如现在我们所在的函数有一个输入参数cmd,是一个结构体类型指针,那么我们可以用如下命令查看指针值及结构体中内容:

1
2
3
4
5
(gdb) print cmd
$3 = (struct cmd *) 0x555555559ac0

(gdb) print *cmd
$5 = {type = 32}

一个完整的例子

首先,我们要确保在gcc编译过程中加入了-g选项,生成调试信息,否则gdb调试过程中能够查看的信息很少。

1
gcc main.c -o build -g

然后打开gdb,

参考文献

0%