gdb 软件调试

一、综述

在《软件调试艺术》这本书的前言里,讲了一个叫Andrew的学生大一时就开始学习了编程,可到大四时还在使用输出调试的方法。 直到他的教授强烈要求他改用正式的调试工具。这怎么和我的情况极其相似呢!-_-!我也是一个'Andrew',也该正式学习学习调试工具了。

在此选用gdb 7.0.1版本,参考书目《软件调试的艺术》。更重要的书是GDB官方手册。

二、快速上手

若调试过程中一眼看不出错误所在,可以设置监视点或使用二分搜索原理。

设置断点 -> contine到断点 -> next或step单步调试一段 -> contine到下一断点

调试工具一般有gdb CLI, DDD, eclipse中集成CDT, Emacs中 gdb -tui 打开终端图形化调试或在进入gdb以后输入Ctrl+X+A

重新编译代码时最好不要退出GDB,因为会保留调试时断点等信息。

主目录的.gdbinit文件用于保存gdb的一些宏,在加载可执行文件之前读取; 本地项目的.gdbinit用于保存之前的调试信息,在加载可执行文件之后读取。

'gdb -command=z x'在执行x文件之前读取z文件中的命令

一个为调试的编译过程: gcc -g3 -Wall -Wextra -c main.c swapper.c gcc -o swap main.o swapper.o gdb swap

三、主要调试操作

1. 单步调试源码

断点
'break 行号','info break'查看行号,'break 行号 if 条件'条件暂停
删除断点
'clear 断点号'
单步调试
'next' 执行下一行;执行到函数时会出现在下一次调用函数时;
单步调试
'step' 执行下一行;执行函数时会进入函数;
恢复操作
'continue' 恢复执行并继续,直到遇到断点;
临时断点
'tbreak' 有效期只有首次达到此断点时为止;
设置条件
'condition num 变量条件' num号断点只有在条件成立时才暂停;
函数完毕
'finish' 执行直到当前栈帧完成之后为止;即在函数返回之后停止;
循环完毕
'until' 执行直到到达当前循环体外下一行代码;用于跳过循环,可接收参数

2. 检查变量

输出变量
'print 变量';
监视点
'watch 变量' 当程序变量值改变时gdb暂停,查看程序状态;
表达式监视点
'watch (表达式)', 表达式返回布尔类型值,ex: watch (z > 28);
观察栈帧
'frame 序号', 当前函数栈帧为0,父函数栈帧为1,父帧的父帧为2,以此类推;
移动观察栈帧
'up' 父栈帧, ex:0->1;
移动观察栈帧
'down' 子栈帧, ex:1->0;
显示所有栈帧
'backtrace';

四、GDB调试方法

序号 步骤 命令 备注
1 编译源代码 $gcc -g -Wall -o main program_file.c
2 开启gdb工具 $gdb main -tui
3 从开始处运行程序 run 若想给程序传入参数,在此输入;再次运行时不必指定参数
挂起 Ctrl+C

五、暂停机制

1. 3种暂停程序方法

以下在帮助文档里可统称为断点:

断点
通知GDB特定位置暂停执行;GDB执行到那一行代码之前;
监视点
通知GDB特定值变化或表达式成立时暂停
捕获点
通知GDB特定事件发生时暂停

2. 跟踪断点

'info breakpoints'列出断点

3.设置断点

命令 功能
break function 在函数入口设置断点(会在所有名为function处设置断点,如c的static函数不同文件可重名,C++的重载函数)
break linenumber 根据行号设置断点
break filename:linenumber 在源代码文件filename的linenumber处设置断点
break filename:function 在源代码文件filename的function处设置断点
break *address 在虚拟内存地址处设置断点(用于源代码不可用或共享库的调试)

注:

  1. 断点的有效性持续到删除、禁用或退出GDB时
  2. 临时断点tbreak有相同的命令设置
  3. hbreak,thbreak等其它断点类型
  4. 修改代码后,GDB自动重载,但断点是根据行号继承上次的

4.删除断点

命令 功能
delete breakpointlist 根据断点号列表删除断点
delete 删除所有断点
clear 清除GDB将执行的下一处断点
clear functionfilename:functionlinenumberfilename:linenumber 同break相似

5.禁用断点

命令 功能
disable breakpointlist 禁用断点
enable breakpointlist 启用断点
enable once breakpointlist 启用一次断点

6.GDB断点属性

序号Num 属性Type 部署Disp 启用状态Enb 地址Address 位置What
1.. 断点Breakpoint
监视点watchpoint
捕获点catchpoint
保持Keep
删除del
禁用dis
y/n 0x........ 断点位置的行号和文件名

7.恢复执行方法

调试的过程是确认程序中某些变量有我们认为该有的值。这就需要暂停去观察。可暂停后还要进行恢复执行。GDB的恢复执行方法有3类。

  1. 使用step和next单步调试,仅执行代码下一行然后再次暂停;
  2. 使用continue使GDB无条件继续执行,直到它遇到一个断点或程序结束;
  3. 使用finish或until有条件继续执行,直到到达某个预先条件、另一断点或程序结束;

8.条件断点

常用于监视变量是否得到错误的值。如果得到,就中断停止。

命令 功能
break break-args if (condition) condition为布尔表达式,'('号可选
cond N condition 转换无条件的中断为有条件的
condition 包括:
相等、逻辑和不相等运算符 < <= == != > >= && ||
按位和移位运算符 & | ^ >> <<
算术运算符 + - * / %
自己的函数 要链接到程序中,ex: 'break test.c:myfunc if ! check_variable_sanity(i)'
库函数 要链接到程序中,ex: 'break 44 if strlen(mystring) == 0';GDB只认返回值为int

9.断点命令列表

当GDB遇断点停止时,几乎每次都要查看某些变量。因此可以让GDB每次到达某个断点时自动执行一组指令,这就是断点命令列表。 commands breakpoint-number
...
commands
...
end
可以给定一个空commands来取消此命令列表。 可以在命令列表第一行加上silent命令,使GDB安静的触发。 可以用define命令创建宏来方便调用,用show user查看所有宏列表。

六、检查和设置变量

1.查看变量

命令 功能
x 检查给定地址的内存
display 每次暂停时输出指定条目
call 调用程序内的函数
print *pointer@number_of_elements 打印动态数组,使用GDB的人工数组。
info locals 得到当前栈帧中所有的局部变量列表

2.设置变量

ex: xset x = 12

3.GDB自己的变量

使用值历史
$1,$2..., $
方便变量
临时记录某些节点的值,ex 'set $q = p', q是方便变量,p是程序变量

七、零散

1. GDB给人一种逐行运行源代码的错觉。

2. 变量名就是增强符号表。

3. 在调试完成前永远不应优化代码。(过早优化是万恶之源)

4. 使用cat /proc/进程号/maps来查看进程的内存布局。

chenyu 于 2014-04-11 22:48 时评论:
------断点 通知GDB特定位置暂停执行;GDB执行到那一行代码之前;---------- | | | * 当用户指定断点位置为无效代码行时,自动滑落到下方的最近一条有效指令地址。
ranler 于 2014-04-13 22:36 时评论:
@chenyu 指的是断点代码处没有对应指令生成么?

发表评论