GDB轻松调试
一、引言
在了解GDB可以做什么,怎么做之前,让我们先来看看为什么要用GDB,或者说对调试工具有什么期望。
一般我们使用GDB(或其他调试工具)是为了发现程序bug,更经常地是在已知程序有错的情况下定位bug。既然这样,我们就需要跟踪程序的执行情况,查看程序执行是否正常,当然这就需要有个让我们与执行程序交互的环境,调试工具提供一个能让程序在你的掌控下执行,并让你能够查看一些执行过程中的“内幕信息”的环境。
为了查看程序运行过程中的状态,我们就希望程序能在适当的位置或者在一定的条件下能够暂停运行;为此,调试工具提供了断点、查看变量/表达式、显示程序栈等功能。看了某个点的“内幕”后,我们还期望更多,所以要能控制程序运行才行,这就要求断点、继续运行、单步(多步)运行、进入函数运行等功能,在某些情况下,还需要通过修改当前的执行环境(变量等)来达到期望的执行顺序。也就是说,光看着是不够的,还需要能改才行。
理解了这些问题后,我们就明白GDB的各个功能的用意了,自然也就明白该如何使用调试工具了。当然,要让GDB有效的发挥作用,还是需要一定的经验与技巧,而这主要靠实践,学习资料(包括本文)充其量只能帮你一把(小心别让它帮倒忙)。
总而言之,我们首先要明白使用调试工具的目的和用意,才能理解它的各项功能,才能借助它快速有效的发现问题;否则,即使工具再强大,你也不知道该如何使用才好。
另外要多结合使用代码检视、运行日志、测试工具等方法来发现潜在的问题,提供程序的质量。这些问题将在另文探讨,先做个广告。
二、GDB能做什么
GDB可以用来调试C、C++、Modula-2的程序。一般来说,GDB能做的事大致可以分为四类:
1、启动程序,按指定的方式执行程序。
2、在指定条件下使程序暂停.
3、当程序被停住时,可以检查此时你的程序中的变化。
4、改变程序中的变量或执行顺序来试验。
三、GDB使用概述
首先要了解的是gdb的help命令,因为你可能记不住各个命令的语法和用途,但只要能正确使用help命令,你就不需要任何其它的gdb资料。
启动gdb后,输入help
[eric@linux eric]$ gdb
GNU gdb Red Hat Linux (5.3.90-0.20030710.40rh)
Copyright 2003 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i386-redhat-linux-gnu".
(gdb) help
List of classes of commands:
aliases -- Aliases of other commands
breakpoints -- Making program stop at certain points
data -- Examining data
files -- Specifying and examining files
internals -- Maintenance commands
obscure -- Obscure features
running -- Running the program
stack -- Examining the stack
status -- Status inquiries
support -- Support facilities
tracepoints -- Tracing of program execution without stopping the program
user-defined -- User-defined commands
Type "help" followed by a class name for a list of commands in that class.
Type "help" followed by command name for full documentation.
Command name abbreviations are allowed if unambiguous.
(gdb)
如上文显示,gdb的命令很多,所以把它分成许多个种类。help命令只是例出gdb的命令种类,如果要看某类中的命令,可以使用help <class> 命令,如:help breakpoints,查看设置断点的所有命令。当如也可以直接help <command>来查看某个命令的具体信息。
gdb 技巧:在记不清整个命令时,可以只打命令的前一个或几个字符,然后敲击两次TAB键来列出所有以这几个字符开头的命令;另为,大多命令都有缩写,如b同 break,c同continue,n同next,p同print等。另为,一个命令在输入能唯一标示命令的前缀后,按一下TAB键就能补齐命令的全称,比如输入ba后按一下TAB键,就自动补齐为backtrace,输入pr后按一下TAB键就补齐为print。
为调试编译代码
为了使 gdb 正常工作, 你必须使你的程序在编译时包含调试信息. 调试信息包含你程序里的每个变量的类型和在可执行文件里的地址映射以及源代码的行号. gdb 利用这些信息使源代码和机器码相关联.
在编译时用 -g 选项打开调试选项.
在GDB中运行程序
当以gdb <program>方式启动gdb后,可以使用r或是run命令运行程序。在程序运行之前,你有可能需要设置下面四方面的事。
1、程序运行参数。
set args 可指定运行时参数。(如:set args 10 20 30 40 50)
show args 命令可以查看设置好的运行参数。
2、运行环境。
path <dir> 可设定程序的运行路径。
show paths 查看程序的运行路径。
set environment varname [=value] 设置环境变量。如:set env USER=hchen
show environment [varname] 查看环境变量。
3、工作目录。
cd <dir> 相当于shell的cd命令。
pwd 显示当前的所在目录。
4、程序的输入输出。
info terminal 显示你程序用到的终端的模式。
使用重定向控制程序输出。如:run > outfile
tty命令可以指写输入输出的终端设备。如:tty /dev/ttyb
调试已运行的程序
可以有两种方法调试已运行程序:
1、用ps查看正在运行的程序的进程ID,然后用gdb <program> PID格式挂接正在运行的程序。
2、先用gdb <program>关联上程序,并进行gdb,在gdb中用attach命令来挂接程序正在运行的进程。detach可用来取消挂接的进程。
暂停/恢复程序运行
你可以使用info program 来查看程序的当前的执行状态。
在gdb中,我们可以有以下几种暂停方式:断点(BreakPoint)、观察点(WatchPoint)、捕捉点(CatchPoint)、信号(Signals)、线程停止(Thread Stops)。如果要恢复程序运行,可以使用c或是continue命令。
查看变量/表达式的值
可以使用print expr(或p expr)来查看程序变量/表达式的值
显示程序栈
可以使用backtrace(或bt)来显示程序栈
单步跟踪
next [n] 执行下一条(或n条)语句,不进入子程序
step [n] 执行下一条(或n条)语句,进入子程序,可用finish从子程序返回
四、GDB常用命令
backtrace 显示程序中的当前位置和表示如何到达当前位置的栈跟踪(同义词:where)
breakpoint 在程序中设置一个断点
cd 改变当前工作目录
clear 删除刚才停止处的断点
commands 命中断点时,列出将要执行的命令
continue 从断点开始继续执行
delete 删除一个断点或监测点;也可与其他命令一起使用
display 程序停止时显示变量和表达时
down 下移栈帧,使得另一个函数成为当前函数
frame 选择下一条continue命令的帧
info 显示与该程序有关的各种信息
jump 在源程序中的另一点开始运行
kill 异常终止在gdb 控制下运行的程序
list 列出相应于正在执行的程序的原文件内容
next 执行下一个源程序行,从而执行其整体中的一个函数
print 显示变量或表达式的值
pwd 显示当前工作目录
pype 显示一个数据结构(如一个结构或C++类)的内容
quit 退出gdb
reverse-search 在源文件中反向搜索正规表达式
run 执行该程序
search 在源文件中搜索正规表达式
set variable 给变量赋值
signal 将一个信号发送到正在运行的进程
step 执行下一个源程序行,必要时进入下一个函数
undisplay display命令的反命令,不要显示表达式
until 结束当前循环
up 上移栈帧,使另一函数成为当前函数
watch 在程序中设置一个监测点(即数据断点)
whatis 显示变量或函数类型
命令的具体使用方法请用上面介绍的help查询,看不明白的地方就多试试。
五、用例子说话
本文是打算写个简单的程序作为例子讲解的,后来一想:“太假”,就讲一个前几天我的实际调试经历吧,因为当时没抓图,这里就用文字描述了,请读者注意其中的思路和方法,具体的一些操作就要劳烦自己去实践了
背景:在将一个linux程序(姑且就叫A吧)重redhat 9.0移植到redhat es时发现程序core了
开始了,呵呵:
1、首先我查看了程序日志,找到引起程序core掉的数据(一个网页);//所以说日志很重要
2、下载了那个网页,用它作为输入,结果必core;//确认出错环境
3、用gdb启动程序,然后触发错误后,用bt查看程序栈,记录栈中的函数调用链以及出错的代码行数
4、在用gdb启动程序,在出错行前设断点,运行之,再触发错误
5、使用next和step精确定位到出错行
6、print一个指针变量,发现不是NULL,再看指针所指结构的各个变量也正常
7、好像没错呀,呵呵,此处是个循环,继续单步便重复6
8、发现循环中指针递减,怀疑指针所指数组越界,打印数组起始位置地址
9、继续循环一直到出错,打印指针变量,发现其指向的地址低于数组起始位置地址,真的越界了
10、初步算是找到了,查看程序源码,发现循环中没有判断该指针是否低于数组起始位置地址
11、修改代码后重新运行,程序不core了
12、将新程序放到正常执行环境下工作,长时间运行后没有发现该问题重现,确认解决问题
13、通知出错部分(一个功能函数库)的作者问题找到、原因
注:为简单起见省略了过程中的一些因系统特殊性引起的工作