通过使用gdb的逐步调试代码,可以看到程序内部是如何运行的,还可以查看程序中变量的值、内存使用情况、栈信息以及其他一些细节问题。
下面通过一个实例来详细的接收gdb的调试的具体步骤。
源程序——计算两个整数的平方和:
这段代码的功能是输入两个整数,求他们的平方的和,当两个数全为0的时候退出运算。
通过阅读代码可以发现,程序使用了一个while(1)的死循序,使其可以一直接受终端的输入,并运行运算后产生输出。运算部分调用了子函数calculate(),并通过参数的值传递方式将输入的两个整数传递给他。
编译并运行程序如下:
接下来使用gdb查看程序内部的情况!
调用gdb
使用断点
断点指出了gdb将要在改电触中断程序的执行,从而便于程序员单步跟踪调试代码。可通过使用break命令,指定一个特定的位置设置断点,当程序运行到断点处就暂停程序,然后把程序的控制权交给调试员和程序员。
用break命令设置断点的方法有多种,下面列出了一些:
命令 | 含义描述 |
break <function> | 在进入指定函数时停住。C++中可以使用class::function或function(type,,type)格式来指定函数名 |
break <linenum> | 在指定行号停住 |
break +offset | 在当前行号的前面的offset行停住。offset为自然数 |
break -offset | 在当前行号的后门的offset行停住。offset为自然数 |
break filename:linenum | 在源文件filename的linenum行停住 |
break filename:function | 在源文件filename的function函数的入口处停住 |
break*address | 在程序运行的内容地址出停住 |
break | 该命令没有参数时,表示在下一条指令处停住 |
break.....if<confdition> | condition表示条件,在条件成立的时候停住。比如在循环中,可以设置break if i=100,表示当i=100的时候停住 |
一般来说,gdb下的命令(例如break,和下面建议提到的list、jump等)后面都可以跟不同的参数,是命令变得更加灵活。这些参数如下表所示:
参数 | 含义描述 |
<linenum> | 行号 |
<function> | 函数名 |
<+offset> | 当前行号的正偏移量 |
<-offset> | 当前行号的负偏移量 |
<filename:linenum> | 某个文件的某一行 |
<filename:function> | 某个文件的某个子函数 |
<*address> | 程序运行时的语句在内存中的地址 |
现在回到程序的调试过程中,我们可以在main函数处设置断点
:
然后使用run命令运行该程序
由此可见,程序在设置的断点处停住运行了,gdb会指出所遇到的断点,然后显示将要执行的下一行程序。
接下来可以使用单步调试命令step(简写为s)来跟踪调试程序,它一次只执行程序中的一行代码。命令如下:
查看运行时数据
在调试程序的过程中,往往需要查看程序中某些表达式或变量的值,以判断程序运行是否正确,使用gdb调试时,常用到的是print、display命令,以及查看内存、寄存器的信息等。
1、print命令
在调试程序时,当程序被停住是,可以使用print命令(简写为p),或是同义命令inspect来查看当前程序的运行数据。print命令的格式为:
print <expr>
print /<f> <expr>
<expr>是表达式,是所调试程序的语言表达式(gdb可以调试多种编程语言);
<f>是输出的格式,比如,如果要把表达式按十六进制的格式输出,那么就是/x
2、输出格式
gdb的命令会根据变量的类型输出变量的值,但也可以自定义gdb的输出变量的格式:
符号 | 含义 |
x | 按十六进制格式显示变量 |
d | 按十进制格式显示变量 |
u | 按十六进制格式显示无符号整型 |
o | 按八进制格式显示变量 |
t | 按二进制格式显示变量 |
a | 按十六进制格式显示变量 |
c | 按字符格式显示变量 |
f | 按浮点数格式显示变量 |
3、自动显示命令display
可以设置一些自动显示的变量,当程序停住时,或是在单步跟踪时,这些变量会自动显示。相关gdb命令式display
格式如下:
display <expr>
display /<fmt> <expr>
display /<fmt> <addr>
4、查看内存
可以使用examine命令(简写是x)来查看内存地址中的值。x命令的语法如下所示:
x /<n/f/u> <addr>
对于该命令解释如下:
- n\f\u是可选参数,可以独立使用,也可以联合使用
- n是一个正整数,表示显示内存的长度,也就是说从当前地址向后显示几个地址的内容
- f表示现实的格式,参加上面。如果地址所指的是字符串,那么格式可以是s;如果是指令地址,那么格式可以是i
- u表示从当前地址往后请求的字节数,如果不指定的话,gdb默认是4个bytes.u参数可用下面的字符来代替:b表示单字节,h表示双字节,w表示四字节,g表示8字节。当指定了长度之后,gdb会从指定的内存地址开始,读写指定字节并把其当做一个值取出来。
- <addr>表示一个内存地址。
例如:
x /4uh 0x48723
此处只是做一个示例而已哈!
5、gdb环境变量
可以在gdb的调试环境中自定义字节的变量。用来保存一些调试程序中的运行数据。
要定义一个gdb的变量很简单,只需要使用gdb的set命令。gdb的环境变量和UNIX一样,也是以$起始。例如:
set $foo=*object_ptr
使用环境变量时,gdb会在第一次使用时创建这个变量,而在以后的使用中,则直接对其赋值。环境变量没有类型,可以给环境变量定义任意的类型,包括结构体和数组。
show convenience
该命令用来查看当前所设置的所有的环境变量。
正是一个比较强大的功能,环境变量和程序变量的交互使用将会使得程序调试更为灵活便捷。
例如:
set $i=0
print bar[$i++]->contents
输入这样的命令之后,只需要按下回车键,重复执行上一条语句,环境变量就会自动累加,从而完成逐个输出的功能。
6、查看寄存器
在调试程序的工程中,有时候也需要查看某些寄存器中的值。寄存器中放置了程序运行时的数据,比如程序当前运行的指令地址IP,程序当前堆栈地址SP等。
可以使用info来查看这些内容:
命令 | 含义描述 |
info registers | 查看寄存器的情况(除了浮点寄存器) |
info all-registers | 查看所有寄存器的情况(包括浮点寄存器) |
info register<regname...> | 查看所指定的寄存器的情况,regname表示寄存器名,多个寄存器之间用逗号隔开 |
print $ip
查看源程序
1、显示源代码
gdb可以打印出所调试程序的源代码,当然,在程序编译时一定要加上“-g”参数,吧源程序信息编译到执行文件中,不然就看不到源程序了。在程序停下来之后,gdb会报告程序停在了程序的第几行。可以使用list来查看源代码:
2、源代码内存
可以使用info line命令来查看源代码在内存中的地址。和大多数gdb命令相同,info line后面也可以跟“行号”、“函数名”、“文件名:函数名”的参数形式,这个命令会显示出所指定的源代码在运行时的内存的地址。
下面来查看一下子函数名calculate()在内存中的地址
改变程序的执行
1、修改变量值
2、跳转执行
jump <linespec>
jump <address>
3、产生信号量
signal 《singal》
4、强制返回
return
return <expression>
5、强制调用函数
call <expression>