文章目录
一、gdb简介
二、调试前的准备
1、生成调试文件
2、启动 gdb
三、gdb 使用方法
1、查看源代码
2、设置 / 查看断点(多种方式设置断点)
方法一
方法二
方法三
3、run
4、删除断点、断点无效
5、逐过程调试(以函数为单位)
6、逐语句调试
7、查看调用链
8、查看变量值
单次查看
长显示
9、指定运行到某行
10、 执行完当前函数
11、从一个断点运行到另一个断点
四、gdb 指令总结
GDB 全称“GNU symbolic debugger”,是 Linux 下常用的程序调试器。发展至今,GDB 已经迭代了诸多个版本,当下的 GDB 支持调试多种编程语言编写的程序,包括 C、C++、Go 等等。实际场景中,GDB 更常用来调试 C 和 C++ 程序。一般来说,GDB主要帮助我们完成以下四个方面的功能:
程序源代码如下:
1 #include
2
3 int add(int x)
4 {
5 int ret=0;
6 printf("start\n");
7
8 for(int i=0;i
Makefile配置文件如下:
1 test:test.c
2 gcc -o test test.c -g -std=c99
3 .PHONY:clean
4 clean:
5 rm -f test
不调试的情况下,使用 gcc 指令编译文件一般是:gcc test.c -o test 但是,如果要调试,那么就必须要加上 -g 指令。其原理用 Visual Studio Code 环境下来类比,使用该 IDE 熟悉的话便知道 发行一个程序的时候,可以有 release 版本和 debug 版本,只有我们选择 debug 版本的时候,程序才可以被调试,否则不行。这里也类似,加上 -g 就相当于生成 debug 版本的程序,不加就默认是 release 版本的。其正确指令如下:
gcc test.c -o test -g -std=c99
同时,我们可以思考一下为什么 gcc 默认发行 release 版本的程序,其实是因为 debug 版本由于需要一些调试信息,所以就需要更大的内存,运行起来会更慢。我们可以分别生成 release 版本和 debug 版本的文件,进行对比,确实 debug 版本所占空间更大。
当然,我们也可以通过查看生成的两个二进制文件,其里面是否包含调试信息。readefl 指令可以查看二进制文件里的一些信息。下图也可以看出,确实 debug 版本里包含调试信息。
此外,这里要特别说明一下,配置文件中,第一个依赖方法里面 -std=c99 的含义。c99 是 C 语言的一个标准,在这个标准里规定了 可以在循环控制条件里面定义变量,如 for(int i=0;i<10;i++) 。如果不使用 -std=c99 那么就会造成编译出错,如下,所以必须加上。
启动 gdb 的指令非常简单,如下即可启动生成的 test 文件。
gdb test
如下,出现这样的界面,就代表进入gdb调试,就可以使用各种指令进行调试:
l 行号:( l 是 list 的简写),显示源代码,接着上次的位置往下列,每次列10行。在后面加上行号可以指定查看该行号附近的代码。
测试效果如下:
(gdb) l
9 {
10 ret+=i;
11 }
12 printf("end\n");
13 return ret;
14 }
15
16 int main()
17 {
18 int x=100;
(gdb) l 1
1 #include
2
3 int add(int x)
4 {
5 int ret=0;
6 printf("start\n");
7
8 for(int i=0;i
b 行号 :(b 是 break 的简写),在某一行打上断点。
info b : 查看断点。
可以看到,断点里面有一些信息,大部分都不难理解,比如 Num是编号,Type是种类,Address是断点在内存的位置,What是断点在程序中的位置。Disp 和 Enb 可能比较难理解。
Disp:断点执行一次之后是否有效 keep:有效 dis:无效
Enb: 当前断点是否有效 y:有效 n:无效
b 文件名:行数 :在指定的文件里面打断点。
b (文件名 : )函数名 : 指定函数打断点。
如下,可以指定函数名打断点,断点生成的地方就是该函数开始执行的地方。
在多文件的时候,可以指定文件名打断点。如下,删除两个断点之后,利用指定文件名的方式在 main函数开始处 添加断点。
r :(run的缩写),如果有断点,执行到断点处停,如果没有断点,执行结束。(相当于 VS 里面的 F5 。)
如果执行 r 之后,再查看断点就会发现不一样。如下,info b 显示的信息里面,多了一行 “breakpoint already hit 1 time” 说明当前断点被命中。
disable breakpoint 断点编号: 使断点无效。
enable breakpoint 断点编号: 使断点有效。
如下,使用 disable 指令之后,info 信息里的 Enb 变成了 n,代表该断点不可用。使用 enable 之后,变成了 y ,表示该断点可用。当然 breakpoint 可以简写为 b 。
d 断点编号:(delete 的缩写)删除 对应编号的断点。这里不是输入行号,是断点的编号。
如下,之前的断点编号为1,删除之后,使用 info 指令查看是没有断点的。
n :(next 的缩写) 一步步调试,但是不会进入函数内部。 ( 相当于 VS 下的 F10,遇到函数调用不会进入函数。)
如下,设置 19 行的断点之后,按下 r 运行代码,在断点处停下,然后 n 调试,显然直接执行完了 add 函数内部的内容,打印了 start 和 end。
s :(step 的缩写) 一步步调试,遇到函数调用的时候,进入函数。 ( 相当于 VS 下的F11,可以进入函数内部调试。)
如下,重新调试后,又回到了 19 行设置的断点处,此时使用 s 调试,进入了 add 函数内部,一步步执行下去,也打印了 start (红色箭头指向处)。
bt : 查看函数调用过程。函数调用是一个压栈的过程,可以使用bt指令来查看。
在进入add 函数之后,使用 bt 指令查看如下,可以看到,先调用了main 函数,然后add 函数压栈。
p 变量:(printf 的缩写) 打印变量的值,但是只能单次打印。
如下,在循环执行到某一步的时候,查看一些变量、地址的值。但是由于不能一直显示,显得非常麻烦。
display 变量名: 查看某变量的值(内置类型,结构体等都可以),类似于 VS 里面的监视,一直会显示。
undisplay 编号: 取消显示某变量。
如下,查看了 ret 的值和地址,执行 s 之后,确实会显示这两个值。然后不想看 ret 的值之后,不可以直接 undisplay ret ,必须要 undisplay 编号,如下红色箭头。
until 行数 :使程序运行到指定行数,在函数内部使用。(VS 里面没有相关功能按键。)
比如在一个函数内部,有循环要执行很多次,就非常麻烦,此时可以使用 until 直接跳过循环到达指定行数。如下,until 12 直接跳过了 add 函数里的循环(由于 i 是在循环内部定义的,所以出了循环就监视不到了。)
finishi :只执行完当前函数,然后停下来。
如下,当前在 add 函数里面,执行 finishi 指令,add函数直接执行完了,等待下一步指令。
c :(continue 的缩写) 当前处于某函数内部的断点 A,下一个该函数内部的断点是 B,执行continue,就会运行到断点B处。
如下,重新 r 之后,进入了第一个断点,然后continue,执行到了第二个断点处。
如下,是一些gdb常用指令总结,有一些是在上面有所展示,有一些没有。
list/l 行号:显示binFile源代码,接着上次的位置往下列,每次列10行。
list/l 函数名:列出某个函数的源代码。
r 或 run:运行程序。
n 或 next:单条执行。
s或step:进入函数调用
break(b) 行号:在某一行设置断点
break 函数名:在某个函数开头设置断点
info break :查看断点信息。
finish:执行到当前函数返回,然后挺下来等待命令
p 变量:打印变量值。
set var:修改变量的值
continue(或c):从当前位置开始连续而非单步执行程序
run(或r):从开始连续而非单步执行程序
delete breakpoints:删除所有断点
delete breakpoints n:删除序号为n的断点
disable breakpoints:禁用断点
enable breakpoints:启用断点
info(或i) breakpoints:参看当前设置了哪些断点
display 变量名:跟踪查看一个变量,每次停下来都显示它的值
undisplay:取消对先前设置的那些变量的跟踪
until X行号:跳至X行
breaktrace(或bt):查看各级函数调用及参数
info(i) locals:查看当前栈帧局部变量的值
quit:退出gdb