Linux下开发工具学习-gdb

学习完gcc和Makefile之后,我们继续来学习gdb相关知识,主要涉及以下内容:gdb相关概念,gdb功能,设置端点,单步跟踪,段错误调试等。

1.什么是gdb

gdb是GNU debugger的缩写,是编程调试工具。

2.gdb功能

gdb有以下四个主要功能:
(1)启动程序,可以按照用户自定义的要求随心所欲的运行程序。比如,带上参数…

(2)可让被调试的程序在用户所指定的调试的端点处停住(端点可以是条件表达式)。

(3)当程序停住时,可以检查此时程序中所发生的事。比如,将变量的值打印出来…

(4)动态改变程序的执行环境。比如,在调试过程中可以动态的改变某个变量的值…

3.运行gdb

(1)假设我们现在有如下simple.c源文件:

首先编译:
gcc -Wall -g simple.c -o simple

编译程序的时候记得要加上-g的选项,以便生成调试信息方便gdb进行调试。若不加该选项,那么调试信息只有汇编代码而无源文件代码信息。

(2)启动gdb进行调试

启动gdb调试文件,可以直接在命令行界面输入gdb <file>,或者输入gdb之后–>进入gdb后再用file命令打开调试文件,即输入 file <file>

4.查看源码
  • list(l)——查看最近10行源码
  • list fun——查看fun函数源码
  • list file:fun——查看file文件中的fun函数源码(可用查看多个文件中的源码)

进入gdb调试器之后,使用list或l命令查看源文件代码:

查看特定的行
查看源码第10行的代码,使用命令:l 10

5.设置断点
  • break 行号
  • break fun
  • break file:行号
  • break file:fun
  • break if <condition>——条件成立时程序停住
  • info break(i b)——查看断点
  • delete(d) n——删除断点n
  • watch expr——设置观察点,当expr值发生改变,程序停止
  • catch <event>——设置捕捉点,当event发生时,程序停止

break命令设置断点,比如在第10行设置断点,或者在main函数处设置断点:

info break或i b命令查看断点:

6.单步调试
  • continue(c)——运行至下一个断线
  • step(s)——单步跟踪,进入函数,类似于VC中的step in
  • next(n)——单步跟踪,不进入函数,类似于VC中的step out
  • finish——运行程序,直到当前函数完成返回。并打印函数返回时的堆栈地址和返回值及参数值等信息
  • until——当厌倦了在一个循环体内单步跟踪时,这个命令可以运行程序直到退出循环体

(1)r(run)命令启动被调试程序(r命令后也可以跟参数:r arg1 arg2):

(2)程序启动后,就停在断点处,使用s(step)命令,单步跟踪:

(3)p(print)命令查看当前变量的值:

i当前值为1

(4)此时我们跟踪到了for循环,并且有如下打印情况:

如果想要跳出for循环,使用命令until:

(5)命令c(continue)运行到下一个断点处:

(6)finish命令退出当前函数,比如当前我跟踪到了函数func中,然后想要退出该函数继续跟踪,就使用命令finish。

7.gdb常用命令总结
  • run(r)——运行
  • list(l)——列出源代码
  • break(b)——设置断点
  • info break(i b)——查看断点信息
  • continue(c)——继续程序运行,直到下一个断点
  • watch——设置观察点
  • step(s)——单步跟踪,类似于VC中的step in
  • next(n)——单步跟踪,类似于VC中的step out
  • finish——运行程序,直到当前函数返回
  • until(u)——退出循环
  • print(p)——查看运行时的变量以及表达式
8.查看运行时数据
  • print——查看变量值
  • ptype——查看类型
  • print array——查看静态数组array
  • print *array@len——查看动态内存(该数组是malloc动态分配的,len是要查看的内存长度,即元素个数)
  • print x=5——改变运行时数据
  • x <addr>——查看内存地址addr中的值

