0%

GDB常用指令

GDB常用指令及示例。有关GDB更详细的信息,可以参考GDB官方手册

启动与退出GDB

一般有三种启动gdb的方式。

启动命令 说明
gdb program 在gdb的监控下运行程序program
gdb program coredump 同时加载core dump文件,一般用于分析程序崩溃的原因。
gdb -p pid 调试正在运行的进程,需要有相关的权限。

gdb启动成功后,会进入到gdb交换界面,有提示符(gdb) 。在交换界面中,可以输入一些指令,来对目标程序进行debug。输入指令quit或组合键Ctrl + D可以退出gdb

1
(gdb)

设置断点

断点用于中断程序的运行,这时可以查看程序的当前状态,进行debug。

break ( b )

break指令用于设置断点,断点位置可以是一个符号,或代码的某一行。下面的示例在第13行和main函数分别设置了一个断点。break指令的缩写是b

1
2
3
4
(gdb) break 13
Breakpoint 1 at 0x804807e: file bubble.s, line 13.
(gdb) break main
Breakpoint 2 at 0x8048074: file bubble.s, line 11.

watch

watch指令用于监控表达式的变化。当表达式的值发生变化时,则停止程序。注意,watch只能监控变量,指针或常量地址需要解引用。rwatch监控读操作,awatch监控读写操作。

If you watch for a change in a numerically entered address you need to dereference it, as
the address itself is just a constant number which will never change. gdb refuses to create
a watchpoint that watches a never-changing value:

1
2
3
4
(gdb) watch 0x600850
Cannot watch constant value 0x600850.
(gdb) watch *(int *) 0x600850
Watchpoint 1: *(int *) 6293584

info watchpoints 查看相关信息。

catch

捕获指定事件。常见的事件如下表格所示。tcatch捕获一次后自动删除相关断点。

event 说明
catch exec 调用exec
catch syscall name/number 调用系统调用,可以指定名字或编号。
catch fork 调用fork
catch vfork 调用vfork
catch load 加载共享库
catch unload 卸载共享库
catch signal signal/all 传递信号

arm 系统调用编号查询

delete ( d )

delete指令用于删除断点,参数是断点号。如果忘记断点对应的断点号,可以用指令info break查看所有的断点。下面的示例删除了断点2。delete指令的缩写是d

1
2
3
4
5
6
7
8
(gdb) info break
Num Type Disp Enb Address What
1 breakpoint keep y 0x0804807e bubble.s:13
2 breakpoint keep y 0x08048074 bubble.s:11
(gdb) delete 2
(gdb) info break
Num Type Disp Enb Address What
1 breakpoint keep y 0x0804807e bubble.s:13

clear指令也可以删除断点,参数是断点在程序中的位置。

命中断点后自动执行指令

命中断点后,GDB会暂停执行程序。这时可以执行一些GDB命令,调试程序。如果断点会多次名字,每次都手工敲,会浪费很多时间。可以用commands命令为断点设置命令。在>提示符前输入命令,一行一个,输入end结束。commands number表示给指定断点设置命令。

示例,调用write系统调用时打印backtrace。

1
2
3
4
5
6
7
8
9
10
11
12
13
(gdb) catch syscall write
Catchpoint 1 (syscall 'write' [1])
(gdb) commands
Type commands for breakpoint(s) 1, one per line.
End with a line saying just "end".
>bt
>c
>end
(gdb) info breakpoints
Num Type Disp Enb Address What
1 catchpoint keep y syscall "write"
bt
c

运行程序

run ( r )

GDB启动之后,被调试的程序可能处于未运行的状态,需要使用run指令来启动程序的运行。程序启动之后,会在断点处停下来。run指令的缩写是r

1
2
3
4
5
6
(gdb) run
Starting program: /home/pk/asm/bubble

Breakpoint 1, main () at bubble.s:13
13 movl $len, %ecx
Current language: auto; currently asm

如果没有设置断点,程序会一直运行下去。或者在任何可能阻塞程序运行的地方停下来,如等待用户输入。

continue ( c )

continue指令用于继续运行程序,直到遇到下列情况:

  1. 遇到断点,程序中断运行。
  2. 程序被阻塞,等待继续运行的条件。如等待用户输入。
  3. 程序正常或异常退出。

continue指令的缩写c

next ( n )

单步执行,不进入子函数。

step ( s )

单步执行,进入子函数。

stepi

调试内联汇编时,GDB认为asm段是单一语句。可以使用stepi命令进入asm段进行单步执行,单独地执行每条指令。

指令级别单步

对于C或其他高级语言编写的程序,单步是以语句为单位。可以使用指令set disassemble-next-line on开启指令级别单步,使用stepi(缩写si)或nexti(缩写ni)单步执行。

查看寄存器或内存

查看寄存器或内存的值,默认是十进制,print/x以十六进制打印,print/o以八进制打印。

1
2
3
4
5
6
7
8
9
10
(gdb) p $eax
$1 = 45
(gdb) p/x $eax
$2 = 0x2d
(gdb) p/o $eax
$3 = 055
(gdb) p values # values 是一个变量。
$4 = 45
(gdb) p &values
$5 = (<data variable, no debug info> *) 0x80490b4

寄存器前面要加$,变量无需前缀;取变量地址需前缀&。这和汇编的语法不同。

examine ( x )

