GDB,即GNU项目的调试器,允许在程序执行时查看其内部情况,或者在程序崩溃时了解其当时的运行状态。GDB可以在大多数流行的UNIX和Microsoft Windows变体上运行,以及在macOS上运行。
本文简要总结了常见的GDB命令和部分高级特性。
花了1个月零散时间码出来的,分享出来以供交流。
当程序被编译和链接时,关于变量名、函数名等的信息(符号表)会丢失——计算机不需要它们来执行你的程序。然而,GDB确实需要它们。用-g开关编译/链接你的程序, 这样让编译器/链接器保存符号表信息。
例如(设现在有一个由hello.c编译而成可执行文件hello):
gcc -g -o hello hello.c
如果在没有使用-g编译/链接的程序上运行GDB,则会看到错误消息:“No symbol table is loaded. Use the “file” command.”。
文章目录
- 1.Starting and stopping GDB
- 2.常用GDB命令
- 3.Basic Debugging(基础操作)
- 4.Breakpoints and watchpoints (断点)
- 5.Continue and step (继续执行)
- 6.Examining the stack(检查栈)
- 7.Examining source code(检查源代码)
- 8.Examining Data(检查数据)
- 9.Examining Memory(检查内存)
- 10.Registers(寄存器)
- 11.Examining the symbol table(检查符号表)
- 12.Altering Execution(修改执行)
- 13.Miscellaneous(其他)
1.Starting and stopping GDB
gdb exe
在gdb下运行你的程序, 关于参数稍后再讨论:
gdb hello
离开gdb: quit (或者 q)
gdb attach pid
针对当前正在运行的程序,pid是它的进程号,可以通过top命令或pgrep exe或**echo $(pidof exe)**来查看. 其中exe是可执行程序的名字。
2.常用GDB命令
命令 | 解析 |
---|---|
b | 设置断点 |
c | 继续执行 |
l | 展示源码 |
n | 执行下一行代码 |
p | 输出参数值 |
q | 退出gdb |
r | 从头运行程序 |
s | 执行下一步 |
x | 检查内存 |
下面是命令的详细介绍。
为了更好理解命令,下文的所有例子都基于一个简陋的c语言代码(当输入n过大时,有段错误,以供大家调试):
1 #include <stdio.h>
2 #include <stdlib.h>
3 #define STR_MAX_SIZE 100
4
5 int sumFunc (int n, int *sum, char* str){
6
7 int cnt = 0;
8
9 *sum = 0;
10 int str_len = 0;
11
12 str_len += snprintf(str + str_len, STR_MAX_SIZE - str_len, "{");
13
14 for (int i=0; i<=n; ++i){
15 *sum += i;
16 str_len += snprintf(str + str_len, STR_MAX_SIZE - str_len, "%d + ", i);
17 }
18 snprintf(str + str_len - 3, STR_MAX_SIZE - str_len + 3, "} = %d", *sum);
19
20 return cnt;
21 }
22
23
24 int main(int argc, char *argv[]) {
25
26 if(argc < 2){
27 printf("err: lack para\n");
28 return 0;
29 }
30 int n = atoi(argv[1]);
31
32 int *sum;
33 char str[STR_MAX_SIZE];
34 int ret = sumFunc(n, sum, str);
35
36 printf("Sum: %d\n%s\n", *sum, str);
37
38 return 0;
39 }
3.Basic Debugging(基础操作)
run ( r )
r
使用 r 命令开始运行程序。
r 参数
若程序运行需要参数,可以键入 r 12 bob,其中 12 和 “bob” 是main需要的参数。
r < filename
同时也可从用户文件中读取。
备注:在gdb中可以随时使用 r 来重新运行程序。
help
help [命令]
查看命令介绍
4.Breakpoints and watchpoints (断点)
使用gdb的主要目的是为了在程序结束前能够暂停它,或者当程序出现问题时,能够查清问题所在。
在gdb中,程序的暂停可能是由多种原因造成的,比如接收到信号、触发断点,或者在执行了GDB的命令(如单步执行)后到达程序的新行。此时,您可以检查和修改变量的值,设置新的断点或清除旧的断点,然后继续程序的执行。通常,GDB提供的消息足以说明程序的状态——但如果需要,也可随时显式查询这些信息。
断点的作用是告知GDB,当程序执行到特定位置时应当暂停。对于每个断点,您可以设定条件,来更精确地控制程序是否在该点停止。通过使用break命令及其变体,您可以指定程序应在特定的行号、函数名或程序地址处暂停。
break (b)
break 行号
break 函数名
break
要设置断点,请使用 break
(缩写为 b
)命令。break
命令的参数可以是行号、函数名或指令的地址。如果没有提供参数,break
命令将在即将执行的下一个指令处设置断点。
其他:break hello.c:行号(函数名) 也是可以的,这里hello.c是源代码文件名。
break 位置 if 条件
断点可以有一个关联的布尔条件。只有当布尔条件(条件用类似于c的语法编写,使用== !=和<等操作符)为真时,GDB才会在断点处(位置为行号、函数名等)停止程序的执行。
tb
临时断点设置,即被命中一次后就被删除失效。用法和b相同。
例子:
(gdb) b 34 if n==5
Breakpoint 1 at 0x40129a: file hello.c, line 34.
(gdb) r 3
Starting program: /opt/work/hello/hello 3
Missing separate debuginfos, use: debuginfo-install glibc-2.17-323.el7_9.x86_64
Sum: 6
{0 + 1 + 2 + 3} = 6
[Inferior 1 (process 14118) exited normally]
(gdb) r 5
Starting program: /opt/work/hello/hello 5
Breakpoint 1, main (argc=2, argv=0x7fffffffdaa8) at hello.c:34
34 int ret = sumFunc(n, sum, str);
注意:这有一个类似c语言的语法陷阱,假如输入b 34 if n = 5
, 此时gdb会在34行执行n=5的赋值语句。若感兴趣可自行验证。
info
info b
可以通过info b命令获取断点信息。
info watchpoints
可以通过info watchpoints命令获取观察点信息。
info locals
可以显示局部变量值。
info frame
显示函数栈信息
info args
显示函数的形参及其值
例子:
(gdb) info b
Num Type Disp Enb Address What
1 breakpoint keep y 0x000000000040129a in main at hello.c:34
stop only if n == 5
breakpoint already hit 1 time
delete (d)
d 断点编号
d breakpoints
使用d命令可以删除相应断点, d breakpoints 是删除所有断点。
注意:d breakpoints 同时也会删除已设置的watchpoints。watchpoints在后面介绍。
watch
watch expr
可以使用watch命令来在表达式的值发生变化时停止程序执行,而无需提前预测这种情况发生的位置。表达式可以是单个变量的值,也可以由多个变量通过运算符组合而成的。例如:
-
单个变量值的引用
-
转换为适当数据类型的地址。例如,`*(int*)0x12345678’将在指定地址上监视一个4字节的区域(假设int类型占用4字节)
-
任意复杂的表达式,如‘ a*b + c/d’。表达式可以采用程序母语中有效的任何操作符
-
表达式上设置观察点,即使表达式还不能求值的时候。
info watchpoints
同 breakpoints, info 命令同样可以获得观察点信息。同时 d 观察点编号 命令也可以删除观察点.
注意:d breakpoints 可同时删除all breakpoints and wathpoints。 但是我没有找到仅删除all watchpoints的命令。同时watch不同于breakpoint, 当程序run结束后wathcpoints的设置全部消失,但是breakpoints仍然有效。
5.Continue and step (继续执行)
命令continue(继续)是恢复程序执行,直到程序正常完成。 相反,step(单步)是只执行程序的一个“步骤”,其中“步骤”可能意味着一行源代码,也可能意味着一条机器指令(取决于使用的特定命令)。无论是continue还是step时,当遇到断点或者信号时,程序都可能停止。
使用单步调试的典型案例:在疑似存在问题的函数或程序段落起始处设置一个断点,运行程序到该断点处暂停,随后逐行走过可疑区域,检查关键变量直到症结显现。
continue ( c )
c
continue命令从程序停止的地址(可能是断点)恢复程序的执行,并将程序运行到下一个断点,或者到程序正常退出。
step (s)
s
单步执行命令(step)会继续运行程序,直到控制权到达不同的源代码行,然后停止程序并将控制权交还给 GDB。单步执行命令只会在源代码行的第一条指令处停止。如果在该行调用的函数内包含调试信息,单步执行命令也会在该函数的第一条指令处停止。简而言之,单步执行程序,每次只执行一条源代码行。如果这一行包含函数调用,step
会进入该函数内部,并在函数的第一条指令处停止。
step count
单步执行count次。如果在count次前遇到了断点或其他非step信号则单步执行立即停止。
stepi (si)
相比step单步执行一行代码,stepi是单步执行一条机器指令,这里i其实是instruction-level。类似 display/i $pc
stepi count
stepi 连续执行count次及其指令
next (n)
n
在当前栈帧中继续执行到下一行源代码。这与单步执行(step)类似,但next不会进入函数内部。如果当前行包含函数调用,next 会执行整个函数调用,但不会进入函数内部进行单步执行,而是在函数调用完成后停在下一行代码。
n count
类似step count, 这里是 next 执行 count 次。
nexti (ni)
执行下一条机器指令,但是不进入函数内部。
nexti count
执行nexti命令 count次。
例子:
(gdb) b 34
Breakpoint 1 at 0x40129a: file hello.c, line 34.
(gdb) r 5
Starting program: /opt/work/hello/hello 5
Missing separate debuginfos, use: debuginfo-install glibc-2.17-323.el7_9.x86_64
Breakpoint 1, main (argc=2, argv=0x7fffffffdaa8) at hello.c:34
34 int ret = sumFunc(n, sum, str);
(gdb) n
36 printf("Sum: %d\n%s\n", *sum, str);
(gdb) c
Continuing.
Sum: 15
{0 + 1 + 2 + 3 + 4 + 5} = 15
[Inferior 1 (process 19526) exited normally]
(gdb) r 5
Starting program: /opt/work/hello/hello 5
Breakpoint 1, main (argc=2, argv=0x7fffffffdaa8) at hello.c:34
34 int ret = sumFunc(n, sum, str);
(gdb) s
sumFunc (n=5, sum=0x7fffffffdaa0, str=0x7fffffffd940 "") at hello.c:7
7 int cnt = 0;
6.Examining the stack(检查栈)
当程序停止时,你首先需要了解的是程序停止的位置以及如何到达这个位置。
每次程序执行函数调用时,都会生成关于调用的信息。这些信息包括调用在程序中的位置、调用的参数,以及被调用函数的局部变量。这些信息被保存在一个称为栈的数据块中。栈在内存中的一个区域进行分配,这个区域被称为调用栈。
当你的程序停止时,GDB 提供的用于检查栈的命令允许你查看所有这些信息。
GDB 会选择一个栈帧,许多 GDB 命令默认引用这个被选中的帧。特别是,每当向 GDB 询问程序中变量的值时,该值都是在被选中的帧中查找的。默认情况下,使用的是当前的栈帧。调用栈被划分为一系列连续的部分,称为栈帧或简称为帧;每个帧是与一次函数调用相关联的数据。帧包含了传递给函数的参数、函数的局部变量,以及函数正在执行的地址。
当你的程序启动时,栈中只有一个帧,即 main 函数的帧。这被称为初始帧或最外层帧。每次调用一个函数时,就会创建一个新的帧。每次函数返回时,对应函数调用的帧就会被消除。如果一个函数是递归的,那么同一个函数可能会有多个帧。实际正在执行的函数的帧被称为最内层帧。这是所有仍然存在的栈帧中最近创建的那个。
在程序内部,栈帧通过它们的地址来标识。一个栈帧包含许多字节,每个字节都有自己的地址;每种计算机都有一个约定来选择一个字节,其地址作为帧的地址。通常,这个地址在执行该帧时保存在一个称为帧指针寄存器的寄存器中。
GDB 为所有存在的栈帧分配编号,从最内层帧的零开始,然后是调用它的帧的编号为一,以此类推向上增加。这些编号在程序中并不是真实存在的;它们由 GDB 分配,以便你在 GDB 命令中有一种方式来指定栈帧。
frame
frame
打印当前栈帧信息
frame arg
帧命令(frame)允许你从一个栈帧移动到另一个栈帧,并且可以打印你选择的栈帧。参数(arg)可以是帧的地址或者是栈帧的编号。
select-frame
select-frame 命令允许你在不打印帧信息的情况下,从一个栈帧移动到另一个栈帧。这是 frame 命令的静默版本。
例子:
(gdb) s
sumFunc (n=5, sum=0x7fffffffdaa0, str=0x7fffffffd940 "") at hello.c:7
7 int cnt = 0;
(gdb) frame 0
#0 sumFunc (n=5, sum=0x7fffffffdaa0, str=0x7fffffffd940 "") at hello.c:7
7 int cnt = 0;
(gdb) frame 1
#1 0x00000000004012af in main (argc=2, argv=0x7fffffffdaa8) at hello.c:34
34 int ret = sumFunc(n, sum, str);
backtrace(bt)
回溯backtrace是对程序如何到达当前位置的总结。对于许多帧,它每帧显示一行,从当前正在执行的帧(帧0)开始,然后是它的调用者(帧1),一直向上显示堆栈。backtrace(或where)命令打印有关调用堆栈的信息。
当程序因“segfault”或其他故障而终止时,操作系统会保存程序的副本。该副本保存在一个名为core.pid的文件中,其中Pid是程序运行时的进程id号(例如core.1357)。通过使用GDB查看核心文件,(例如GDB core.1357),可以准确地知道程序终止的位置。使用backtrace命令,将看到堆栈帧的整个列表。堆栈帧#0是程序终止的地方。
backtrace(bt)
打印整个堆栈的回溯:堆栈中所有帧每帧一行。 通过Ctrl-c可随时停止回溯。
bt n
类似的,但是只打印最里面的n帧。
bt full
bt full n
同时打印局部变量的值。n指定要打印的帧数。
其他:
-
core文件转储开关。可使用
ulimit -c
查看, 若输出0表示关闭。ulimit -c unlimited
来开启core,ulimit -c 0
是关闭core。命令本质就是控制core文件的大小,0时表示关闭。
-
core的名字,可在/proc/sys/kernel/core_pattern中查看。常见命名格式:
core
:这是默认模式,生成的核心文件将简单地命名为 “core”。core.%p
:这将包括进程的PID,生成如 core.1234 的文件名。core.%e
:这将包括可执行文件的名称。core.%u
:这将包括导致核心转储的用户的用户名。core.%t
:这将包括核心转储的时间戳。 -
若core命令使用的默认模式,那gdb流程是:
gdb hello
再(gdb) core core
.
例子:
[hello]# ./hello 100
Segmentation fault (core dumped)
[root@localhost hello]# gdb hello
GNU gdb (GDB) Red Hat Enterprise Linux 9.2-10.el7
......
Reading symbols from hello...
(gdb) core hello-core
[New LWP 27271]
Core was generated by `./hello 100'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0 0x00000000004012b6 in main (argc=2, argv=0x7fffbad0d968) at hello.c:36
36 printf("Sum: %d\n%s\n", *sum, str);
Missing separate debuginfos, use: debuginfo-install glibc-2.17-323.el7_9.x86_64
(gdb) p n
$1 = 926031915
从n的结果来看,出现了踩内存的现象。
7.Examining source code(检查源代码)
GDB可以打印程序源代码的一部分,因为程序中记录的调试信息告诉GDB使用了哪些源文件来构建它。当程序停止时, GDB自动打印它停止的行。同样,当选择一个堆栈帧时,GDB打印该帧中停止执行的行。也可以通过下面的显式命令打印源文件的其他部分。
list(l)
list (l) 行号
list 文件名:行号
打印当前源文件中以行号为中心的行。
注意:list命令默认打印10行。
list 函数名
list 文件名:函数名
打印当前源文件中以该函数为中心的行。
list
如果最近打印的行是通过list命令打印的,那么该命令将打印出紧随这些行之后的行;然而,如果最近打印的一行是作为显示堆栈帧的一部分单独打印的,那么该命令将打印出以该行为中心的行。
list -
在最后打印的行之前打印行
list *address
指定包含address的源行。
其他:默认情况下,GDB使用任意形式的list命令打印十行源代码。可以使用set listsize来改变它。例如
set listsize 20, 将显示20行代码。
例子:
(gdb) list 36
31
32 int *sum;
33 char str[STR_MAX_SIZE];
34 int ret = sumFunc(n, sum, str);
35
36 printf("Sum: %d\n%s\n", *sum, str);
37
38 return 0;
39 }
40
8.Examining Data(检查数据)
检查程序中数据的常用方法是使用print命令(p)。它计算并打印编写程序所用语言的表达式的值。
print( p )
print expr
print /f expr
expr是表达式。默认情况下,expr的值以适合其数据类型的格式打印;您可以通过指定‘ /f’来选择不同的格式,其中f是指定格式的字母.
print /f
如果省略expr, GDB将再次显示最后一个值。
默认情况下,GDB根据其数据类型打印一个值。有时候这并不是想要的。例如,可能希望以十六进制形式打印数字,或者以十进制形式打印指针。或者,可能希望以字符串或指令的形式查看内存中某个地址的数据。要完成这些工作,需要在打印值时指定输出格式。
指定输出数据格式通过斜杠和格式字母的形式。 支持的格式字母:
符 号 | 含义 |
---|---|
x | 十六进制整数 |
d | 带符号十进制整数 |
u | 无符号十进制整数 |
o | 八进制整数; |
t | 二进制整数 |
a | 变量地址,既可以是十六进制的绝对地址,也可以是最近的前一个符号的偏移量。您可以使用此格式来发现未知地址位于何处(在什么函数中) |
c | 将其视为整数并将其打印为字符常量。这将打印数值及其字符表示。对于超出7位ASCII范围的字符,字符表示将被替换为八进制转义‘ \nnn’。如果没有这种格式,GDB会将char、unsigned char和signed char数据显示为字符常量。vector的单字节成员显示为整数数据 |
f | 浮点值 |
s | 字符串。指向单字节数据的指针显示为以空结束的字符串,单字节数据的数组显示为固定长度的字符串。其他值以其自然类型显示。如果没有这种格式,GDB将指向char、unsigned char和signed char的指针和数组显示为字符串。vector的单字节成员显示为整数数组。 |
r | 使用“原始”格式打印。默认情况下,GDB可能使用特殊类型打印变量,但使用r格式可绕过任何可能存在的值类型的漂亮显示格式。 |
display
如果您发现需要频繁地打印表达式的值(以查看它是如何变化的),您可能需要将其添加到自动显示列表中,以便GDB在每次程序停止时打印其值。添加到列表中的每个表达式都有一个数字来识别它;若要从列表中删除表达式,请指定该数字。具体指令如下:
display命令显示项目编号、表达式及参数当前值。就像手动使用 x
或 print
命令请求的显示一样,可以自定义偏好的输出格式。display 命令会根据您所设定的格式要求来选择使用 print还是 x —— 当您指定了 i
格式、s
格式或指定了单元大小时,它便会调用 x;在其他情况下,它则会调用 print。(x命令见下一章)
display expr
将表达式expr添加到表达式列表中
display /fmt expr
对于只指定显示格式而不指定大小或计数的fmt,将表达式expr添加到自动显示列表中,但每次都以指定的格式fmt显示它。请参阅上面的输出格式。
display /fmt addr
当指定格式为 i或 s,或者包含了单元大小或单元数量时,请将表达式 addr 作为程序每次暂停时需要查验的内存地址添加。所谓“查验”,实际上就是执行 x/fmt addr
命令的操作。有关详细内容,请参考下一章检查内存。举例来说,执行 display/i $pc
命令可以在程序每次暂停时查看即将执行的机器指令($pc
通常代表程序计数器;关于寄存器的更多内容,请参考后续的“寄存器”章节)。
其他:这里/fmt好像就是format的缩写,使用时仅能指定一种输出格式。而不像其他命令的参数,比如x /nfu, 其中 n f u同时从3个方向指定格式。
9.Examining Memory(检查内存)
x
x
x addr
x /nfu addr
使用命令x(表示“检查”)来检查内存,可以忽略原数据格式使用指定的格式输出内存值。
n, f 和 u 都是可选参数,它们指定要显示多少内存以及如何格式化内存;Addr是一个表达式,给出您想要开始显示内存的地址。(n是数量,f是格式,u是单元大小)
注意:如果使用nfu的默认值,则不需要键入斜杠‘ /’有几个命令为addr设置方便的默认值。
符号 | 含义 | 默认值 |
---|---|---|
n | 重复计数, 是一个十进制整数。它指定要显示多少内存(以单位u计算) | 1 |
f | 显示格式, 是print (’ x’, ’ d’, ’ u’, ’ o’,‘ t’, ‘ a’, ‘ c’, ‘ f’, ‘ s’),另外还有 i (用于机器指令)。初始值默认为‘ x’。每次使用x或print时,默认值都会更改。 | 十六进制 |
u | 单元尺寸。b–字节,h–双字节,w–4字节,g–8字节 | 4字节 |
addr | addr是希望GDB开始显示内存的地址。表达式不需要有指针值。 |
备注:
每次使用x指定单位大小时,该大小将成为下次使用x时的默认单位。(对于‘ s’和‘ i’格式,单位大小将被忽略,通常不会写入。)
addr的默认值通常在最后一个被检查的地址之后。但是其他几个命令也设置了默认值地址:
“info breakpoints”:查看断点信息,直至最后一个断点所列的地址。
“info line”:查询行信息,直至该行的起始地址。
“print”:当用于展示内存中的数值时,显示变量内容。
例子:
x/3uh 0x54320
,显示3个双字节内存值的请求,格式为无符号十进制整数(’ u’),从地址0x54320开始。
disassemble
disassemble
此命令专用于将一段内存区域以机器码指令的形式输出。
disassemble /m
通过指定 /m 修饰符,它还能展示源代码与反汇编的混合视图;
disassemble /r
通过指定 /r 修饰符,它能够以十六进制和符号形式同时展现原始指令。
默认情况下,输出的内存区域是被选定帧中的程序计数器周边的函数。该命令接受一个参数,即程序计数器的值;GDB 将输出围绕该值的函数内容。若提供两个参数,应使用逗号隔开,周围可留有空格。这些参数定义了要输出的地址区间(起始地址包含在内,结束地址不包含在内)。在此情况下,函数的名称也将一同打印(因为在指定的地址区间内可能包含多个函数)。
例子:显示了Intel x86的混合源代码+汇编,当程序在函数开始后停止时:
(gdb) disassemble /m
Dump of assembler code for function main:
24 int main() {
0x000000000040122f <+0>: push %rbp
0x0000000000401230 <+1>: mov %rsp,%rbp
0x0000000000401233 <+4>: add $0xffffffffffffff80,%rsp
25 int n = 5;
=> 0x0000000000401237 <+8>: movl $0x5,-0x4(%rbp)
26 int *sum;
27 char str[STR_MAX_SIZE];
28 int ret = sumFunc(n, sum, str);
0x000000000040123e <+15>: lea -0x80(%rbp),%rdx
0x0000000000401242 <+19>: mov -0x10(%rbp),%rcx
0x0000000000401246 <+23>: mov -0x4(%rbp),%eax
0x0000000000401249 <+26>: mov %rcx,%rsi
0x000000000040124c <+29>: mov %eax,%edi
0x000000000040124e <+31>: callq 0x401142 <sumFunc>
0x0000000000401253 <+36>: mov %eax,-0x14(%rbp)
--Type <RET> for more, q to quit, c to continue without paging--c
...
33 }
0x0000000000401276 <+71>: leaveq
0x0000000000401277 <+72>: retq
End of assembler dump.
某些架构中,常见有多套指令助记符或其他语法规则。 对于那些动态链接并依赖于共享库的程序,调用共享库内函数或转向共享库中某处的指令,可能会呈现出一个看似错误的地址——这实际上是指向重定位表的坐标。在某些系统架构中,GDB 有能力将这些地址转换成真实的函数名。
10.Registers(寄存器)
机器寄存器是计算机执行算术、执行逻辑、跟踪指令和调用堆栈位置的地方。
在表达式中,您可以将机器寄存器内容引用为名称以“$”开头的变量。每台机器的寄存器名称是不同的;使用信息寄存器来查看机器上使用的名称。
info registers
info registers
输出所有寄存器的名称和值。
注意:不包含浮点寄存器和向量寄存器(在选定的堆栈帧中)
GDB在大多数机器上提供了四个“标准”寄存器名称,用于在表达式中使用(只要它们不与架构的标准寄存器助记符冲突)。寄存器名称pc和p**c和sp分别用于程序计数器寄存器和栈指针寄存器。fp用于包含指向当前栈帧的指针的寄存器,而f**p用于包含指向当前栈帧的指针的寄存器,而ps用于包含处理器状态的寄存器。例如,你可以用十六进制打印程序计数器,如下所示:
GDB为大多数平台提供了四个“标准”寄存器名称,这些名称可以在表达式中使用(只要它们不与特定架构的寄存器标准助记符冲突):
寄存器符号 | 含义 |
---|---|
$pc | 程序计数器 |
$sp | 栈指针寄存器 |
$fp | 一个包含当前栈帧指针的寄存器 |
$ps | 一个包含当前处理器状态的寄存器 |
p register
x register
例子:
# 十六进制展示程序计数器值(/x 输出十六进制格式)
(gdb) p/x $pc
$1 = 0x40116d
# 输出接下来要执行的指令(/x 以机器指令输出)
(gdb) x/i $pc
=> 0x40116d <sumFunc+43>: mov $0x64,%eax
#栈指针加4
(gdb) set $sp += 4
11.Examining the symbol table(检查符号表)
本章介绍的命令旨在查询程序内定义的符号(包括变量名、函数名和类型名)。这些信息是程序代码的基本组成部分,它们在程序运行过程中保持不变。GDB通过检索程序符号表来获取这些信息。
info
info address symbol
描述符号数据存储的位置。对于寄存器变量,这表示它保存在哪个寄存器中。对于非寄存器局部变量,这将打印该变量始终存储的堆栈帧偏移量。注意,
info symbol addr
打印存储在地址addr上的符号的名称。如果没有符号被精确地存储在addr, GDB打印最近的符号和偏移量。
这与info address命令相反。您可以使用它来查找给定地址的变量或函数的名称。
例子:
(gdb) info addr n
Symbol "n" is a complex DWARF expression:
0: DW_OP_fbreg -20
.
(gdb) p /x $pc
$1 = 0x401237
(gdb) info symbol 0x401237
main + 8 in section .text of /root/hello
info functions
打印所有函数名
whatis
whatis [arg]
打印参数arg的数据类型,arg可以是表达式或类型名称。若无参数指定,则显示历史值记录中的最新值。若arg为表达式,则不会实际进行求值,且表达式内的任何可能产生副作用的行为(例如赋值或函数调用)都不会执行。若arg为类型名称,则可能是类型的直接名称或typedef定义的名称;在C语言代码中,它也可能是struct struct-tag、union union-tag或enum enum-tag这样的结构。
ptype
ptype [arg]
Ptype接受与whatis相同的参数,但是打印类型的详细描述,而不仅仅是类型的名称。
例子:
(gdb) whatis sumFunc(n, sum, str)
type = int
(gdb) ptype sumFunc
type = int (int, int *, char *)
12.Altering Execution(修改执行)
当认为在程序中发现了错误时,或许希望知道,能否通过修复这个表面上的错误来确保程序在后续运行中产出正确的结果。您可以通过实验来探究这一问题,借助GDB提供的那些能够改变程序执行流程的特性。
改变变量值
print x=6
将数值4赋予变量x,并随后输出赋值表达式的结果(该结果为4)。若您对赋值表达式的值不感兴趣,只需使用set var
命令而非print
命令即可。set var
命令与print
命令实质上是一致的,差别仅在于前者不会显示表达式的值,也不会将其记录在值的历史列表中。
set var x=6
调用函数
在gdb提示符下,您可以调用一个函数并显示其返回值(如果有的话)。
print expr
对表达式expr求值并显示结果值。Expr可以包括对正在调试的程序中的函数的调用
备注:在实际使用中,我仅仅把它当做一个简易计算器,来在debug过程中验证一些计算是否符合预期。
call expr
对表达式expr求值,但不显示空返回值。如果您想从程序中执行一个不返回任何东西的函数(也称为void函数),但又不想让输出与GDB将打印的空返回值混在一起,那么您可以使用print命令的这个变体。如果结果不为空,则打印并保存在值历史记录中.
13.Miscellaneous(其他)
dump(内存保存到文件中)
您可以使用dump命令将数据从内存复制到文件中。dump命令将数据写入文件,
dump [format] memory filename start_addr end_addr
dump [format] value filename expr
将从start_addr到end_addr内存范围内内容转存到filename,或者将expr的值以给定的格式转储到filename。
format参数可以是以下任意一个:
参数 | 含义 |
---|---|
binary | 原始二进制形式 |
ihex | Intel Hex Format |
srec | Motorola S-record format |
tekhex | Tektronix Hex format |
注意:GDB遵循GNU二进制工具套件(如objdump和objcopy)对格式的定义。若未指定格式,GDB将直接以未经处理的二进制形式输出数据。
Core Files
当程序非正常结束(如段错误)时,Linux系统将生成一个核心文件,该文件记录了程序执行瞬间的内存镜像。这些核心文件的命名规则为core.pid,其中pid代表了程序运行时的进程编号。为了探究程序终止的具体位置,您可以启动GDB并指定核心文件名(如gdb core.2367),随后执行堆栈回溯,以查看程序的调用栈情况。
备注:详细内容可以查看第六章backtrace(bt)命令。
其他:文中提供代码存在段错误,可用于debug。错误原因:无符号运算造成的整数溢出。
Snapshots
在某些操作系统上,GDB能够对程序的状态进行“快照”,我们称之为“检查点”,并且可以在后续恢复到这个状态。 回到一个检查点,实质上撤销了自检查点保存以来程序所经历的所有变动。这涵盖了内存、寄存器的更改,甚至在一定范围内还包括系统状态的变化。可以说,这就像是时光倒流,回到了检查点保存的那一刻。
因此,当您单步调试程序并感觉即将接近问题发生点时,您可以保存一个检查点。然后,如果您不小心走过了头,错过了关键语句,您无需从程序开始处重新启动,只需回到检查点,从那里再次开始。这种方法特别有用,尤其是当到达您认为错误发生的位置需要花费大量时间或步骤时。
checkpoint
保存已调试程序当前执行状态的快照。checkpoint命令不接受任何参数,但是为每个检查点分配一个小的整数id,类似于断点id
info checkpoint
列出已保存在当前调试会话中的检查点。对于每个检查点,将列出以下信息:
Checkpoint ID Process ID Code Address Source line, or label
(gdb) checkpoint
checkpoint 1: fork returned pid 22367.
(gdb) info checkpoints
* 0 process 15026 (main process) at 0x0
1 process 22367 at 0x40119c, file hello.c, line 13
restart checkpoint-id
恢复编号为checkpoint-id的检查点所保存的程序状态。所有程序变量、寄存器、栈帧等都将恢复到保存检查点时的值。本质上,GDB将“倒转时钟”,回到保存检查点的那个时刻。
注意: 断点、GDB变量、命令历史等不会因恢复检查点而受到影响。通常情况下,检查点只恢复驻留在被调试程序中的内容,而不包括调试器本身的内容。
delete checkpoint checkpoint-id
删除先前保存的以checkpoint-id标识的检查点。
图形界面
启动gdb时指定-tui参数(例如:gdb -tui exe
),或者运行gdb过程中使用“Crtl+X+A
”组合键,都可以进入图形化调试界面。