斯坦佛编程教程-Unix编程工具(三)

 

你的程序中可能存在一两个bug,有很多找bug的方法,但是一个好的调试器可以让这个工作变得简单起来。在大部分的各种大小的程序中,找出程序中所有的bug几乎是不可能的,你只能一直盯着代码——你需要通过程序运行时的行为来找出这个bug。投资一些时间来好好学习一下调试器非常值得的。
GDB 我们把GNU的调试器称作gdb,它几乎在每一个领域都超过了dbx,并且和gcc编译器配合得非常完美。其他的一些漂亮的调试环境包括ups和CodeCenter,但是他们都不如gdb通用,而且CodeCenter可不便宜。虽然gdb没有一个像其他软件一样漂亮的图形界面,但它能为高端的程序员提供几乎所有他们想知道的信息。
这个部分并不会深入描述gdb的每一个细节,但是重要的部分我们都会提到。我们可以用help命令来查看gdb在线的帮助。如果你需要更多的信息,试着在终端用xinfo命令来获取,或者使用emacs的info-browser模式。
开始调试 在make的时候有两种方式来调用gdb,要在shell中启动调试器,只要在终端输入: gdb program
program是你要进行调试的目标可执行文件。如果你在调用gdb的时候没有指明任何目标的话,gdb在启动后会让你指定目标文件,然后你才能做一些有用的事。
另一种选择是在使用emacs的时候你可以用[Esc]-x 调用gdb,之后会提示你可执行文件的名字。如果emacs没有指明目标,则你无法使用gdb。emacs的窗口会分裂成两个部分,一个显示gdb的信息,另一个显示源文件。
运行调试器 当你开始运行的时候,调试器会装载你的程序和它的特征表(包含了一些比如变量名,源文件等等的有用信息)。这个特征表就是-g这个编译条件所产生的,调试器会读取这些信息来运行程序。
调试器是一个交互性的程序,当它启动的时候,它会给你提示框用来输入命令,调试器中最常用的命令有:设置断点,单步调试,断点后继续运行,检查变量的值.
运行程序 run重置程序,从最开始的地方开始运行。你可以在运行的时候提供一些参数,方式如同你在终端运行程序是一样的。 step单步跟踪调用,运行源文件的下一行代码,并返回调试器。如果调用了子程序,那么就跟踪到子程序。 step count 运行到源代码的"count"行。 next  单步调用,不会跟踪到子程序。 finish一直运行到当前方法或函数完成。 return将选取的栈的帧返回到它的调用者处。 jump address从某个地方或某一行继续运行程序
当一个目标可执行文件第一次被选中,那么当前的源文件就是main函数所在的文件,而当前的代码行就是方法里面的第一行执行的代码。 当你在运行程序的时候,总会运行到一些源文件的某一行,当你暂停程序的时候(当程序流遇到断点的时候),当前的目标文件就是当前执行代码所在的源文件。同样的,当前源代码行就是你所执行的代码行。
断点 你可以在程序中的某一行设置断点。每一个断点在设置的时候都会被加上一个编号,然后你就可以通过编号来操纵它了。 你可以用break命令在代码中你想让程序停下来的地方来设置断点,断点的位置可以通过很多的方式来设置,比如用文件名或是行号或是方法名(行中要有实际运行的代码——注释和空格都不能算)。如果文件没有指定,那么就默认为当前的目标文件,如果没有设置断点,那么当前代码行就是断点。 gdb提供了以下一些命令来控制断点: info break打印初所有的断点的位置和状态。 break function在指定的方法的开始的位置设置一个断点。 break linenumber在当前源文件特定的行设置一个断点。 breakfilename:linenumber在特定文件的特定行设置一个断点。 你也可以设置一个if语句来创建一个条件断点: break fn if expression当表达式的值为正的时候就在断点处停止。表达式可以是任何C的表达式,当遇到断点的时间偶就会使用当前的栈来计算。 disable/enable breaknum在设置了断点的行开/关断点。 delete breaknum删除某行的断点 commands breaknum设置当程序运行到断点处时将要运行的命令。命令可以是任意的C声明或是gbd命令。这可以用于实时的编码而不用重新编译(太棒了!)。 cont继续运行已停止的程序。
例如,下面的命令... break binky.c:120 break DoGoofyStuff
在binky.c的120行处设置断点,而另一个断点在方法DoGoofyStuff这个方法的首行。当程序运行到这些位置的时候,程序将会停止让你在调试器中获取一些信息。 Gdb和其他大部分的调试器都提供了决定程序运行方式的机制。我们经常会问到我运行到了程序的哪个位置(a)还有程序中的变量的值是多少(b)。
检查栈 为了回答问题(a),我们可以用breaktrace命令来检查程序运行栈。程序运行栈类似于程序运行时留下的脚印;每当方法被调用的时候,运行栈就会被创建,当方法运行到return的时候,运行栈就会清空,然后栈也会被回收。这些栈帧包含着一系列调用语句的变量信息还有每个方法调用所传递的参数。方法调用语句会将当前运行跳转到相应的方法中去。
Gdb将栈帧设置成从0到无穷大的有序帧。任何时候,gdb都是只将一个帧作为当前帧。对于选定的帧,变量的查找是确定的。当程序的在调试遇到断点的时候,gdb会选择最内层的帧。下面的一些命令可以用于通过序号或是地址来选择其他的帧。 backtrace显示所有栈帧,在寻找引起程序崩溃的调用序列的时候非常有用。 frame framenumber检查帧号为framenummber帧。这个操作并不会改变运行的上下文,但是可以允许不同帧里的变量。 down选择并打印出被调用的栈帧。 up选择并打印出调用了这个方法的栈帧。 info args显示当前栈帧的参数变量。 info locals显示当先帧栈的局部变量。
检查源文件 另一个查找当前程序的运行位置的方法或是查找其他的有用信息的方法就是检查相应的源文件。gdb提供了下面的几个命名:

