zoukankan      html  css  js  c++  java
  • gdb

    第十章 gdb

    程序中除了一目了然的Bug之外都需要一定的调试手段来分析到底错在哪。到目前为止我们的调试手段只有一种:根据程序执行时的出错现象假设错误原因,然后在代码中适当的为止插入printf,执行程序并分析打印结果,如果结果和预期的一样,就基本上证明了自己假设的错误原因,就可以动手修正Bug了,如果结果和预期的不一样,就根据结果做进一步的假设和分析。调试工具gdb的基本思想仍然是“分析现象->假设错误原因->产生新的现象去验证假设”。

    1.单步执行和跟踪函数调用

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

     

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

     

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

     

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

     

    也可以什么都不输直接敲回车,gdb提供了一个很方便的功能,在提示符下直接敲回车表示用适当的参数重复上一条命令。gdb的很多常用命令有简写形式,例如list命令可以写成l,要列一个函数的源代码也可以用函数名做参数:

     

    现在退出gdb的环境:

     

    现在把源代码改名或移到别处再用gdb调试,就列不出源代码了:

    可见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局部变量的值:

     

    如果想查看main函数当前局部变量的值也可以做到,先用frame命令(简写为f)选择1号栈帧后再查看局部变量:

     

    注意到result数组中有很多元素具有杂乱无章的值,我们知道,未经初始化的局部变量具有不确定的值。到目前为止一切正常。用s或n往下走几步,然后用print命令(简写为p)打出变量sum的值:

     

    第一次循环i是1,第二次循环i是2,加起来是3,没错。这里的$1表示gdb保存着这些中间结果,$后面的编号会自动增长,在命令中可以用$1、$2、$3等编号代替相应的值。由于我们本来就知道第一次调用的结果是正确的,再往下跟也没意义了,可以用finish命令让程序一直运行到从当前函数返回为止:

     

    返回值是55,当前正准备执行赋值操作,用s命令赋值,然后查看result数组:

     

    第一个值55确实赋给了result数组的第0个元素。下面用s命令进入第二次add_range掉用,进入之后首先查看参数和局部变量:

     

    由于局部变量i和sum没初始化,所以具有不确定的值,又由于两次调用是挨着的,i和sum正好取了上次调用时的值。i的初值不是0倒没关系,在for循环中会赋值为0的,但sum如果初值不是0,累加得到的结果就错了。好了,我们已经找到错误原因,可以退出gdb修改源代码了。如果我们不想浪费这一次调试机会,可以在gdb中马上把sum的初值改为0继续运行,看看这一处改了之后还有没有别的Bug:

     

    这样结果就对了。修改变量的值除了用set命令之外也可以用print命令,因为print命令后面跟的是表达式,而我们知道赋值和函数调用也都是表达式,所以还可以用print来修改变量的值,或者调用函数:

     

    printf的返回值表示实际打印的字符数,所以$6的结果是13。

    前面用到的gdb的基本命令:

     

    2.断点

    断点调试实例:

     

    这个程序的作用是:首先从键盘读入一串数字存到字符数组input中,然后转换成整型存到sum中,然后打印出来,一直这样循环下去。scanf(“%s”, input);这个调用功能是等待用户输入一个字符串并回车,scanf把其中第一段非空白(非空格、Tab、换行)的字符串放到input数组中,并自动在末尾添加‘’。接下来的循环从左到右扫描字符串并把每个数字累加到结果中,例如输入是“2345”,则循环累加的过程是(((0*10+2)*10+3)*10+4)*10+5=2345。注意字符型的‘2’要减去‘0’的ASCII码才能转换成整数值的2,‘0’的ASCII码是48,而‘’的ASCII码是0,二者是不相同的。下面运行程序看问题:

     

    又是这种现象,第一次是对的,第二次不对。而这个程序我们赋了初值,下面调试:

     

    可见,如果变量要赋初值,start不会跳过变量定义语句。

    用display命令使得每次停下来的时候都显示当前sum值,然后继续往下走:

     

    用undisplay可以取消对先前设置的那些变量的跟踪。这个循环应该是没有问题的,因为第一次的结果正确。如果不想一步一步走这个循环,可以用break命令(简写为b)在第9行设一个断点(Breakpoint):

     

    break命令的参数也可以是函数名,表示在某一个函数开头设断点。现在用continue命令(简写为c)连续运行而非单步运行,程序到达断点会自动停下来,这样就可以停在下一次循环的开头:

     

    然后输入新的字符串准备转换:

     

    问题暴露出来了,新的转换应该再次从0开始累加,而sum现在已经是123了,原因在于新的循环没有把sum归零。可见断点有助于快速跳过与问题无关的代码,然后在有问题的代码上慢慢走慢慢分析,“断点加单步”是使用调试器的基本方法。至于应该在哪里设置断点,怎么知道哪些代码可以跳过而哪些代码要慢慢走,也要通过对错误现象的分析和假设来确定,就像以前分析确定在哪里插入printf语句一样。一次调试可以设置多个断点,用info命令可以查看已经设置的断点:

     

    每个断点都有一个编号,可以用编号指定删除某个断点:

     

    有时候一个断点暂时不想用可以禁用掉而不必删除,这样以后想用的时候可以直接启用,而不必重新从代码里找应该在哪一行设断点:

     

    gdb的断点功能非常灵活,还可以设置断点在满足某个条件时才激活,例如我们仍然在循环开头设置断点,但是仅当sum不等于0时才中断,然后用run命令(简写为r)重新从程序开头连续执行:

     

    结果是第一次执行scanf之前没有中断,第二次却中断了。

    gdb基本命令:

     

    3.观察点

    断点是当程序执行到某一代码行时中断,而观察点(Watchpoint)是当程序访问某一存储单元时中断,如果我们不知道某一存储单元时在哪里被改动的,这时候观察点尤其有用。

    观察点调试实例:

     

    下面善春原来设的断点,从头执行程序,重复上次的输入,用watch命令设置观察点,跟踪input[4]后面那个字节(input[5]):

     

    gdb基本命令:

     

    4.段错误

    在gdb中遇到段错误就会停下来。如scanf输入整型变量就必须要加&,否则就会出段错误,而输出字符串就不要加&。

    学习C语言不可能不去了解底层计算机体系结构和操作系统的原理,不了解底层原理一个scanf都用不好,更没办法写出正确的程序。

  • 相关阅读:
    vue-fullcalendar插件
    iframe 父框架调用子框架的函数
    关于调试的一点感想
    hdfs 删除和新增节点
    hadoop yarn 实战错误汇总
    Ganglia 安装 No package 'ck' found
    storm on yarn(CDH5) 部署笔记
    spark on yarn 安装笔记
    storm on yarn安装时 提交到yarn失败 failed
    yarn storm spark
  • 原文地址:https://www.cnblogs.com/exew/p/8268535.html
Copyright © 2011-2022 走看看