前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Linux环境下通过GDB调试C项目实战

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

作者头像
glm233
发布2020-09-28 11:42:05
5.2K0
发布2020-09-28 11:42:05
举报
通过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

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

tZOCxH.png
tZOCxH.png

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

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

输入vi Makefile,我们看到:

tewbLt.png
tewbLt.png

这个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 查看代码:

tmNfSA.png
tmNfSA.png

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

tmaMD0.png
tmaMD0.png
tmaKuq.png
tmaKuq.png

我们可以看到,函数入口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文件夹下的预定义函数:

tm6rI1.png
tm6rI1.png

没啥问题,ok,下一个

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

tmDlh8.png
tmDlh8.png

查看完毕,果真和之前初步调试的一样,在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具体实现代码,就会发现其中函数调用时出现的数组越界,这样就会导致缓冲区泄露,可能会修改内存,造成不可知的错误,这样是最可怕的,因为无法准确预料到,后续会产生难以估计的错误**

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2020-05-29 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 通过GDB调试找到程序的bug
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档