转自:http://www.findfunaax.com/notes/file/49
一、综述
在《软件调试艺术》这本书的前言里,讲了一个叫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 | 根据行号设置断点 |
breakfilename:linenumber | 在源代码文件filename的linenumber处设置断点 |
break filename:function | 在源代码文件filename的function处设置断点 |
break *address | 在虚拟内存地址处设置断点(用于源代码不可用或共享库的调试) |
注:
- 断点的有效性持续到删除、禁用或退出GDB时
- 临时断点tbreak有相同的命令设置
- hbreak,thbreak等其它断点类型
- 修改代码后,GDB自动重载,但断点是根据行号继承上次的
4.删除断点
命令 | 功能 |
---|---|
delete breakpointlist | 根据断点号列表删除断点 |
delete | 删除所有断点 |
clear | 清除GDB将执行的下一处断点 |
clear function、 filename:function 、 linenumber 、 filename: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类。
- 使用step和next单步调试,仅执行代码下一行然后再次暂停;
- 使用continue使GDB无条件继续执行,直到它遇到一个断点或程序结束;
- 使用finish或until有条件继续执行,直到到达某个预先条件、另一断点或程序结束;
8.条件断点
常用于监视变量是否得到错误的值。如果得到,就中断停止。
命令 | 功能 |
---|---|
break break-args if (condition) | condition为布尔表达式,'('号可选 |
cond N 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来查看进程的内存布局。