针对最后一个简单介绍一下,比如我现在程序的for循环处设置一个断点,然后改变for循环中i的值,使之大于100,这样就会跳出for循环,然后在打印出result的值进行观察:

由上分析
(1)进入for循环之后,进行单步跟踪,打印除数次数i=1, result=1。
(2)然后动态改变i的值:print i=101,使得i>100,再次单步跟踪之后编译器检查到i不满足循环条件,于是跳出循环转至下一行的printf语句处。
(3)p result,验证result的值,result=1,而不是5050,说明i的值确实被改变了。

9.程序错误

程序的错误通常有如下三种错误:

  • 编译错误:编写程序的时候没有符合语言规范导致编译错误。
  • 运行时错误:编译器检查不出这种错误,但在运行的时候可能会导致程序崩溃。
  • 逻辑错误:编译和运行都很顺利,但是程序没有干它该干的事情。
10.gdb调试逻辑错误

(1)有如下代码:

这段代码是想要将hello以及olleh输出,但是运行之后,并无olleh输出。

这是怎么导致的呢?先说结果,上述olleh存在reverse_str数组中的实际情况是这样的:\000olleh,由于是以\0开头,所以输出字符串为空。

(2)下面我们用gdb调试一下:

1)首先在for循环出设置一个断点—>单步跟踪到i=0,str[i]=’h’,然后我们查看reverse_str[5-i]=’h’,这个时候都还是OK的,h确实存入reverse_str数组了。

(2)我们继续跟踪,直到退出for循环,或者使用until命令直接跳出循环:

打印出reverse_str,发现该数组的第一个字段并没有被填充进去,仍然是\000,所以reverse_str无法打印输出,这就解释的通了。

(3)那么,该怎么改呢?我们可以将其字母依次向前移动一格,即,将for循环中的[5-i]改为[4-i]即可。

11.gdb调试段错误

(1)段错误是由于访问非法地址而产生的错误:
1)访问系统数据区,尤其是往系统保护的内存地址写数据。最常见就是向一个地址为0的指针中写入数据
2)内存越界(数组越界,变量类型不一致等)访问到不属于你的内存区域。

(2)有如下示例,向一个地址为NULL的指针中写入数据:

运行出错,段错误:

(3)使用gdb进行调试,直接命令r运行程序会,编译器会提示你段错误出现在哪一个位置,bt(backtrace)回溯命令可以进行堆栈跟踪,我们知道是第7行出现错误后list 7定位到出错代码处:

附:gdb堆栈跟踪方法
程序“调用堆栈”是当前函数之前的所有已调用函数的列表(包括当前函数)。每个函数及其变量都被分配了一个“帧”,最近调用的函数在 0 号帧中。要打印堆栈,使用bt命令,如上面所示。

12.core文件调试

有时程序会在执行过程中崩溃,但我们又无法获知程序是由于产生了什么动作而崩溃的,无法再现这一过程也就无法进行调试。这个时候可以利用core文件进行调试。

(1)core文件:在程序崩溃时,一般会生成一个叫core的文件。core文件记录的是程序崩溃时的内存映像,并加入调试信息。core文件生成的过程叫做core dump。

(2)设置生成core文件
1)ulimit -c:查看core-dump状态,若为0则不产生core文件
2)ulimit -c 数字(如ulimit -c 1024):使用该命令设置core文件
3)ulimit -c unlimited:也可以使用该命令,不限制core文件

例如,对上面的例子,设置core为unlimited之后,再次运行该程序会生成core文件:

然后就可以利用core文件进行调试,命令格式如下所示。

(3)gdb利用core文件调试
1)gdb文件名 core文件
2)bt

使用core文件调试上述程序:

如上提示,第七行出现段错误。

附其他学习资料:
陈皓“用GDB调试程序”:http://blog.csdn.net/haoel/article/details/2879

《Linux下开发工具学习-gdb》有3个想法

发表评论

电子邮件地址不会被公开。 必填项已用*标注