Unix系统开发-sdb的启动
首先来看看在哪些情况下需要对程序进行调试。
第一种情况(这是大多数用户都会碰到的),程序在运行过程中忽然跳了出来,屏幕上显示一个xxxx-core dumped消息,然后Shell提示符就又显示出来了,其中xxxx表示出错原因。这种情况的出现一般是系统核心认为进程的执行出现了异常,如进程试图去访问一块不允许它访问的存储区域(Memory Fault,Segmentation Fault);或者扫描某个无终止符的字符串(Bus Error);或者浮点运算溢出或被0除(Arithmetic Exception),等等。此时操作系统会把进程当时的内存映象写到当前目录下的一个名叫core的文件中。这种情况下我们可以使用sdb来检查此core文件,以决定出错的地点以及程序执行的状态,如函数间的调用关系、变量的值,等等。
第二种情况,程序可能并没有什么异常行为,但就是怎么也得不到正确的输出结果。这时需要在该进程运行过程中对之进行调试。这种情况下我们可以使用sdb逐条语句地跟踪程序的执行过程,并在执行过程中检查有关变量的值的变化情况。
上述两种情况并不是绝然分开的。实际上它们可以结合在一起使用。例如,当我们利用core文件对某个已终止的进程进行调试时,可以在sdb中重新启动相应程序的运行,然后对语句的执行进行一些控制。这样我们就能够知道在出现异常之前哪个程序到底是如何动作的。
为了使sdb能够很好地对程序进行调试,在编译程序时应指示编译程序和链接程序在目标代码中加入调试用的各种信息,如程序中的变量名、函数名及其在源程序中的行号等。我们知道,使用-g选项可以完成这一点。如我们可以用如下命令编译前一章给出的有毛病的程序代码:
$ cc -o myprog myprog.c myfunc.c
myprog.c:
myfunc.c:
$ ls -l myprog
-rwx-xr-x 1 yxz users 4224 Sep 1 10:17 myprog
$ cc -g -o myprog myprog.c myfunc.c
myprog.c
myfunc.c
$ ls -l myprog
total 26
-rwxr-xr-x 1 yxz users 5404 Sep 1 10:21 myprog
$
这时我们会发现,新生成的myprog比不带-g 选项生成的myprog要大的多。故在程序调试完成之后应将可执行程序中的调试用信息去掉。最简单的方法当然是使用不带-g 选项的cc命令重新编译一遍。另外UNIX系统提供了另外一个名为strip的工具,使用此命令也可以将程序中的调试信息去掉。
现在我们可以试着运行一下那个有问题的程序myprog。在shell提示符下输入:
$ myprog 1 111
Arithmetic Exception -core dumped
$
我们看到,程序由于异常而推出了,并且在当前目录下将生成一个名为core 的文件。这个文件有时非常庞大。在文件系统的维护中,有一条就是要定期找出各目录下的core 文件并将其删除掉。
发生此种情况时可以使用sdb来对之进行调试。输入:
$ sdb myprog
即可进入sdb调试程序。
sdb将接受三个参数:
待调试的可执行文件名;
待调试的core文件名,一般缺省是core;
由冒号分隔的一个目录表,sdb将在这些目录表中去查找有关的源文件。此目录表的缺省设置是当前目录
有时当前目录下的core文件可能并不是待调试的程序的core 文件,此时用这个core 文件进行调试就是不合适的了。为防止这一点,可在命令行中指定第二个参数为减号(-),如下所示:
$ sdb myprog -
这里的"-"告诉sdb忽略当前目录下的core文件。
第三种情况,我们试用对活动过程(正在运行的进程)进行调试的情况。例如,假定某个程序正在后台运行,但我们注意到该程序的某些部分执行起来非常慢,这时我们可以在不杀死这个进程的情况下对之进行调试:
$ sdb /proc/1111
这里1111为待调试进程的进程号,用户可以用PS命令得到。系统在/proc目录下用文件的形式保存了每一个活动进程的信息,而文件名正好就是相应的进程号。
指定的进程将在执行时遇到第一个系统调用或调用sdb后收到某个软中断信号时暂停其运行,我们就可以在sdb中检查变量的值、设置断点、恢复执行,等等。在退出sdb时,控制又返回程序,执行进程又从其原停止的地方继续执行。
第四种情况,一般情况下当被调试的活动进程在收到某个软中断信号时sdb会停止该进程。为了防止这一点,可以使用-s 选项。例如:
$ sdb -s 14 myprog
将告诉sdb不要因为软中断信号14(闹钟报警信号)而使进程的执行停止。此时该信号被传给相应进程。在程序接收并处理多个软中断信号的情况下,可以使用多个-s选项。
在sdb命令行中还有其他一些选项,对此我们不再一一列举,读者可以参考命令帮助。
在使用上述方法之一进入sdb之后,便可以进行在前一节中提到的各种操作,如显示或设置变量值、函数调用关系、控制语句的执行等。下一节我们将详细讨论完成这些操作的方法。
UNIX系统开发-sdb命令使用:程序执行控制
我们说程序调试的主要目的是观察变量的变化情况。但如果程序就一直不停地运行直至它终止或出错时才停下来的话,将没有机会去观察任何变量。因此,如何有效地控制程序中各语句的执行,使之在适当的时候暂停下来,待调试人员在显示或设置了某些存储单元的值之后再从停下的地方继续执行下去,是调试程序应具有的一项基本功能。在这一节中我们就来具体介绍如何控制程序的运行。
对程序执行控制的第一个问题是设置断点。一个断点实际上就是程序中某行语句。当程序执行到这条语句时控制会重复返回到sdb,由此提供给用户进行其他操作,如显示变量值的机会。
1.断点的设置和删除
在sdb中设置断点的方法比较多。但基本是b命令的变种。例如,我们可以使用如下语句在main()函数的第一个可执行行(非变量定义这类语句)设置一个断点:
* main:b
也可以直接用行号来设置断点。例如,在myprog.c中,第10行是main()函数中的第一个可执行语句,则使用如下命令也能达到同样的效果:
* 10b
注意这是在myprog.c为当前文件的情况下进行的。
如果直接输入:
* b
命令,则可将当前行设成是一个断点。但是若当前行不是一个可执行语句,那么sdb将把当前行之后的第一个可执行语句处设置一个断点。
在设置完断点之后,为了解程序中设置有哪些断点,可以使用B命令:
* B
0x80483f0 myprog.c:10 main+0x8
0x80483f7 myprog.c:11 main+0xf
0x8048407 myprog.c:12 main+0xlf
0x8048440 myprog.c:9 TestInput
0x8048447 myprog.c:10 TestInput+ 0x7
0x804482 myprog.c:13 TestInput+ 0x42
在设置完断点之后要将其删除,可以使用d命令。如:
* main:d
sdb将把在main()函数中设置的第一个可执行行上的断点删除。如果直接使用d命令,sdb将逐个列出所有断点并询问用户是否删除之。回答y断点将被删除。使用D命令则可删除程序中设置的所有断点。
2.sdb中启动程序的运行
在设置好所需的断点之后我们就可以重新启动程序的运行了。这可以使用r命令。如:
* r 111 2
BREAKPOINT process 554 function main() in myprog.c
10: for(i=1;i<argc:i++)
*
sdb将在main()函数中设置第一个断点处停下来并显示该行的语句。r后面给出的是传给可执行程序myprog的两个参数。因此上述命令同在shell提示符下输入:
$ myrprog 111 2
是相同的。不同的只是在sdb中程序的执行会在断点处停下来。
如果只输入
* r
命令,sdb使用最近一次执行调试程序时给它提供的参数来启动之。如果想不带任何参数来重新启动程序的运行,可使用
* R
命令。
3.控制程序的单步或者连续执行
在用r(R)命令启动程序的运行之后,sdb将在第一个断点处暂停程序的运行。此时断点行成为当前行,但并未被执行。此时我们可以在sdb的星号(*)提示符下输入前面已介绍过的或后面将要介绍的各种命令。当然最重要的是显示那些关键变量的值,以了解程序的运行情况。在这之后便可以继续程序的执行。
继续执行有两种方式。的一种是单步方式,即逐条语句执行。这可以使用S命令完成:
* s
STEPPED process 584 function main() in myprog.c
14: printf("The %dth value '%s' tis BAD!n",i,argv[i]);
*
sdb在执行完当前语句之后,将当前行后移一行并显示出其代码。对于用户自定义的函数调用,S命令并不将其当成是一条语句,此时它将指示sdb进入该函数(使之成为当前函数)。如我们可以接着上面输入下列命令:
* s
STEPPED process 584 function main() in myprog.c
11: if(TestInput(argv[i])== TESTOK)
* s
BREAKPOINT process 594 function main() in myprog.c
11: if(TestInput(argv[i])==TESTOK)
* s
BREAKPOINT process 584 function TestInput() in myfunc.c
9: {while (* ValueInput)}
* s
也能让sdb将用户自定义的函数调用,当成是一条普通语句而不进入此函数的定义。此时可以在那些包含有用户自定义函数调用的行,使用S命令以执行之。
s 和S命令都可以接收一个指明待执行的语句数目的参数。例如:
* s 2
使sdb执行当前行及其下那行语句,然后当前行之下的第2行将成为新的当前行。注意当使用s 或S时,如果sdb遇上一个未加-g选项编译的用户自定义函数,那么执行将继续直到一个带-g 选项编译的函数为止。
控制程序执行的第二种是使用c或C命令。一个最简单的c命令将使sdb从当前行把程序执行到下一个断点处。在c命令中还可以临时设置“断点”。例如:
* 8c
将使sdb在第8行上设置一个断点并使程序从当前行执行至该断点处后停下来,然后将此断点自动删除并等待用户输入其他命令。
在c命令中还可以指定在继续执行时后面第几个断点处停下来。例如:
* c 2
将使sdb从当前行开始执行,直到当前行之后的第2个断点为止。一般的:
* c
命令实际上相当于:
* c 1
C命令的作用及用法同c基本上是相同的。只不过此时sdb将进程收到的终止信号传给该进程进行处理。这对于调试有软中断处理的进程是很有用的。但sdb并不是将进程所收到的所有信号都传送给进程进行处理。要传递的信号可在sdb的命令行中用-s选项指定。
我们可以用g命令来告诉sdb在给定的行开始继续程序的执行:
* 6g
此命令使sdb在当前文件的第6行处恢复执行。而:
* 6g2
使sdb在当前文件的第6行恢复执行并跳过两个断点。但在使用g命令时必须小心。如果此命令跳过了那些必须得初始化语句,或者进程的执行被恢复到某个并不终止进程的函数中,此时都将会是程序的执行出现人为的差错。一般而言,除非能够确信所跳过的代码段是不正确的代码,不要轻易使用g命令。
4 关联命令
我们可以告诉sdb在使用到某种情况下暂停下来,完成某些操作之后继续执行。例如:
* 7b t; i/;c
将使得sdb在当前文件的第7行设置一个断点,然后每当遇到该断点时都显示函数调用栈(t)、变量i的值(i/),并继续程序的执行(c)。
上述命令的一种变化形式是a(Announce命令)。例如:
* FunctionName:a
将使得sdb在程序执行过程中每当函数FunctionName被调用时即显示其函数名和参数值。而命令:
* FunctionName:6a
将使得sdb在函数FunctionName中的第6行每次被执行时都将其显示出来。
5.函数调用
sdb能够调用程序中用户自定义的任何函数。这种功能对于下面两种情况可能比较有用:
在需要用各种不同的参数对函数进行测试。
用用户自己定义的一个函数来显示程序中的某些数据。
不论是何种情况,我们均可以用如下命令来进行函数调用:
* FunctionName(Arg1,Arg2,....)
或
* FunctionName(Arg1,Arg2,....)/m
在第一种用法下,sdb只是简单地执行指定的函数。在第二种用法下,sdb将在执行指定的函数之后显示返回值。此值一般将按十进制方式显示,除非指定了另外的显示格式。
提供给函数的参数可以是寄存器名、整数、浮点数、字符、字符串常量或是其他局部当前函数,或者是当前函数可以访问的变量。
6.变量的监视
sdb提供了一种被成为“观察点”(Watch)的机制。借此机制,我们可以监视变量值得变化或某些地址的内容的变化。例如:
* i $m
可用于设置对某个变量的观察点。此处变量i为被监视的变量。而用:
* 0x00400000:m
则可将地址0x00400000 处设置成观察点,而0x00400000将成为被监视的地址。
在设置了观察点之后,sdb会同处理S命令一样开始程序的单步执行,并在变量的值或指定地址单元的内容被改变时停止程序的执行。由于变量值的改变或地址内容的变化,或者其他什么原因而使程序的执行中止,包括因为遇到断点而使程序停止的情形,都将使得所设置的观察点被sdb自动删掉。
被观察的变量必须是当前函数的局部变量或者是当前函数所能够访问的变量
其他命令
除了上面介绍的五类主要命令之外,sdb还支持其他一些命令。使用这些命令,可以完成其他一些辅助性的工作。分别介绍如下。
. !Shell Command
此命令可以使sdb去执行指定的Shell Command。这里Shell Command是一个合法命令。例如可以输入:
* ! ls -l
sdb将执行指定的ls -l 命令,并将其输出显示出来。
使用:
* ! sh
能够生成一个新的Shell进程。在此新的Shell中可以完成某些工作,然后用exit命令返回到sdb中。
<FileName
指示sdb从文件FileName中读取sdb调试命令,并且逐个执行之。
"SomeString
指示sdb显示出某个字符串。常用此命令给出某些提示信息,
k
此命令将使当前对程序的调试终止,之后仍可用r命令重新启动被调试的程序。如:
* k
1111:killed
*
这里1111是被调试进程的进程号。
* q
此命令将使sdb停止其自身的运行而返回至原Shell提示符下。在调试完程序之后一般都要使用此命令。
UNIX系统开发-sdb命令使用:源程序的显示和搜索
sdb命令的使用
同我们前面介绍过的mail,ftp一类工具类似,sdb也是一个命令解释程序。也就是说,用户在sdb提示符(一个星号*)下输入sdb能够识别的命令,sdb将根据被调试的程序的具体情况给出响应。
例如,在运行myprog出错,生成core文件之后进入sdb时,sdb将给出如下的响应:
$ sdb myprog
12: return ((100/atoi(ValueInput))? TESTOK:! TESTOK);
*
sdb给出来的实际上是程序出错所在的函数,在源程序文件中的行号以及出错那一行的语句。
在sdb的使用中要注意三个“当前”概念:
(1)当前文件 即当前将要被执行的语句所在的那个源程序文件
(2)当前函数 即当前将要被执行的语句所在的那个函数
(3)当前行 这个概念只有在编译时加入-g选项才会有,它指的是将要被执行的那条语句。与当前行相应,有一个行号的概念。它指的是每条语句在程序中位于第几行。注意行号是从文件头开始计算的,第一行的行号为1,空白行和注释也包括在内。
在用core文件进行调试时,当前行和当前函数分别被设成是程序出错时所执行的那条语句所在地行和函数(如同上面显示出来的那样)。但如果在编译时未加-g选项,显示出来的将只有函数名和函数的地址了。
在对活动进程进行排错时,sdb将把当前函数和当前行分别设成是main()函数和main()函数的第一个可执行的语句行。
不论是哪种情况,sdb都将显示出*提示符。在此提示符之下我们可以输入各种sdb命令,以控制程序的执行或观察变量的变化情况,等等。在下面的几个小节中我们将分别详细讨论这些问题。
源程序的显示和搜索
程序出错一般来说不只是出错的那条语句本身造成的。事实上出现错误经常是前面或相关的代码执行了不正确的操作或少了某些必要的处理。因此调试过程中经常要观察一下源程序中的语句,或者在程序中搜索某个符号出现在什么地方。其中字符串的搜索功能同vi基本上是相同的,而文件的显示则同另外一个我们没有具体讨论的编辑器ed类似。下面我们将具体介绍这些命令。
1.源程序的显示
在用core进入sdb之后,在*提示符后输入w命令,该命令指示sdb显示源程序中的当前行为中心的前后10行的内容并保持当前行不变:
* w
7:int
8: TestInput(char * ValueInput)
9: {while ( * ValueInput)
10: if (! isdigit( * ValueInput)) return (! TESTOK);
11: else ValueInput++;
12: return ((100/atoi(ValueInput))? TESTOK:! TESTOK);
13: }
*
我们看到,在进入sdb时,当前行是第12行,以该行为中心的10行内容正好就是上面所显示出来的。其他可以显示源程序语句的sdb命令如下:
P 显示当前行
l 显示对应于当前指令的那条语句
Z 显示当前行开始的下面10条语句
Ctrl+D 显示当前行之后(不包括当前行)的第10条语句
n 显示第n条语句,这里n是一个数
注意这些命令显示出的是源程序语句还是汇编语句(后面我们将要介绍)取决于最近一次显示出的是什么。
2.改变当前行
在用户显示语句时,当前行也会相应地发生变化。例如,Z命令将使当前行向程序尾移动9行,而Ctrl+D则使当前行向后移动10行。
在使用数字来显示某行语句时将使该行语句成为当前行。而在*提示符之后按一下回车,当前行将下移一行。例如,接着上面的例子,输入:
* 8p
8: TEstInput(char * ValueInput)
* 回车
9: { while ( * ValueInput)}
*
这里8p实际上是两条命令的组合。它使当前行移至源文件的第八行,然后再显示出新的当前行。按回车键将使当前行后移一行。
3.改变当前源文件
在vi中我们可以用e命令对另外某个文件进行编辑。sdb也提供了e命令,可以用此命令来改变当前文件,如:
* e myprog.c
current file is now myprog.c
* 8p
8: main(int argc,char * argv[])
*
我们看到,当前文件改变之后,sdb将第一行设为是当前行。如果此文件的第一行是个函数,那么该函数便成为当前函数。否则将临时出现没有当前函数的情况。
在上一节中,我们介绍过在命令行中可以指定源文件搜索目录名列表(缺省情况为当前目录)。如果某个文件不在此搜索目录中,则可以用e命令将其加入:
* e Another SourceDir
这里Another SourceDir是一个目录名。如果要显示该目录下的某个文件,只需要输入:
* e FileName.c
当然直接使用:
* e Another SourceDir/FileName.c
也能达到同样的效果。
使用:
* e FunctionName
将使包含函数FunctionName的文件名成为当前文件,而当前函数不言而喻将成为FunctionName。当前行则理所当然的是该函数的第一行。同一程序中函数名在各模块中的唯一性保证了这一点是能够成功的,但如果包含指定函数的文件不在当前搜索目录列表中,则必须用e命令将其加入。
4.字符串的搜索
在vi中,我们可以在命令方式下使用“/“或者“?”命令,从当前位置向后或者向前搜索某个字符串,在sdb中也同样可以完成这一点。使用这两个命令我们可以查找源程序中某个或某类符号的出现。之所以说某类,是因为我们可以用正规表达式来指定待搜索的串(也即在搜索串中可以使用*,?,[,],-,^这类特殊字符)。
例如,为了查找myprog.c中argv出现在那些行上,可输入:
* /argv/
8: main(ini argc,char * argv[])
sdb将从当前行开始向文件尾搜索,到达文件尾之后又从文件头开始直至搜索到某个匹配的串或到达当前行为止。
与/相反,?命令将从当前行向文件头方向搜索,因此如果我们将上述/argv/换成:
* ? argv?
14: printf("The %dth value' %s'tis BAD! n",i,argv[i]);
*
所得的结果一般是不同的。
/或?命令之后的/或?并不是必须的。另外如果要在同一方向上继续搜索上次搜索过的串,只需要直接输入/或者?即可。
下一节:函数调用层次显示........
标签: