【Linux】GDB保姆级调试指南(什么是GDB?GDB如何使用?

在CentOS7 下安装 GDB

💦检查机器上是否安装了gdb

rpm -qa | grep gdb

若没有反应,则没有安装,进行下一步

💦gdb的安装

sudo yum install -y gdb

四、GDB在那个开发版本(debug / release)中进行应用呢?

💦看看gdb如何使用

下面是本次调试所要使用到的代码:

  1 #include <stdio.h>
  2 
  3 int AddToTop(int top)
  4 {
  5     printf("Enter AddToTop\n");
  6 
  7     int count = 0;
  8     for(int i = 1;i <= top; ++i)
  9     {
 10        count += i;
 11     }
 12 
 13     printf("Quit AddToTop\n");                                                                         
 14     return count;
 15 }
 16 
 17 int main(void)
 18 {
 19     int top = 10;
 20     int ret = AddToTop(top);
 21 
 22     printf("ret = %d\n", ret);
 23     return 0;
 24 }

下面是Makefile中的内容,用于自动化编译:

  1 mytest:test.c
  2     gcc -o mytest test.c -std=c99                                                                      
  3 .PHONY:clean
  4 clean:
  5     rm -rf mytest

注:-std=c99表示以c99的标准来编译代码


  • 如果要进入gdb开始调试,那直接**gdb + 可执行程序**即可

  • 不过进去之后发现似乎有一些奇怪的内容,【no debugging symbols found】,翻译过来就是没有调试信息。那这是为何呢?是gdb出问题了吗?

  • 先不要着急,如果有经常调试的同学就可以知道只有在【DeBug】的环境下才会有我们想要的调试信息,所以可以初步推断这可能不是一个【DeBug】版本的可执行程序

  • 先使用**q(quit)**退出gdb

让我们先看下去,了解一下其他的知识再来解决这个问题

💦【Debug版本】与【Release版本】的区别

接下去我们就来说说有关【DeBug】和【Release】版本的不同之处
📚**【Debug】—— 调试版本
📚
【Release】**—— 发布版本

  • 在使用 VS 的时候我们可以直接使用鼠标来进行操作,当前程序以DeBug或者是Release的形式进行运行,那么运行出来的可执行程序版本也是不同的,我们程序员在编写代码后运行一般是使用【DeBug】环境进行运行。因为在企业里写软件项目,将代码写完后程序员自己要做简单的测试,保证代码没有问题

  • 当程序员自己测试完没有问题之后,就会将这个可执行程序给到测试人员进行测试,而且会给出自己的单元测试报告。对于测试人员来说所处的模式是【Release】,也就是将来客户要使用的这款软件的发布版本

  • 当测试在测的过程中,一定会发现一些问题。此时测试人员就会把报告再打回研发部。研发部做修改重新生成Release版本的可行性程序给到测试人员继续测试

  • 最后只有当测试通过了,再将生成的【单元测试报告】与产品经理进行核对之后没有问题,那这个软件才可以真正地面向市场👉

💦Linux中开发环境的转换

其实对于我们刚才直接make自动化生成的可执行程序是通过gcc直接编译产生得到的,它是一个**【Release】版本**的可执行程序,因此无法进行调试。

  • 若是我们想要使用**gcc/g++**去生成一个可执行程序时,默认是【Release】版本的而不是【DeBug】

  • 但若是我们想要去生成一个**【DeBug】版本的可执行程序也是可以的,只需要修改一下我们的Makefile即可,给gcc后面带上一个-g的命令选项,此时再去make**一下的话生成的就是【DeBug】版本的了


  • 为了之前的【Release】版本不被覆盖,我们将其重命名一下为**mytest-release**

  • 在生成【DeBug】版本后一样对其进行一个重命名为**mytest-debug**

  • 通过观察上图中两个可执行文件的大小便可以发现虽然它们都是可执行程序,但是容量大小却不一样,这是为什么呢❓

⚡ :因为以Release版本发布的软件是给客户的,客户是不需要调试信息
⚡ :往可执行程序里添加很多的调试信息意味着软件的体积会变大

  • 一方面,用户下载需要时间了
  • 另一方面,用户下载好之后将软件启动、运行都需要更多的时间,体验不好。一般能不加就不加

⚡ :但是对于DeBug来说会自动加调试信息,容量体积比Release大

💦总结

⚡ :程序的发布方式有两种,debug模式和release模式
⚡ :Linux gcc/g++出来的二进制程序,默认是release模式
⚡ :要使用gdb调试,必须在源代码生成二进制程序的时候, 加上-g选项

五、使用GDB调试代码----指令学习

💦 指令集汇总

因为这个调试器是在Linux环境下的,是纯命令行模式,所以会有很多的指令,做好心里准备😢

注:()括号里面是该指令的全称 
💜 l(list) 行号/函数名 —— 显示对应的code,每次10行

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

💜**b(breakpoint) + 行号** —— 在那一行打断点

💜**b 源文件:函数名** —— 在该函数的第一行打上断点

💜**b 源文件:行号** —— 在该源文件中的这行加上一个断点吧

💜**info b** —— 查看断点的信息
breakpoint already hit 1 time【此断点被命中一次】
💜**d(delete) + 当前要删除断点的编号** —— 删除一个断点【不可以d + 行号】

  • 若当前没有跳出过gdb,则断点的编号会持续累加

💜**d + breakpoints** —— 删除所有的断点

💜**disable b(breakpoints)** —— 使所有断点无效【默认缺省】

💜**enable b(breakpoints)** —— 使所有断点有效【默认缺省】

💜**disable b(breakpoint) + 编号** —— 使一个断点无效【禁用断点】

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

  • 相当于VS中的空断点

💜**enable breakpount** —— 使一个断点有效【开启断电】

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

💜**s(step)** —— 逐语句【相当于F11,】

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

💜**set var** —— 修改变量的值

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

💜**display**—— 跟踪查看一个变量,每次停下来都显示它的值【变量/结构体…】

💜**undisplay + 变量名编号**—— 取消对先前设置的那些变量的跟踪

排查问题三剑客🗡
💜**until + 行号** —— 进行指定位置跳转,执行完区间代码

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

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

💦指令演示

看了上面的这些命令后,相信你一定回到了刚开始学习Linux指令的时候那种恐惧感,不过没关系,我会一一地演示这些指令,让你在看完本文后有一个基本的调试能力💪

  • 首先我们进入到gdb,然后它会等待我们输入指令

✨行号显示

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

  • 首先若是直接【L】的话便会随机显示出该源文件中的随机10行内容,这不是我们想要的

  • 若是【L 0】或者是【L 1】的话那就是从第一行开始往下列10行的内容
    • 注意这里的L是小写,而且与数字之间要有一个空格

  • 接下去若是想要看到我们所写的全部代码,只需要多Enter几次就可以了,gdb会自动记忆你上次敲入的指令

✨断点设置

b + 行号 —— 在那一行打断点

b 源文件:函数名 —— 在该函数的第一行打上断点

b 源文件:行号 —— 在该源文件中的这行加上一个断点

✨查看断点信息

info b —— 查看断点的信息

  • 若是直接执行【info】的话,出来的就是所有的调试信息

  • 但若是我们只想查看一下所打的断点的信息,那就在后面加个**b/breakpoint**

  • 接下来简要介绍一下断点的一些字段信息

  • Num —— 编号

  • Type —— 类型

  • Disp —— 状态

  • Enb —— 是否可用

  • Address —— 地址

  • What —— 在此文件的哪个函数的第几行

  • 最后的话就是每个断点信息的下面这块**breakpoint already hit 1 time**即此断点被命中1次
✨删除断点

d + 当前要删除断点的编号 —— 删除一个断点【不可以d + 行号】

d + breakpoints —— 删除所有的断点

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

✨开启 / 禁用断点

disable b(breakpoints) —— 使所有断点无效【默认缺省】

enable b(breakpoints) —— 使所有断点有效【默认缺省】

disable b(breakpoint) + 编号 —— 使一个断点无效【禁用断点】

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

✨运行 / 调试

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

  • 首先若是将断点删除掉,使用【r】指令运行的话就会直接运行到程序结束

  • 再加上断点去运行的话就会在打的断点处停下来

✨逐过程和逐语句

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

  • 可以看到,我从第一个断点处也就是20行的位置开始执行,按下【n】之后因为在其后即22行有一个断点,此时就会直接运行到断点处

s(step) —— 逐语句【相当于F11,一次走一条代码,可进入函数,同样的库函数也会进入】

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

  • 接下去我们可以就继续【n】,然后进行逐过程调试,来到for循环中,那么逐过程也就是变量i的累加和计数器count的累加,所以会反复执行(通过图中最左侧可以看出是第8行和第10行在反复执行

  • 可以看到后面我没有再按【n】了,但是依旧会执行上面的步骤,这点上面也有提到过,因为gdb会自动化记忆你上一次执行过的命令,所以若是不想再敲了,直接**Enter**就可以了

 打印 / 追踪变量

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

  • 都执行了那么多次了,不知道【i】和【count】发生了怎样的变化,将它们打印出来看看吧💻

  • 通过继续执行【n】,然后再去打印就可以发现**i的值和count**的值发生了变化

但是你不觉得这样每次去打印会显得很繁琐吗,那一定会的,所以我们有更好的办法💡

display —— 跟踪查看一个变量,每次停下来都显示它的值【变量/结构体…】

  • 我们也可以去追踪一下这两个变量的地址,不过可以看到对于地址来说是不会发生改变的

undisplay + 变量名编号 —— 取消对先前设置的那些变量的跟踪

  • 但是呢,每次都追踪打印这么多内容又太多了,我想把它们取消了可以吗?答:当然是可以的

  • 既然有**display那就有undisplay**

✨ 查看函数调用

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

  • 通过仔细观察刚才追踪的4个变量最左侧的编号,就可以看到它们的排列的顺序是倒着的。因为变量i和变量count是我们先追踪的,它们的地址是我们后追踪的,所以可以看出这很像是一个压栈的过程

  • 其实不仅是对于它们,**Addtop函数和main**函数也呈现这样的关系。此时我们就可以通过【bt】这个指令来查看函数压栈的过程,此时便可以看到因为

✨ 修改变量的值

set var —— 修改变量的值

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

💦最常用指令(指令三剑客)

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

✨指定行号跳转

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

  • 可以看到,当前在for循环内容执行累加的逻辑,但若是我们一直这么执行下去,就没有时间排错了,除了上面的哪一种【set var】之外,还有一种方法其实起到直接结束当前循环的作用,那就是进行指定行号跳转

  • 通过观察下图可以看到,当我们运行了**until 13**之后,程序直接就给出了我们最终的结果count,而且即将要执行最后的打印语句,说明我们跳转成功了

✨强制执行函数

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

  • 有时候我们会有这样的需求,在初步排查的时候推断可能是某个函数内部的逻辑出了问题,但是呢又不想一步步地进到函数内部进行调试,在VS中其实很简单,只需要在函数下方设个断点,然后F5直接运行到断点处即可

  • 但是在Linux下的gdb中,我们可以使用【finsh】指令来直接使一个函数执行完毕。从下图我们可以看到,首先【s】进到函数内部,接下去我直接使用**finish**,可以看到它直接回到了调用函数的位置,returned了一个返回值

  • 然后可以看到,在获取到返回值后,也就直接进行了printf打印

✨跳转到下一断点

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

  • 这点也是我刚才在上面有提到过的,在VS中,我们要直接跳转到下一个断点处只修要按下F5即可,那在gdb中该如何操作呢,你需要敲个【c】就可以了

  • 从下图我们可以看出,对于这个指令的用处可谓是非常大,当我处于第一个断点也就是20行的时候,直接敲下【c】,就可以运行到第二个断点处也就是第10行。之后若反复敲【c】,因为这是一个单语句的循环,所以循环的下一次还是会执行到此处。上面的这两个功能就和我们在VS中用的F5是一个道理

六、GDB调试的实战演练

📖纸上得来终觉浅,绝知此事要躬行🔨

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值