前言
GDB是一个由GNU开源组织发布的、UNIX/LINUX 操作系统下的、基于命令行的、功能强大的程序调试工具。
GDB 支持断点、单步执行、打印变量、观察变量、查看寄存器、查看堆栈等调试手段。在 Linux 环境软件开发中,GDB 是主要的调试工具,用来调试 C 和 C++ 程序(也支持 go 等其他语言)。
一、常用指令
-g: 使用该参数编译可以执行文件,得到调试表。 gdb ./a.out list: list 1 列出源码。根据源码指定 行号设置断点。 b: b 20 在 20 行位置设置断点。 run/r: 运行程序 n/next: 下一条指令(会越过函数) s/step: 下一条指令(会进入函数) p/print: p i 查看变量的值。 continue:继续执行断点后续指令。 finish:结束当前函数调用。 quit:退出 gdb 当前调试。
二、案例说明
使用 gdb 之前,要求对文件进行编译时增加 -g 参数,加了这个参数过后生成的编译文件会大一些,这是因为增加了 gdb 调试内容。
1、测试源文件
#include <stdio.h> void myprint(int i) { if (i % 2 == 1) { printf("this run, i = %d\n", i); } } int main(void) { int i = 0; printf("hello world\n"); for (i = 0; i < 10; i++) { myprint(i); } }
2、编译和调试
①、编译
gcc test.c -o test -g
②、启动对 test 的调试
gdb test
list/l n 从第 n 行开始显示程序, 后续继续输入 list/l,就可以显示后面的代码
break/b n 在第 n 行设置断点,断点那一行不会执行
run/r 运行程序
接下来按 next/n/step/s 继续向下执行
next/n :下一个,调用函数就跑
step/s :单步,会进入调用的函数
要注意的是,如果是系统函数,按 s 就出不来了,这时用 until+行号直接执行到行号处
进到 printf 系统函数出不来的示例
使用 until 出来
print/p i 查看 i 变量的值
continue 直接运行到结束
三、其他指令
run:使用 run 查找段错误出现位置。 set args: 设置 main 函数命令行参数 (在 start、 run 之前) run 字串 1 字串 2 ...: 设置 main 函数命令行参数 info b: 查看断点信息表 b 20 if i = 5: 设置条件断点。 ptype:查看变量类型。 bt:列出当前程序正存活着的栈帧。 frame: 根据栈帧编号,切换栈帧。 display:设置跟踪变量 undisplay:取消设置跟踪变量。 使用跟踪变量的编号
四、案例说明
1、将上述 main 函数做如下修改,制造段错误
int main(void) { int i = 0; char *p = "TEST"; printf("hello world\n"); p[0] = 'Q'; for (i = 0; i < 10; i++) { myprint(i); } }
gcc test.c -o test -g gdb test run
可以看到段错误的位置
2、将上述 main 函数做如下修改,传参测试
int main(int argc, char *argv[]) { int i = 0; printf("hello world\n"); printf("argc = %d\n", argc); printf("argv[0] = %s, argv[1] = %s\n", argv[0], argv[1]); for (i = 0; i < 10; i++) { myprint(i); } }
命令行执行下述命令
gcc test.c -o test -g gdb test run
3、将上述 main 恢复成最初版本,做断点测试
int main(void) { int i = 0; printf("hello world\n"); for (i = 0; i < 10; i++) { myprint(i); } }
设置两个断点,一个是普通断点(打在第14行),一个是条件断点(当 i = 6 时打在第6行),再执行 run
b 14 b 6 if i = 6 run
backtrace 命令是列出当前堆栈中的所有帧。在下面的例子中,栈上只有一帧,编号为0,属于 main 函数。
backtrace (或者bt)
接着,我们执行了 next 命令。下面我们继续通过 backtrace 命令来查看栈帧信息。
从上面输出结果,我们能够看出,有两个栈帧,第1帧属于 main 函数,第0帧属于 myprint 函数。
每个栈帧都列出了该函数的参数列表。从上面我们可以看出,main 函数没有参数,而 myprint 函数有参数,并且显示了其参数的值。
有一点我们可能比较迷惑,在第一次执行backtrace的时候,main 函数所在的栈帧编号为0,而第二次执行的时候,main 函数的栈帧为1,而 myprint 函数的栈帧为0,这是因为与栈的向下增长规律一致,我们只需要记住编号最小帧号就是最近一次调用的函数。
4、查看断点信息表 info b
5、栈帧 frame
栈帧用来存储函数的变量值等信息,默认情况下,GDB 总是位于当前正在执行函数对应栈帧的上下文中。
在前面的例子中,由于当前正在 myprint函数中执行,GDB 位于第0帧的上下文中。可以通过 frame 命令来获取当前正在执行的上下文所在的帧。
下面,我们尝试使用 print 命令打印下当前栈帧的值,如下:
如果我们想看其他栈帧的内容呢?比如 main 函数中的变量,那需要先切换栈帧再查看,我们可以通过 frame [num] 来切换栈帧,如下:
6、通过 ptype 查看变量的类型
7、通过 display 设置跟踪变量
和 print 命令一样,display 命令也用于调试阶段查看某个变量或表达式的值,它们的区别是,使用 display 命令查看变量或表达式的值,每当程序暂停执行(例如单步执行)时,GDB 调试器都会自动帮我们打印出来,而 print 命令则不会。
也就是说,使用 1 次 print 命令只能查看 1 次某个变量或表达式的值,而同样使用 1 次 display 命令,每次程序暂停执行时都会自动打印出目标变量或表达式的值。因此,当我们想频繁查看某个变量或表达式的值从而观察它的变化情况时,使用 display 命令可以一劳永逸。
undisplay:取消设置跟踪变量。 使用跟踪变量的编号