1. 调试利器gdb
1.1. 调试意义
搞电子都知道,电路不是焊接出来的,是调试出来的。程序员也一定认同,程序不是写出来的,是调试出来的。那么调试工具就显得尤为重要,linux作为笔者重要的开发平台,在linux中讨论调试工具主要是为那些入门者提供一些帮助。调试工具能让我们能够监测、控制和纠正正在运行的程序。我们在运行一些程序的时候,可能被卡住或出现错误,或者运行过程或结果,没能如我们预期,此时,最迫切需要明白究竟发生了什么。为了修复程序,剖析和了解程序运行的细节, 调试工具就成为了我们的必备工具,工于善其事,必先利其器。
1.2. 命令参数
描述 backtrace(或bt) 查看各级函数调用及参数 finish frame(或f)帧编号 连续运行到当前函数返回为止,然后停下来等待命令 选择栈帧 info(或i) locals 查看当前栈帧局部变量的值 list(或l) list 行号 list 函数名 next(或n) print(或p) quit(或q) set var start step(或s)
列出源代码,接着上次的位置往下列,每次列10行 列出从第几行开始的源代码 列出某个函数的源代码 执行下一行语句 打印表达式的值,通过表达式可以修改变量的值或者调用函数 退出gdb调试环境 修改变量的值 开始执行程序,停在main函数第一行语句前面等待命令 执行下一行语句,如果有函数调用则进入到函数中
比如还有几个命令如下: wath 观察一个变量
current 跳转到下个断点,或则跳转到观察点 quit 退出gdb调试
1.3. 基本使用
如果一个进程已经在运行,你需要将GDB连接到它上面,可以通过指定进程ID来实现。假设程序已经崩溃,要分析问题的原因,则用GDB分析core文件。
1.3.1. 启动程序
一旦你在GDB里面,使用'run'命令来启动程序进行调试。
1.3.2. 给程序传参数
使用'set args'给你的程序传参数,当程序下次运行时将获得该参数。'show args'将显示传递给程序的参数。
1.3.3. 检查堆栈
每当程序停止,任何人想明白的第一件事就是它为什么停止,以及怎么停在那里的。该信息被称为反向跟踪。由程序产生每个函数调用和局部变量,传递的参数,调用位置等信息一起存储在堆栈内的数据块中,被称为一帧。我们可以使用GDB来检查所有这些数据。 GDB从最底层的帧开始给这些帧编号。
bt: 打印整个堆栈的回溯 bt 打印n个帧的回溯
frame : 切换到指定的帧,并打印该帧 up : 上移'n'个帧
down : 下移'n'个帧 ( n默认是1)
1.3.4. 检查数据
程序的数据可以在里面GDB使用'print'命令进行检查。例如,如果'x'是调试程序内的变量,'print x'会打印x的值。
1.3.5. 检查源码
源码可以在GDB中打印。默认情况下,'list'命令会打印10行代码。 list : 列出'linenum'行周围的源码 list : 从'function'开始列出源码 disas : 显示该函数机器代码
1.3.6. 停止和恢复程序
使用GDB,我们可以在必要的地方设置断点,观察点等来停止程序。 break : 在'location'设置一个断点。当在程序执行到这里时断点将被击中,控制权被交给用户。
watch : 当'expr'被程序写入而且它的值发生变化时GDB将停止 catch : 当'event'发生时GDB停止 disable : 禁用指定断点 enable : 启用指定断点
delete : 删除 断点/观察点/捕获点。 如果没有传递参数默认操作是在所有的断点
step: 一步一步执行程序
continue: 继续执行程序,直到执行完毕
1.3.7. 退出 GDB
用'quit'命令还从GDB中退出。
GDB还有更多的可用选项。里面GDB使用help选项了解更多详情。
1.4. 普通命令
1.4.1. list命令
list linenum 显示程序第linenum行周围的程序 list function 显示函数名为function的函数的源程序 list 显示当前行后面的源程序 list - 显示当前行前面的源程序
1.4.2. run(r)
运行命令。
run args run命令可以直接接命令行参数值,也可以在执行run之前通过 set args + 参数值实现。
1.4.3. break(b)
? 打断点,使用方法:
b linenum 在某行打断点
b +offset/-offset 在当前行号的前面或后面offset停住 b filename:linenum 在某文件的某行打断点 b filename:function 在某文件某个函数入口停住 b *address 在程序的运行地址处停住 b 没有参数在下一句停住
b where if condition 当某个条件满足时,在某一行停住(这个很有用,比如b 10 if (ret == 5)
对于break命令,我们要灵活使用。例如打多个断点。多线程程序中我们可以主函数中线程创建后立即打断点,执行线程函数入口打断点等。
关闭断点:delete(d) breakpoint-id
? 设置断点的条件方式如下:
设置断点的时候加入条件
break foo if value_a > value_b 用condition命令
condition bnum expression 例如: condition 6 if value_a == 10
如果你设置的断点条件,无效会提示:(这于断点的上下文有关,关于断点的上下文会子专门章节阐述)
No symbol \取消断点条件 condition bnum 断点条件特殊用法
断点条件的一个特殊用法是,程序只有在到达断点一定次数之后才会停止。这用一个特殊的命令可以实现。
ignore bnum count
ignore 设置的触发条件在重新加载程序之后自动删除。
ignore 2 10 //触发断点10次后,才会停止,每次触发断点count自减1
如果一个断点及设置了条件,又设置了触发次数,在触发次数count为0之前,是不会判断断点的条件。
ignore 命令对breakpoint watchpoint catchpoint都有效。
1.4.4. 单步命令
step count 一次性执行count步,如果有函数会进入函数 next count 一次执行count,不进入函数
finish 运行程序,直到当前函数完成返回,并打印函数返回时的堆栈地址和返回值以及参数信息
until 退出循环体(尤其是针对for循环这种,很烦的)
1.4.5. continue命令
当程序被停住之后,可以使用continue(c)命令,恢复程序的运行直到程序结束,或到达下一个断点。这里要注意如果没有断点程序是会直接结束的。
1.4.6. print(p)命令
这个命令比较常用,用来查看我们想看的内容。比如有关数组可以看全部,也可以看从左到右某一部分:
print命令针对变量查看的输出格式有: x 按十六进制格式显示变量 d 按十进制格式显示变量
u 按十六进制格式显示无符号整型 o 按八进制格式显示变量
t 按二进制格式显示变量t 按二进制格式显示变量 a 按十六进制格式显示变量 c 按字符格式显示变量 f 按浮点数格式显示变量
1.4.7. watch命令
这个命令比较有用。watch一般用来观察某个表达式(变量也是一种表达式)的值是否有变化,如果有变化,马上停住程序。我们有一下几种方法设置观察点: