前言
作为一名计算机系的学生,接触编程也有两年时间了,一个比较深的感受:光会码代码是远远不够的,调试代码的能力十分重要!
经常有同学说,这个我知道思路,代码也打出来了,为什么跑出来的结果总是不如我所愿!打代码1小时,调试1天这种事常常发生。
当然,这并不是我们打代码的能力不够,再好的程序员也会因为各种原因打出有bug的程序,debug是家常便饭。
两年以来,我身边的人(包括我),大部分在debug的时候用的是“打印法”——在源代码中加入大量的输出语句来观察代码执行过程中发生的事情。诚然,这种方法简单易行,对于少量的代码,用此法是比较方便的,但是一旦代码量增加,程序复杂起来,就容易乱上加乱了。
最近接触了gdb调试器,功能十分强大,希望以后在实战中能够熟悉这个工具,利用它来完成调试。
gdb主要功能
- 监视程序中变量的值的变化
- 设置断点,使程序在指定的代码行上暂停执行,便于观察
- 单步执行代码
- 分析崩溃程序产生的core文件
使用时的一些问题
1.单步执行时,不进入标准库函数内部,在gdb中执行下列命令:
show debug-file-directory
#显示路径为 /usr/lib/debug
set debug-file-directory
#将路径设置为空
show debug-file-directory
#显示路径为空
#每一次进入gdb都需要重新设置一次
#单步执行时,可以用next,不进入函数内部
2.用gdb调试,使用gcc编译时加上-g选项,将调试信息加到程序中:
gcc -ggdb3 -o test test.c
#-ggdb3为最大限度包含调试信息
调试步骤
1.调用gdb
gdb test
2.设置断点并调试
(gdb)break main
#在main函数内第一个非简单赋值语句处中断
(gdb)break 行号
#break可用‘b’代替
(gdb)run
#执行程序
(gdb)step
#单步执行,可用‘s’代替,会显示行号+该行代码(下一步准备执行的代码)
#类似的有next,但是next不跟踪到函数内部
(gdb)print 表达式
(gdb)print 变量=表达式
(gdb)print 开始表达式@要打印的连续内存空间大小
#打印表达式or变量的值,编号并存入历史记录中。$n表示第n个print命令,$$n表示从当前编号倒着数的第n+1个
(gdb)display 表达式
#每当运行到断点,就显示表达式的值
(gdb)quit
#退出gdb
(gdb)finish
#强制退出调试
显示数据命令
#1.display和print命令
#2.内存检查命令
x/format address
#format:<n/f/u>,分别代表显示单元的个数,格式,字节数。
#如x/2c:显示2个单元,char型,4个字节(u参数缺省,默认为4)
#3.printf命令
#4.set命令:显示并修改数据
set variable height=200
设置断点
break test:21 #文件test的第21行
break printmessage #函数printmessage处设置断点
continue #恢复程序运行(至下一断点或结束)
cont 2 #忽略断点2次
condition 1 checksum>700 #当表达式满足时,1号断点才中断
tbreak 41 #在41行处设置断点,只中断一次
enable 断点编号 #恢复暂时失效的断点
disable 断点编号 #使断点失效
delete 断点编号 #删除断点
clear 断点行号 #清除断点
设置观察窗口
watch counter>15 #当counter>15时才停下来,counter的取值需在作用域内
查看栈信息
#使用backtrace或者bt
frame <n> #frame 0表示栈顶,frame 1表示栈的第一层
#info args:显示当前函数的参数名及值
#info locals:显示当前函数中所有局部变量及值
查看源程序
list <linenum> #显示第linenum行周围的源程序
list <function> #显示函数的源程序
搜索源代码
forward-search <regexp> #向前搜索,regexp为正则表达式
reverse-search <regexp> #向后搜索
查看汇编代码
disassemble func #查看函数func的汇编代码
改变程序的执行
set var width = 47 #改变变量的取值
jump <linespec> #跳转到某个运行点
jump <address> #跳转到某个内存地址
#jump不会改变当前程序栈中的内容,谨慎使用!
return <expression> #强制函数返回
程序崩溃:core dump分析
gdb test core.**** #将core装载进gdb,查找崩溃原因及位置
bt #打印栈桢指针(可知道每个函数被执行的最后一行)
结语
以上只是我在学习过程中的一些笔记,没有很具体的举例子说明,比较抽象。建议初学者根据知识点,找个例子一步步实操一下,熟能生巧,学起来也就十分轻松了!