GDB调试入门
GDB是GNU开源组织发布的一个强大的UNIX下的程序调试工具。或许,各位比较喜欢那种图形界面方式的,像VC、BCB等IDE的调试,但如果你是在 UNIX平台下做软件,你会发现GDB这个调试工具有比VC、BCB的图形化调试器更强大的功能。所谓“寸有所长,尺有所短”就是这个道理。一般来说,GDB主要帮忙你完成下面四个方面的功能:
- 启动你的程序
- 可让被调试的程序在你所指定的调置的断点处停下来
- 当程序被停下来看,可以检查此时你的程序中所发生的事
- 动态的改变你程序的执行环境
GDB是一个非常强大的调试工具,即使大家使用习惯了图形化调试工具,但这些图形化的调试工具的后端大多数都是使用 GDB 的,命令行的调试工具却有着图形化工具所不能完成的功能。以下以 hellowld.c
为例介绍 GDB 的调试入门:
编写代码
开始工作时,先确认在你的编程环境中是否安装了开发环境,如果未安装,参考 安装开发环境 章节完成开发环境的安装:
#include <stdio.h>
int main(int argc, char **argv)
{
int i;
int result = 0;
printf("hello world.\n");
for(i = 1; i <= 100; i++) {
result += i;
}
printf("result = %d\n", result );
return 0;
}
编译代码,使用 GDB 调试程序时,在 gcc
编译时要带上 -g
参数:
gcc helloworld.c -o hellowrld -g
运行一下程序,看看效果:
thead@yoc:~/works > ./hellowrld China
Hello World China!
result = 5050
thead@yoc:~/works > ./hellowrld
helloworld.
result = 5050
启动调试
$ gdb helloWorld
GNU gdb (GDB) Red Hat Enterprise Linux 8.2-12.el8
Copyright (C) 2018 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-redhat-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from helloworld...done.
(gdb) run <----------------------------- 不带参数运行
Starting program: /home/zhuzhg/helloworld
Missing separate debuginfos, use: yum debuginfo-install glibc-2.28-101.el8.x86_64
helloworld.
result = 5050
[Inferior 1 (process 1069013) exited normally]
(gdb) run China <----------------------------- 带参数运行
Starting program: /home/zhuzhg/helloworld China
Hello World China!
result = 5050
[Inferior 1 (process 1071086) exited normally]
(gdb)
断点
在介绍之前,我们首先需要了解,为什么需要设置断点。我们在指定位置设置断点之后,程序运行到该位置将会“暂停”,这个时候我们就可以对程序进行更多的操作,比如查看变量内容,堆栈情况等等,以帮助我们调试程序。
设置断点
文件行号断点:
break hellowrld.c:9
函数断点:
break main
条件断点:
break helloworld.c:17 if c == 10
临时断点, 假设某处的断点只想生效一次,那么可以设置临时断点,这样断点后面就不复存在了:
tbreak helleworld.c:9
禁用或启动断点:
disable # 禁用所有断点 disable bnum # 禁用标号为bnum的断点 enable # 启用所有断点 enable bnum # 启用标号为bnum的断点 enable delete bnum # 启动标号为bnum的断点,并且在此之后删除该断点
断点清除:
clear # 删除当前行所有breakpoints clear function # 删除函数名为function处的断点 clear filename:function # 删除文件filename中函数function处的断点 clear lineNum # 删除行号为lineNum处的断点 clear f:lename:lineNum # 删除文件filename中行号为lineNum处的断点 delete # 删除所有breakpoints,watchpoints和catchpoints delete bnum # 删除断点号为bnum的断点
变量查看
在启动调试以及设置断点之后,就到了我们非常关键的一步-查看变量。GDB调试最大的目的之一就是走查代码,查看运行结果是否符合预期。既然如此,我们就不得不了解一些查看各种类型变量的方法,以帮助我们进一步定位问题。
变量查看: 最常见的使用便是使用print(可简写为p)打印变量内容。
以上述程序为例:
gdb helloworld break helloworld.c:17 if i == 0 (gdb) run Starting program: /home/zhuzhg/helloworld helloworld. Breakpoint 2, main (argc=1, argv=0x7fffffffdca8) at helloworld.c:17 17 result += i; (gdb) print i <------------------ 查看变量 i 当前的值 $1 = 10 (gdb) print result <------------------ 查看变量 result 当前的值 $2 = 45 (gdb) print argc <------------------ 查看变量 argc 当前的值 $3 = 1 (gdb) print str $4 = 0x4006c8 "Helleo World" <------------------ 查看变量 str 当前的值
查看内存: examine(简写为x)可以用来查看内存地址中的值。语法如下:
x/[n][f][u] addr
其中:
- n 表示要显示的内存单元数,默认值为1
- f 表示要打印的格式,前面已经提到了格式控制字符
- u 要打印的单元长度
- addr 内存地址
单元类型常见有如下:
- b 字节
- h 半字,即双字节
- w 字,即四字节
- g 八字节
示例:
(gdb) x/4b str 0x4006c8: 01001000 01100101 01101100 01101100
可以看到,变量 str 的四个字节都以二进制的方式打印出来了。
查看寄存器内容:
ra 0x3ff7ef2282 0x3ff7ef2282 <__libc_start_main+160> sp 0x3ffffffaa0 0x3ffffffaa0 gp 0x2aaaaac800 0x2aaaaac800 tp 0x3ff7fdd250 0x3ff7fdd250 t0 0x3ff7ed60b0 274742468784 t1 0x3ff7ef21e2 274742583778 t2 0x2aaaaac4f0 183251944688 fp 0x3ffffffab0 0x3ffffffab0 s1 0x0 0 a0 0x1 1 a1 0x3ffffffc28 274877905960 a2 0x3ffffffc38 274877905976 a3 0x0 0 a4 0x3ffffffad8 274877905624 a5 0x0 0 a6 0x3ff7fd88a8 274743527592 (内容过多未显示完全)
单步调试
在启动调试设置断点观察之后,没有我们想要的信息怎么办呢?这个时候,就需要单步执行或者跳过当前断点继续执行等等。而本文所说的单步调试并非仅仅指单步执行,而是指在你的控制之下,按要求执行语句。
单步执行-next:
next命令(可简写为n)用于在程序断住后,继续执行下一条语句,假设已经启动调试,并在第12行停住,如果要继续执行,则使用n执行下一条语句,如果后面跟上数字num,则表示执行该命令num次,就达到继续执行n行的效果了:
gdb helloworld <------------------------------- 加载程序 (gdb) break helloworld.c:18 <------------------------------- 设置断点 (gdb) run <------------------------------- 启动调试 The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /home/zhuzhg/helloworld Helleo World. Breakpoint 2, main (argc=1, argv=0x7fffffffdca8) at helloworld.c:18 <-------- 程序在 18 行暂停 18 result += i; Breakpoint 2, main (argc=1, argv=0x7fffffffdca8) at helloworld.c:18 18 result += i; (gdb) next <-------- 单步执行 17 for(i = 1; i <= 100; i++) { Breakpoint 2, main (argc=1, argv=0x7fffffffdca8) at helloworld.c:18 18 result += i; (gdb) next 2 <-------- 执行两次 Breakpoint 2, main (argc=1, argv=0x7fffffffdca8) at helloworld.c:18 18 result += i;
单步进入-step:
如果我们想跟踪add函数内部的情况,可以使用step命令(可简写为s),它可以单步跟踪到函数内部,但前提是该函数有调试信息并且有源码信息。
断点继续-continue:
我们可能打了多处断点,或者断点打在循环内,这个时候,想跳过这个断点,甚至跳过多次断点继续执行该怎么做呢?可以使用continue命令(可简写为c)或者fg,它会继续执行程序,直到再次遇到断点处。