学科分类
目录
Linux编程

GDB调试

本小节将介绍Linux系统中使用的一种非常强大的调试工具——gdb。gdb可以逐条执行程序、操控程序的运行,并且随时可以查看程序中所有的内部状态,如各变量的值、传给函数的参数、当前执行的语句位置等,藉此判断代码中的逻辑错误。掌握了gdb的使用方法,Linux用户将能使用更多灵活的方式去调试程序。

下面将结合几个初学者易犯的错误实例,来讲解如何使用gdb调试程序。

案例37:本案例的代码实现一个针对数组的排序程序,其中包含:初始化数组、数组排序和数组打印这三部分功能。

 1    #include <stdio.h>
 2    #include <stdlib.h>
 3    #include <time.h>
 4    #define N 5                                    //定义数组长度
 5    void init_arr(int *arr, int len)            //生成随机数数组
 6    {
 7        int i = 0; 
 8        for (i = 0; i < len; i++) {
 9            arr[i] = rand() % 20 + 1;
 10        }
 11    }
 12    void select_sort(int *arr, int len)        //选择排序算法
 13    {
 14        int i, j, k, tmp; 
 15        for (i = 0; i< len-1; i++) {
 16            k = j;
 17            for (j = i+1; j < len; j++) {
 18                if (arr[k] > arr[j]) 
 19                    k = j;
 20            }
 21            if (i != k) {
 22                tmp = arr[i];
 23                arr[i] = arr[k];
 24                arr[k] = tmp;
 25            }
 26        }
 27    }
 28    void print_arr(int *arr, int len)            //打印数组
 29    {
 30        int i; 
 31        for (i = 0; i < len; i++)
 32            printf("arr[%d] = %d\n", i, arr[i]);
 33    }
 34    int main(void)
 35    {
 36        int arr[N];
 37        srand(time(NULL));                        //生成随机数种子
 38        init_arr(arr, N);                        //生成数组
 39        print_arr(arr, N);                        //打印原始数组
 40        select_sort(arr, N);                    //数组排序
 41        printf("----------- after sort ------------\n");
 42        print_arr(arr, N);                        //打印排序后的数组
 43        return 87;
 44    }

执行此段程序,结果如下:

arr[0] = 10
arr[1] = 19
arr[2] = 20
arr[3] = 20
arr[4] = 12
----------- after sort ------------
arr[0] = 10
arr[1] = 12
arr[2] = 19
arr[3] = 20
arr[4] = 20

程序顺利执行,但按照预期,打印结果“after sort”所在行之后的部分,应为一个有序序列,输出结果显然并非如此,这说明程序的逻辑出现了错误。此时我们可以启用gdb调试工具,在代码中设置端点,逐步执行程序,再根据程序中变量值的变化,判断错误原因。

在启用gdb调试工具之前,首先需要在待调试的程序代码中加入调试信息。实现此操作的方法如下:

[itheima@localhost ~]$ gcc gdbtest.c –o app –g

即在gcc编译的基础上,添加选项“-g”,此时将会生成一个带有调试信息的可执行文件app。输出直接编译产生的文件gdbtset和带有调试信息的可执行文件app的详细信息,会发现文件app要比gdbtest大,多出的内容将用于程序调试。

之后便可使用gdb调试此段程序,使用的命令如下:

(gdb) gdb app

执行该命令之后,系统会输出gdb的版本号及其它相关信息,此时的命令提示由“[itheima@localhost ~]$”变为“(gdb)”。

与C语言等的调试步骤相同,在调试之前,需要先在代码中设置断点,因此应先列出程序代码。列出程序代码的命令如下:

list 行号

该命令表示,从指定行开始,列出所有代码。使用此命令后,程序中的前10行代码将被显示在屏幕上,此时可以使用“l”继续列出代码。

根据之前程序输出的结果,可以粗略判断出有错的代码应在排序函数中,因此可以在排序函数中设置断点。设置断点的命令如下:

b 行号

该命令表示在对应行设置一个断点。

假设想要查看代码中已经设置的断点,可以使用“info”命令,该命令的格式如下:

info b

执行此命令后,对应代码中设置的断点信息将会显示在屏幕上。此时程序中已设置断点的信息如下:

Num   Type      Disp Enb Address      What
1    breakpoint   keep y  0x000000000040064f in select_sort 
​                          at gdbtest.c:20

该信息中主要包括:断点编号Num、断点状态Enb、断点地址Address以及断点在程序中所处的位置。

在设置断点时还可以指定条件,例如若想在i=5时设置断点,可以使用以下命令:

b 22 if i = 5

该命令表示当“i=5”时,在代码第22行设置一个断点,此时使用“info b”命令查看断点信息,显示结果如下:

Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x000000000040064f in select_sort 
                                                   at gdbtest.c:20
2       breakpoint     keep y   0x0000000000400661 in select_sort 
                                                   at gdbtest.c:22
    stop only if i = 5

此时在第二个断点的相关信息之后,显示“stop only if i = 5”,表示当程序执行到i=5时,断点才会生效。

在断点的信息中,有一项为“Enb”,当此项显示为“y”时,表示断点生效。此项可通过命令“disable”设置为“n”,表示断点无效,其使用格式如下:

disable Num

其中的参数“Num”表示断点的编号。若要将断点的“Enb”状态重新修改为“y”,可以使用命令“enable”。

若在调试的过程中,发现设置的某些断点意义不大,可以将断点删除。删除断点的命令为“delete”,其使用格式如下:

delete Num

断点设置好之后,便可以再次运行程序,查看调试信息了。在gdb中运行程序的命令为“run”,输入此命令,程序将开始执行。

在遇到断点时,程序会停止,此时可以使用命令“p”查看当前状态下代码中变量的值,该命令的使用方法如下:

p 变量名

若希望程序继续向下执行,可以使用命令“s”,s即“step”,表示单步执行。使用命令s则会进入C函数内部,因C函数作为标准函数库,基本都不会出现错误,此时可以使用命令“n”跳过库函数检查。另外使用命令“finish”,也可以跳出当前函数,继续往下执行。

使用命令p时,变量的值仅会输出一次,若想在执行的过程中跟踪某个变量的值,使用这种方法显然比较麻烦。gdb中还提供了另外一个命令“display”,该命令的用法与p相同,但是程序每往下执行一句,需要跟踪的变量的值就会被输出一次。使用命令“undisplay”可以取消跟踪。

分析程序,发现在select_sort函数中需要跟踪的变量只有3个,即:i、j、k。使用display命令跟踪这三个变量。发现在程序执行的过程中,变量k的值一直为1,而正常情况下k应保存外层循环i的值,因此可以判断k的赋值应该有问题。观察代码,发现代码19行应为“k=i”。

若想结束调试,可以使用“continue”结束当前断点调试,再使用“quit”命令退出调试,回到命令窗口。

虽然gdb调试工具很强大,但其工作原理仍遵循“分析现象->假设错误原因->产生新现象->验证假设”这一基本思想。透过现象深入分析错误原因,针对假设的原因设计验证方法等,都需要严密的分析和思考,因此切不可过于依赖工具,而忽视了严谨思维的重要性。

点击此处
隐藏目录