1.自定义输出
想必大家都有利用输出函数如printf来帮助我们调试程序的经历,这是一种比较原始的程序调试辅助方法,在Linux下也可以为我们所用。不过这种方法有一个明显的缺点,就是在调试完后我们必须注释或删除掉这些辅助代码。Linux C提供了-DDEBUG这个编译标记来定义DEBUG这个符号,借助于该符号,我们可以在应用程序中添加额外代码并根据需要决定执行与否。
如:
#include<stdio.h>
//*******dtest.c*******
int main()
{
#ifdef DEBUG
printf("Debug output....../n");
#endif
printf("Main function ended!/n";
}
运行:
$ cc -o dtest dtest.c
$ ./dtest
Main function ended!
$ rm dtest
$ cc -o dtest -DDEBUG dtest.c
$ ./dtest
Debug output......
Main function ended!
通过以上示例,你应该明白了-DDEBUG标记的用法了吧,呵呵~~~也许你会想,如果我有好几段调试代码,而我希望根据需要每次选择相应的一段来运行,这是否能实现呢?不怕做不到,就怕想不到。事实上我们可以方便的实现这种构想,如下:
#define BASIC_DEBUG 1
#define EXTRA_DEBUG 2
#define SUPER_DEBUG 4
//code 1
# if (DEBUG & BASIC_DEBUG )
printf。。。
#endif
//code 2
# if (DEBUG & EXTRA_DEBUG )
printf。。。
#endif
//code 3
# if (DEBUG & SUPER_DEBUG )
printf。。。
#endif
上述代码中我们自定义了几个宏变量,通过"&“来决定代码段的执行与否。我们知道”&“为按位取余运算符,所以当DEBUG的值为1时,只有code 1会执行,当DEBUG的值为2时,只有code 2会执行,而当DEBUG的值为3时,code 1和code 2都会执行,依次类推。。。可是我们如何根据需要设置DEBUG的值呢?这还得看-DDEBUG标记,在命令提示符下直接给-DDEBUG赋值就OK了,如cc -o dtest
-DDEBUG=5 dtest.c
2 调试工具(gdb)
前面介绍的调试方法只是一种帮助我们调试的辅助方法,作为专业的程序员,我们应该熟练掌握功能更加全面的调试工具。下面就详细介绍在Linux下比较常用的gdb工具,它是GNU所提供的。
示例程序:
#include<stdio.h>
//*******ArraySort.c*******
/* 1 */ typedef struct {
/* 2 */ char *data;
/* 3 */ int key;
/* 4 */ } item;
/* 5 */
/* 6 */ item array[] = {
/* 7 */ {“bill”, 3},
/* 8 */ {“neil”, 4},
/* 9 */ {“john”, 2},
/* 10 */ {“rick”, 5},
/* 11 */ {“alex”, 1},
/* 12 */ };
/* 13 */
/* 14 */ sort(a,n)
/* 15 */ item *a;
/* 16 */ {
/* 17 */ int i = 0, j = 0;
/* 18 */ int s = 1;
/* 19 */
/* 20 */ for(; i < n && s != 0; i++) {
/* 21 */ s = 0;
/* 22 */ for(j = 0; j < n; j++) {
/* 23 */ if(a[j].key > a[j+1].key) {
/* 24 */ item t = a[j];
/* 25 */ a[j] = a[j+1];
/* 26 */ a[j+1] = t;
/* 27 */ s++;
/* 28 */ }
/* 29 */ }
/* 30 */ n--;
/* 31 */ }
/* 32 */ }
/* 33 */
/* 34 */ main()
/* 35 */ {
/* 36 */ int i;
/* 37 */ sort(array,5);
/* 38 */ for(i = 0; i < 5; i++)
/* 39 */ printf(“array[%d] = {%s, %d}/n”,
/* 40 */ i, array[i].data, array[i].key);
/* 41 */ }
下面我将借助于该程序来逐步讲解gdb的使用方法。
a. 进入调试模式
$ cc -g -o ASort ArraySort.c
$ gdb ASort
输入以上命令后就进入调试模式了,此时命令提示符变为”(gdb)",可以输入help命令查看相应的命令。有一点需要说明的是,-g参数可以帮助输出调试信息。
(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)
除了上述命令,我还想补充的是kill命令可以终止正在调试的程序,而quit命令将终止gdb的使用。
b. 运行程序
在调试模式下通过run命令来运行程序,如果程序需要参数,你可以像在普通模式下运行程序那样在run命令后添加参数。在本程序中,直接输入run命令即可:
(gdb) run
结果:
Starting program: /home/ubuntu/ASort
Program received signal SIGSEGV, Segmentation fault.
0x080484ae in sort (a=0x804a040, n=5) at ArraySort.c:28
28 /* 23 */ if(a[j].key > a[j+1].key) {
(gdb)
程序因出现错误而终止执行,我们可以用backtrace(可简写为bt)或where命令来查看退栈里的情况,如:
(gdb) bt
#0 0x080484ae in sort (a=0x804a040, n=5) at ArraySort.c:28
#1 0x0804863a in main () at ArraySort.c:47
c. 检查变量
当程序出现问题的时候我们会想到去查看变量的值进而推断问题所在,在调试模式下print命令可以帮助我们输出变量或者表达式的值。
前面讲到程序在if(a[j].key > a[j+1].key) 处终止执行,于是我们想到查看变量j的当前数值:
(gdb) print j
$1 = 4
gdb把输出变量保存在伪变量中,这里j 的值为4且保存在变量$1中。从结果我们可以看出,程序是在尝试执行if(a[4].key > a[4+1].key)的时候终止执行的,由此我们不难看出,我们定义的数组的长度为5,即数组下标为0-4,而此处用到了下标为5的数组元素,显然越界。至此我们已经明白了问题所在,一个解决方法为将第二个for循环改为:for(j = 0; j < n-1; j++) {
再此,我还要探讨一下print的用法,利用print命令我们可以查看变量,数组或者指针元素的值,再举一个例子:
(gdb) print a[j]
$2 = {data="rick",'/0'<repats 4091 times>, key=5}
(gdb) print $
$3 = {data="rick",'/0'<repats 4091 times>, key=5}
变量$通常保存着上一个伪变量的值,而$$则保存着当前倒数第二个伪变量的值。如果希望输出数组中连续几个元素的值,可用@<number>命令,如:
(gdb) print array[0]@5
此外,我们可以利用list命令将当前位置的程序输出,再次输入list命令将继续输出后续部分。
d. 设置断点
我们可以通过设置断点来暂停程序的运行,下面我们先来看看gdb中与设置断点相关的命令
(gdb) help breakpoint
Making program stop at certain points.
List of commands:
awatch — Set a watchpoint for an expression
break — Set breakpoint at specified line or function
catch — Set catchpoints to catch events
clear — Clear breakpoint at specified line or function
commands — Set commands to be executed when a breakpoint is hit
condition — Specify breakpoint number N to break only if COND is true
delete — Delete some breakpoints or auto-display expressions
disable — Disable some breakpoints
enable — Enable some breakpoints
hbreak — Set a hardware assisted breakpoint
ignore — Set ignore-count of breakpoint number N to COUNT
rbreak — Set a breakpoint for all functions matching REGEXP
rwatch — Set a read watchpoint for an expression
tbreak — Set a temporary breakpoint
tcatch — Set temporary catchpoints to catch events
thbreak — Set a temporary hardware assisted breakpoint
watch — Set a watchpoint for an expression
Type “help” followed by command name for full documentation.
Command name abbreviations are allowed if unambiguous.
可用break linenumber命令给指定行设置断点,continue(cont)命令将继续执行断点后面的程序。前面提到可以用print array@<number>命令输出数组中连续几个元素的值,事实上我们还可以利用display array@<number>命令在程序每次运行到该断点的时候自动输出数组的内容,此外,我们还可以通过commands命令让程序在执行到断点的时候自动输出数组的内容但不暂停程序的执行,如:
(gdb) display array[0]@5
(gdb) commands
>cont
>end
设置了断点就需要清楚断点,gdb给我们提供了disable命令和delete命令来禁止或者清除断点,我们可以先通过infor命令查看程序已定义的断点,如:info break(display命令类似),gdb就会列出已定义的断点及其序号,然后就可以利用该序号执行disable命令或delete命令,如disable 1将会禁止第一个断点的执行
前面的实例程序还有一个问题,就是第30行“n--”这句,实际上该行是不需要的,它的存在反而导致比较次数不够而无法得到正确的结果,一种简单的解决方法为将该行直接删除,此处我们介绍利用commands命令来修改变量的值,如下:
<gdb> commands 2
> set variable n=n+1
>cont
>end
(gdb) run