Linux环境下通过GDB调试C项目实战

2020-09-28 11:42:05 浏览数 (1)

通过GDB调试找到程序的bug

请查看位于https://github.com/xmu-Linux101/Linux101/tree/201720182/experiments/gcc-5-gdb的代码

这个是向量加法的程序,但是有一些小bug,请通过GDB调式工具找出具体的bugs。

调式过程请尽量使用截图工具保留下来,便于评判。

提交PDF实验报告。

前情回顾

编译过程可分为四个阶段:

  1. 预处理(Pre-Processing)
  2. 编译(Compiling)
  3. 汇编(Assembling)
  4. 链接(Linking)

调试选项

gcc-g

默认情况下,gcc在编译时不会建个调试符号插入到生成的二进制代码中,如果需要生成调试符号信息,可以使用gcc -g选项,一般不加调试选项,否则会使代码增大。

gdb 调试器的功能

​ 1.设置断点 ​ 2.单步执行程序,便于调试 ​ 3.查看程序中变量值的变化 ​ 4.动态改变程序的执行环境 ​ 5.分析崩溃程序产生的core文件

以上这些就是这次实验的前置知识,需要我们采用gdb调试器来找出一些程序的bug

首先看一下这个程序的目录结构:

我们可以看到文件的目录结构是一个典型的C语言项目架构:Makefile,include文件夹下是预先定义好的库函数,粗看文件结构应该可以想到array.c是一个具体实现函数功能的文件,main.c则是总的主函数,进行测试编写的代码功能是否正常执行

在找这个项目的bug之前我们必须确认一下Makefile的内容是否有逻辑错误或者语法错误,这样才能保证我们后期的调试没有问题

输入vi Makefile,我们看到:

这个Makefile中的几条命令大致为:

make clean:清除已经存在的result可执行文件

make/make result:将已经得到的可执行文件main.o与array.o链接成可执行文件result,不开启O2优化或采用O0优化,在此之前将main.c和array.c分别编译成可执行文件main.o和array.o

make_clean:清除已经存在的main.o可执行文件

array_clean:清除已经存在的main.o可执行文件

array:清除已经存在的array.o可执行文件并编译array.c生成array.o文件

main:清除已经存在的main.o可执行文件并编译mian.c生成main.o文件

main_optimize:编译mian.c生成main.o文件,开启O2优化(该优化选项会牺牲部分编译速度,除了执行-O1所执行的所有优化之外,还会采用几乎所有的目标配置支持的优化算法,用以提高目标代码的运行速度.)

array_optimize:编译mian.c生成main.o文件,开启O2优化(该优化选项会牺牲部分编译速度,除了执行-O1所执行的所有优化之外,还会采用几乎所有的目标配置支持的优化算法,用以提高目标代码的运行速度.)

make diff:观察生成的不加优化的版本代码和加入O2优化的release版本代码执行结果,查看区别

观察可得:Makefile并未存在任何语法错误、以及逻辑上的错误,初步排查断定是在代码实现上出了问题

在确定了Makefile没有大问题之后,我们采用gdb调试器来调试,首先gdb ./main启动调试器,list 查看代码:

我们观察可以得到,这是一个实现了创建两个一维向量(长度都为16)、并且将他们相加,最后输出相加结果的程序,更进一步,我们在第11行设置断点,display i和array_a[i]的信息:

我们可以看到,函数入口array_fill_with(int *array, int length, int fillWith)是有这三个参数,但是在实现代码中,length是其定义的数组长度,但是在循环中for(int i=0;i<=length;i )竟然写成了<=length,这样就会导致执行到array[length]=fillWith这条语句,那这是什么意思呢,在C语言中,定义一个数组,array[length],那么我们可以使用的有效元素范围就只有0~length-1,但是在这里的话就属于很严重的数组越界,也就是我们这里常说的未定义行为,但是到这里,我们还不能完全确定是否程序中就只有这个错误,我们还需要检查所有其他的代码才能确定:

查看include文件夹下的预定义函数:

没啥问题,ok,下一个

主要的array.c,启动gdb调试器:

查看完毕,果真和之前初步调试的一样,在array_add和array_fill两个函数里面都涉及到段错误,数组越界,length被取等号,但为什么没有发生报错或者错误终止程序是因为在最后的print函数里面只涉及到了正常的0~array.lenth-1的范围,当然如果将print函数里面也改成length取等号的话,很有可能最后一个元素(即第17个的值会不太一样)

其实,写出这样程序会造成十分严重的错误,但这种错误又非常隐蔽,难以发现以及调试。这里有个简单的例子:

代码语言:javascript复制
 #include <stdio.h>
    int main()
    {
        int i, a[10];
        for(i = 1; i <= 10;   i)
            a[i] = 0;
        return 0;
    }

你看到这个程序,真的会输出是11个0吗,但其实它运行起来是死循环,这就是C语言中数组越界带来的巨大隐患:

数组中的下标从0开始。 那么在上面代码中只能访问:a[1]、a[2]、a[3]、a[4]、a[5]、a[6]、a[7]、a[8]、a[9]

i自加到10时,a[10]属于数组下标越界,在C语言立,它会这样处理,对越界空间进行操作,破坏原有数据。访问之后程序会破坏内存原有数据,导致缓冲区泄露,并且发生不可预知的错误(在这里则是将i的内存地址和a[10]绑定起来,相当于每次修改a[10]的时候就顺便将i置为0,这样就会导致死循环)

总结来说:这个项目运行起来没有问题,看起来让人放心,但是,仔细去调试它的array.c具体实现代码,就会发现其中函数调用时出现的数组越界,这样就会导致缓冲区泄露,可能会修改内存,造成不可知的错误,这样是最可怕的,因为无法准确预料到,后续会产生难以估计的错误

让人放心,但是,仔细去调试它的array.c具体实现代码,就会发现其中函数调用时出现的数组越界,这样就会导致缓冲区泄露,可能会修改内存,造成不可知的错误,这样是最可怕的,因为无法准确预料到,后续会产生难以估计的错误**

0 人点赞