list linenum 打印源文件中以linenum为中心的10行代码

list function   打印方法开始附近的10行代码。

list    打印下面10行代码。

    list命令会打印出源文件中以当前行为中心的代码行,如果在emacs中使用gdb的这些命令就太老土了。

 

检查数据

如果要回答“这些变量的值事什么?”,那么你应该使用下面的命令...

print expression    打印表达式的值,表达式是一个有效的c表达式,能够包括方法调用和算数表达式,以及当前栈帧的所有的变量。

set variable=expression    将变量的值设置成表达式所表示的值。你可以设置当前范围内的所有的变量的值。所有以$开头的变量都可以用作gdb的临时变量。

display expression    在程序运行到停止的时候打印表达式的值,在你一步步运行程序的时候,如果你想查看变量的值,这些会非常有用。

undisplay    取消之前的display命令请求。

 

    在gdb中,有两种方式来显示变量的值:显示一次当前变量的值或是持续显示变量整个生命周期的值。print命令将会打印当前变量的值,而display命令将会让调试器在程序运行的每一步都打印变量的值,只要变量还存在。目标值是用c语法来区分的,例如...

print x.y[3]

将会打印x结构体中名为y的数组的第四个变量值。那些可以访问的变量存在于当前方法的活动帧,还有全局变量和静态变量。print和display都可以用来打印任意的复杂表达式,甚至是那些包含有方法调用的表达式。但是如有一个方法有副作用,那么你可得注意了。

 

缩写

最后,我们可以更简单的去使用gdb,那就是所有的命令都可以用缩写,这样你就不必每次都打全命令了。一个命令的缩写就是在不引起歧义的情况下用尽量少的字母来替代命令,一些特殊的命令break,delete,run,continue,step,next,print,你在使用的时候就只需要用首字母就行了。另外,你最后输入的命令只要按一下return键就又可以出现了。这在单步调试的时候查看变量的值将会非常的有用。


更多的命令 editmode mode设置gdb的命令行模式。mode的值可以是emacs, vi, dumb. shell command 像在终端运行程序一样运行完剩下的程序。 history打印出使用过的命令历史。
调试策略 一些人因为他们不想去学习另外的工具而不去使用调试器,这是错误的!花上一些时间来学习使用一个调试器和他的特性,这将让你在排除困难的时候更有效率。
很多时候bug会导致程序的崩溃(或者是“内核崩溃”,“注册机崩溃”等等。),然后你的程序就挂在那,只留下如“片中断”之类的提示。如果你的程序也会这样崩溃,那么调试器就可已截获到程序的信号,这些信号包含着它所发现的错误,并且允许你检查程序的状态。这样你花不了多大的力气,调试器就能显示出程序在崩溃的时候的状态。
通常情况下,bug不会引起程序很明显的崩溃,而是产生一些内部出问题的征兆。在这种情况下,一种技术就是在程序运行不正常的地方设置一个断点,然后通过观察堆栈调用的情况来获得一些数据,然后控制程序到坏的状态里。另一种技术就是在问题开始之前设置一些断电,然后一步步接近问题,时刻检查程序运行的状态。
posted @ 2012-11-10 17:15  玉减香销  阅读(147)  评论(0编辑  收藏  举报