C语言入门(21)——使用DBG对C语言进行调试

C语言入门(21)——使用DBG对C语言进行调试

 

程序中除了一目了然的Bug之外都需要一定的调试手段来分析到底错在哪。到目前为止我们的调试手段只有一种:根据程序执行时的出错现象假设错误原因,然后在代码中适当的位置插入printf,执行程序并分析打印结果,如果结果和预期的一样,就基本上证明了自己假设的错误原因,就可以动手修正Bug了,如果结果和预期的不一样,就根据结果做进一步的假设和分析。
我们介绍一种非常强大的调试工具gdb,可以完全操控程序的运行,并且随时可以查看程序中所有的内部状态,比如各变量的值、传给函数的参数、当前执行的语句位置等。调试的基本思想仍然“分析现象->假设错误原因->产生新的现象去验证假设”这样一个循环,根据现象如何假设错误原因,以及如何设计新的现象去验证假设,这都需要非常严密的分析和思考,如果因为手里有了强大的工具就滥用,而忽视了严谨的思维,往往会治标不治本地修正Bug,导致一个错误现象消失了但Bug仍然存在,甚至是把程序越改越错。

单步执行和跟踪函数调用

看下面的程序:

#include <stdio.h>

int add_range(int low, int high)
{
	int i, sum = 0;
	for (i = low; i <= high; i++)
		sum = sum + i;
	return sum;
}

int main(valoid)
{
	int result[100];
	result[0] = add_range(1, 10);
	result[1] = add_range(1, 100);
	printf("result[0]=%d\nresult[1]=%d\n", result[0], result[1]);
	return 0;
}


 

add_range函数从low加到high,在main函数中首先从1加到10,把结果保存下来,然后从1加到100,再把结果保存下来,最后打印出的两个结果是:

 

结果显然不正确,在小学我们就学了高斯小时候的故事,从1加到100应该是5050。下面通过gbd把错误找出来。

 

在编译时要加上-g选项,生成的目标文件才能用gdb进行调试:

(gdb) -g选项的作用是在目标文件中加入源代码的信息,比如目标文件中第几条机器指令对应源代码的第几行,但并不是把整个源文件嵌入到目标文件中,所以在调试时目标文件时必须保证gdb也能找到源文件。gdb提供一个类似shell的命令行环境,上面的(gdb)就是提示符,在这个提示符下输入help可以查看命令的类别:

 

可以进一步查看某一类别中有哪些命令,例如查看files类别下有哪些命令可以用:

  

现在试试用list命令从第一行开始列出源代码:

 

一次只列10行,如果要从11行开始继续列源代码可以输入

(gdb) list

 

也可以什么都不输直接敲回车,gdb提供了一个很方便的功能,在提示符下直接敲回车表示用适当的参数重复上一条命令。

 

gdb的很多常用命令有简写形式,例如list命令可以写成l,要列一个函数的源代码也可以用函数名做参数:

 

 

gcc的-g选项并不是把源代码嵌入到目标文件中的,在调试目标文件时也需要源文件。我们继续调试。首先用start命令开始执行程序:

 

 

这表示停在main函数中变量定义之后的第一条语句处等待我们发命令,gdb列出这条语句表示它还没执行,并且马上要执行。我们可以用next命令(简写为n)控制这些语句一条一条地执行:

 

用n命令依次执行两行赋值语句和一行打印语句,在执行打印语句时结果立刻打出来了,然后停在return语句之前等待我们发命令。虽然我们完全控制了程序的执行,但仍然看不出哪里错了,因为错误不在main函数而在add_range函数,现在用start命令重新来过,这次用step命令(简写为s)进入函数中去执行:


这次停在了函数中变量定义之后的第一条语句处。在函数中有几种查看状态的办法,backtrace命令(简写为bt)可以查看函数调用的栈帧:

 

可见当前的add_range函数是被main函数调用的,main传进来的参数是low=1,high=10。main函数的栈帧编号为1,add_range的栈帧编号为0。现在可以用info命令(简写为i)查看add_range局部变量的值:

 

 

用s或n往下走几步,然后用print命令(简写为p)打出变量sum的值:

 

根据上面两步的结果我们可以看到,变量sum没有做初始化,所以直接导致后面的运算结果错误。

 

这时候我们已经找到错误原因,可以退出gdb修改源代码了。

 

代码修改如下:

#include <stdio.h>
 
int add_range(int low, int high)
{
         inti, sum = 0;
         for(i = low; i <= high; i++)
                   sum= sum + i;
         returnsum;
}
 
int main(void)
{
         intresult[100];
         result[0]= add_range(1, 10);
         result[1]= add_range(1, 100);
         printf("result[0]=%d\nresult[1]=%d\n",result[0], result[1]);
         return0;
}


 

修改完毕重新编译并运行:

发现结果正确了。

下面是常用的gdb指令:

 

backtrace(或bt) 查看各级函数调用及参数

finish 执行到当前函数返回,然后停下来等待命令

frame(或f) 帧编号 选择栈帧

info(或i) locals 查看当前栈帧局部变量的值

list(或l) 列出源代码,接着上次的位置往下列,每次列10行

list 行号 列出从第几行开始的源代码

list 函数名 列出某个函数的源代码

next(或n) 执行下一行语句

print(或p) 打印表达式的值,通过表达式可以修改变量的值或者调用函数

set var 修改变量的值

start 开始执行程序,停在main函数第一行语句前面等待命令

step(或s) 执行下一行语句,如果有函数调用则进入到函数中

 

posted on 2014-06-30 12:30  三少爷的剑123  阅读(241)  评论(0编辑  收藏  举报

导航