xprint指令类似,但支持更多的格式。格式如下:x /nfu address

  • n 内存单元的个数
  • f 显示方式
    • x 十六进制
    • d 十进制
    • u 无符号十进制
    • o 八进制
    • t 二进制
    • a 十六进制
    • i 指令
    • c 字符
    • f 浮点数
  • u 一个地址单元的长度
    • b 单字节
    • h 双字节
    • w 四字节
    • g 八字节

示例:

1
2
3
4
5
6
7
8
9
(gdb) x/7xw &values    # 按十六进制显示7个内存单元,每个内存单元4字节
0x80490b4 <values>: 0x0000002d 0x00000022 0x0000005a 0x0000004e
0x80490c4 <values+16>: 0x00000003 0x00000009 0x0000000a
(gdb) x/5i main # 查看从main开始的5条汇编指令
0x8048074 <main>: mov $0x80490b4,%edi
0x8048079 <main+5>: mov $0x6,%ebx
0x804807e <main+10>: mov $0x6,%ecx
0x8048083 <loop>: mov (%edi),%eax
0x8048085 <loop+2>: cmp %eax,0x4(%edi)

display

当程序停止时自动打印表达式的值,指令格式与指令x类似,但是不支持长度。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
(gdb) display $pc             # 监控 pc 寄存器
(gdb) display *(&values + 4) # 监控values数组的第二个值,语法与C语言一样
(gdb) run
Starting program: /home/pk/asm/bubble

Breakpoint 1, main () at bubble.s:13
13 movl $len, %ecx
2: *(&{<data variable, no debug info>} 134516916 + 4) = 3
1: $pc = (void (*)()) 0x804807e <main+10>
Current language: auto; currently asm
(gdb) n
loop () at bubble.s:15
15 movl (%edi), %eax # load value to eax
2: *(&{<data variable, no debug info>} 134516916 + 4) = 3
1: $pc = (void (*)()) 0x8048083 <loop> # 自动打印监控的值

指令info display查看当前会自动打印的表达式。指令undisplay num可用于取消自动打印。

1
2
3
4
5
6
7
8
9
10
11
(gdb) info display
Auto-display expressions now in effect:
Num Enb Expression
2: y *(&{<data variable, no debug info>} 134516916 + 4)
1: y $pc
(gdb) undisplay 2 # 取消自动打印表达式2
(gdb) info display
Auto-display expressions now in effect:
Num Enb Expression
1: y $pc
(gdb)

backtrace

查看调用栈。

1
2
(gdb) backtrace
#0 loop () at bubble.s:17

源代码

list ( l )

查看源代码。默认显示10行,再执行一次显示接下来的10行。如果接一个行号,则显示以此行为中心的10行。

1
2
3
4
5
6
7
8
9
10
11
(gdb) list 10  # 显示以第10行为中心的10行代码
5
6 .equ len, (values_end - values)/4 -1
7
8 .section .text
9 .global main
10 main:
11 movl $values, %edi
12 movl $len, %ebx
13 movl $len, %ecx
14 loop:

shell指令

shell

shell命令可以在gdb环境中直接执行shell指令,而不必退出gdb

1
2
(gdb) shell date +"%Y-%m-%d"
2021-05-16

make

gdb环境中,可以直接使用make指令,来执行当前目录下的makefile文件。

1
2
(gdb) make bubble
make: “bubble”是最新的。

分析coredump

如果要分析的coredump与主机架构不一致,需要使用交叉工具链中的gdb

使用gdb同时加载可执行文件和coredump文件,然后用backtrace命令打印调用栈,找出导致coredump的代码位置。

1
2
3
gdb elf coredump
......
(gdb) backtrace

一般可执行文件会依赖非常多的库文件,如果gdb找不到库文件,或找到的库文件不带调试信息,会导致gdb无法准确找到错误点。这时可以用命令info shared library找出依赖的所有库文件,然后找到带调试信息的文件,按照rootfs的路径组织在一起,然后用命令set sysroot切换gdb的库文件查找路径。

1
2
(gdb) info shared library
(gdb) set sysroot

GDB脚本

可以将GDB调试命令写在一个文件中,然后使用GDB加载这个文件,可以大大减少输入调试命令的时间,提高调试效率。例如将下面的内容保存为test.gdb,在GDB交互界面使用source test.gdb命令加载GDB脚本。也可以使用--command选项在启动GDB时加载脚本。

1
2
3
4
5
6
7
8
9
set pagination off

catch syscall write
commands
bt
c
end
run
quit
1
gdb --batch --command=test.gdb --args your_program [pram...]

扩展资料:gdbinit

GDB小技巧

代码补全

gdb环境中,如果忘记了指令,或者指令比较长,可以尝试敲tab键,用于提示匹配的指令,或快速输入指令。

如果当前已经输入的内容,只会匹配到一个指令,敲一下tab,剩下的内容会自动补全;如果匹配两个或多个指令,敲两下tab,会输出匹配的所有指令。

1
2
(gdb) con[tab][tab]
condition continue

自动加载共享库

如果使用命令gdb command启动gdb,则gdb不会加载共享库。原因如下。需要执行run命令启动程序,gdb才会加载共享库。

gdb automatically loads symbol definitions from shared libraries when you use the run
command, or when you examine a core file. (Before you issue the run command, gdb
does not understand references to a function in a shared library, however—unless you are
debugging a core file).