• 【Linux】Linux工具——gdb


    1. gdb 概述

    GDB是GNU开源组织发布的一个强大的UNIX下的程序调试工具。或许,各位比较喜欢那种图形界面方式的,像VC、BCB等IDE的调试,但如果你是在 UNIX平台下做软件,你会发现GDB这个调试工具有比VC、BCB的图形化调试器更强大的功能。所谓“寸有所长,尺有所短”就是这个道理。

    一般来说,GDB主要帮忙你完成下面四个方面的功能:

    • 1、启动你的程序,可以按照你的自定义的要求随心所欲的运行程序。
    • 2、可让被调试的程序在你所指定的调置的断点处停住。(断点可以是条件表达式)
    • 3、当程序被停住时,可以检查此时你的程序中所发生的事。
    • 4、动态的改变你程序的执行环境。

    从上面看来,GDB和一般的调试工具没有什么两样,基本上也是完成这些功能,不过在细节上,你会发现GDB这个调试工具的强大,大家可能比较习惯了图形化的调试工具,但有时候,命令行的调试工具却有着图形化工具所不能完成的功能。

    2.使用gdb

    mycode.c

     我们先生成一个Debug版本的可执行程序(gcc加上-g选项)

    我们可以验证一下它是不是含有debug信息

     很好,有

    接下来我们就可以使用gdb进行调试了

    在此之前要确保安装了gdb

    2.1.显示代码

     l(list) 行号/函数名 —— 显示对应的code,每次10行

    • 首先若是直接【L】的话便会随机显示出该源文件中的随机10行内容,这不是我们想要的
    • 若是【l 0】或者是【l 1】的话那就是从第一行开始往下列10行的内容
      • 注意这里的L是小写,而且与数字之间要有一个空格
    • 我们想要看第二行或者第10行,可以像下面这么做

    • 这个也没有显示出全部代码啊!!!只需要多Enter几次就可以了,gdb会自动记忆你上次敲入的指令

    • 我们还可以查看一个函数

    2.2.设置断点

    • r或run:运行程序。

    因为我们还没有设置断点,所以一直执行到尾了

     我们现在来打断点

    • break/b 行号:在某一行设置断点
    • break/b 函数名:在某个函数开头设置断点
    • b/break 源文件:行号 —— 在该源文件中的这行加上一个断点 

    2.3.查看断点信息

     我们打了3个断点,我们发现我们的断点不像windows上面的断点,我们现在看不到断点,那我们怎么去查呢?

    • info break/b :查看断点信息。
    • 若是直接执行【info】的话,出来的就是所有的调试信息
    • 但若是我们只想查看一下所打的断点的信息,那就在后面加个b/break

     断点的信息都显示出来了

    • 接下来简要介绍一下断点的一些字段信息
    • Num —— 编号
    •  Type —— 类型
    •  Disp —— 状态
    •  Enb —— 是否可用
    •  Address —— 地址
    •  What —— 在此文件的哪个函数的第几行
    • 最后的话就是每个断点信息的下面这块breakpoint already hit 1 time即此断点被命中1次

    2.4.删除断点 

    我们想删除断点,该怎么做呢?

    我们先看看我们之前设置的断点

    •  d + 当前要删除断点的编号 —— 删除一个断点【不可以d + 行号】
    • d + breakpoints —— 删除所有的断点
    • 此时若继续将这个20行的断点打上时,就可以发现其编号为【4】,而并不是从1开始,这是因为我们没有退出过gdb,所以会持续上一次的编号继续往下

    2.5.开启/禁用断点

    •   disable b(breakpoints) —— 使所有断点无效【默认缺省】
    • 我们看到所有断点的Enb从y变成了n,代表从可用变成了不可用
    • enable b(breakpoints) —— 使所有断点有效【默认缺省】
    • 我们看到所有断点的Enb从n变成了y,代表从不可用变成了可用
    • disable b(breakpoint) + 编号 —— 使一个断点无效【禁用断点】

    • enable b(breakpoint) + 编号 —— 使一个断点有效【开启断点】

    2.6.运行/调试 

    r(run) —— F5【无断点直接运行、有断点从第一个断点处开始运行】

    • 首先若是将断点删除掉,使用【r】指令运行的话就会直接运行到程序结束
    • 再加上断点去运行的话就会在打的断点处停下来

    2.7.逐过程和逐语句 

    我们先看看我们设置的断点 

     n(next) —— 逐过程【相当于F10,为了查找是哪个函数出错了】

    • 可以看到,我从第一个断点处也就是17行的位置开始执行,按下【n】之后因为在其后即19行有一个断点,此时就会直接运行到断点处
    • 按n,再按n,直到结束
    • s(step) —— 逐语句【相当于F11,一次走一条代码,可进入函数,同样的库函数也会进入】

    • 此时我们按下【s】,也就相当于是【step】,让程序一步一步地走,继而进入了Addtop这个函数(如下图),若是你在printf()语句要执行时按下【s】的话gdb就会进入printf()库函数内部去执行,这里就不展示了

    • 接下去我们可以就继续【n】,然后进行逐过程调试,来到for循环中,那么逐过程也就是变量i的累加和计数器count的累加,所以会反复执行(通过图中最左侧可以看出是第8行和第10行在反复执行)
    • 可以看到后面我没有再按【n】了,但是依旧会执行上面的步骤,这点上面也有提到过,因为gdb会自动化记忆你上一次执行过的命令,所以若是不想再敲了,直接Enter就可以了

    2.8.打印 / 追踪变量 

    p(print) 变量名 —— 打印变量值

    • 都执行了那么多次了,不知道【i】和【count】发生了怎样的变化,将它们打印出来看看吧
    • 通过继续执行【n】,然后再去打印就可以发现i的值和count的值发生了变化
    • 但是你不觉得这样每次去打印会显得很繁琐吗,那一定会的,所以我们有更好的办法💡

      display —— 跟踪查看一个变量,每次停下来都显示它的值【变量/结构体…】
    • 我们也可以去追踪一下这两个变量的地址,不过可以看到对于地址来说是不会发生改变的
    • 但是呢,每次都追踪打印这么多内容又太多了,我想把它们取消了可以吗?答:当然是可以的
    • 既然有display那就有undisplay
    • undisplay + 变量名编号 —— 取消对先前设置的那些变量的跟踪

    2.9. 查看函数调用 

    bt —— 看到底层函数调用的过程【函数压栈】

    • 通过仔细观察刚才追踪的4个变量最左侧的编号,就可以看到它们的排列的顺序是倒着的。因为变量i和变量res是我们先追踪的,它们的地址是我们后追踪的,所以可以看出这很像是一个压栈的过程
    • 其实不仅是对于它们,Addtop函数和main函数也呈现这样的关系。此时我们就可以通过【bt】这个指令来查看函数压栈的过程,此时便可以看到因为

    2.10.修改变量的值 

    set var —— 修改变量的值

    • 对于这个修改变量的值,很像是在VS里调试之前设置的那种条件断点,可以使调试开始后直接运行到此断点处。不过对于【set var】而言是在调试过程中进行设置

     2.11.gdb指令合集

    注:()括号里面是该指令的全称 

    1.  l(list) 行号/函数名 —— 显示对应的code,每次10行
    2. r(run) —— F5【无断点直接运行、有断点从第一个断点处开始运行】
    3. b(breakpoint) + 行号 —— 在那一行打断点
    4. b 源文件:函数名 —— 在该函数的第一行打上断点
    5. b 源文件:行号 —— 在该源文件中的这行加上一个断点吧
    6. info b —— 查看断点的信息
    7. breakpoint already hit 1 time【此断点被命中一次】
    8. d(delete) + 当前要删除断点的编号 —— 删除一个断点【不可以d + 行号】

    • 若当前没有跳出过gdb,则断点的编号会持续累加
    1. d + breakpoints —— 删除所有的断点
    2. disable b(breakpoints) —— 使所有断点无效【默认缺省】
    3. enable b(breakpoints) —— 使所有断点有效【默认缺省】
    4. disable b(breakpoint) + 编号 —— 使一个断点无效【禁用断点】
    5. enable b(breakpoint) + 编号 —— 使一个断点有效【开启断点】

    • 相当于VS中的空断点
    1. enable breakpount —— 使一个断点有效【开启断电】
    2. n(next) —— 逐过程【相当于F10,为了查找是哪个函数出错了】
    3. s(step) —— 逐语句【相当于F11,】
    4. bt —— 看到底层函数调用的过程【函数压栈】
    5. set var —— 修改变量的值
    6. p(print) 变量名 —— 打印变量值
    7. display —— 跟踪查看一个变量,每次停下来都显示它的值【变量/结构体…】
    8. undisplay + 变量名编号 —— 取消对先前设置的那些变量的跟踪

    • 排查问题三剑客🗡
    1. until + 行号 —— 进行指定位置跳转,执行完区间代码
    2. finish —— 在一个函数内部,执行到当前函数返回,然后停下来等待命令
    3. c(continue) —— 从一个断点处,直接运行至下一个断点处【VS下不断按F5】

    3.最常用指令(指令三剑客) 

    掌握了上面的这些,你就可以在Linux下调一些简单的代码了,不过想做到高效地进行调试,就需要学习一下【三剑客】

    3.1.指定行号跳转 

    until + 行号 —— 进行指定位置跳转,执行完区间代码

    • 可以看到,当前在for循环内容执行累加的逻辑,但若是我们一直这么执行下去,就没有时间排错了,除了上面的哪一种【set var】之外,还有一种方法其实起到直接结束当前循环的作用,那就是进行指定行号跳转
    • 通过观察下图可以看到,当我们运行了until 13之后,程序直接就给出了我们最终的结果count,而且即将要执行最后的打印语句,说明我们跳转成功了
    • 然后可以看到,在获取到返回值后,也就直接进行了printf打印

    3.2.强制执行函数 

    finish —— 在一个函数内部,执行到当前函数返回,然后停下来等待命令

    • 有时候我们会有这样的需求,在初步排查的时候推断可能是某个函数内部的逻辑出了问题,但是呢又不想一步步地进到函数内部进行调试,在VS中其实很简单,只需要在函数下方设个断点,然后F5直接运行到断点处即可
    • 但是在Linux下的gdb中,我们可以使用【finsh】指令来直接使一个函数执行完毕。从下图我们可以看到,首先【s】进到函数内部,接下去我直接使用finish,可以看到它直接回到了调用函数的位置,returned了一个返回值

    3.3.跳转到下一断点

    c(continue) —— 从一个断点处,直接运行至下一个断点处【VS下不断按F5】

    • 这点也是我刚才在上面有提到过的,在VS中,我们要直接跳转到下一个断点处只修要按下F5即可,那在gdb中该如何操作呢,你需要敲个【c】就可以了
    • 从下图我们可以看出,对于这个指令的用处可谓是非常大,当我处于第一个断点也就是20行的时候,直接敲下【c】,就可以运行到第二个断点处也就是第10行。之后若反复敲【c】,因为这是一个单语句的循环,所以循环的下一次还是会执行到此处。上面的这两个功能就和我们在VS中用的F5是一个道理

    4.gdb实战演练

    •  本次演示使用的代码是
    • 我们取名mycode.c,用gcc编译成带有debug信息的
    • 接下来,我们进入GDB 调试模式。

    • 首先我们在程序第16行设置上一个断点,然后【r】从第15行开始运行
    • 然后我们使用【s】进入到swap函数中,因为我首先不想调试,想先立马看看运行结果,但是此时又已经进入调试了,那么我们就可以使用到【finish】来立马执行完这个函数,然后观察一下结果
    • 可以看到,最后打印出结果的时候a和b的值确实发生了交换
    • 既然清楚了二者会进行一个交换,接下去我们就逐语句【n】进行一个单步追踪吧
    • 因为提前看了执行结果,所以我们要重新开始调试,按下【r】即可,它会询问你是否需要重新开始调试,选择y之后就可以重新从16行开始进行调试
    • 首先通过【display】记录一下两个变量的值和地址
    • 接着按【s】进入到swap函数里,追踪一下指针x和指针y的内容,也就是它们所存放的地址,就可以看到,函数内部已经接受到了这两个变量的地址
    • 然后对我们要观察的值变化继续做一个追踪
    • 并且在执行完第一个语句t = *x时,临时变量t中已经存放了变量a的值,也就是指针所指向的那块空间中的值
    • 接下去执行*x = *y,此时*x中的值就发生了变化,因为指针x可以直接找到变量a的地址,所以可以对其中的内容做修改,就变为了20
    • 接下去执行*y = t,同理,指针y可以直接找到变量b的地址,所以可以对其中的内容做修改,将原本保存在临时变量t中的10赋值给到*y,也就修改了其中的内容
    • 再按【n】的话这个swap函数就执行结束了,回到了main函数,就可以清楚地看到函数内部的修改带动了函数外部值的变化,真正地通过【传址调用】交换了两个数
    • 整体的调试结束!!!!
  • 相关阅读:
    华为云桌面Workspace,实惠更实用!
    el-table多选表格 实现默认选中 删除选中列表取消勾选等联动效果
    Vue的props配置项
    k8s~动态生成pvc和pv
    Ubuntu离线安装nvidia-docker完整过程(最简单的解决方法解决nvidia-docker: command not found)
    Java基础-字符串
    element-plus ElMessageBox 实现复制功能,动态点击html元素不生效解决
    Android 屏幕适配
    PostgreSQL简介及安装步骤
    el-dialog弹窗中进度条的(mqtt提供)数据无法清空(清空方法)
  • 原文地址:https://blog.csdn.net/2301_80224556/article/details/139479928