【Linux】——调试器-gdb的使用

序言:

  • 本期,我将带领大家学习的关于linux下的调试器gdb的使用,废话不多说跟着我一起去看看吧!!


目录

前言

(一)背景介绍

1、debug模式和release模式

2、为什么Release不能调试但DeBug可以调试

3、初步见识

1️⃣readelf

(二)调试代码

1、命名大全

2、具体演示

0️⃣行号显示

1️⃣断点设置

2️⃣逐过程(逐语句)演示

3️⃣强制执行

4️⃣跳转到下一断点

(三)总结


前言

在之前的学习中,我们学习了关于linux下环境开发相关的工具,主要讲解了以下几个:

  • 💨【yum】—— 是Linux下非常常用的一种包管理器,进行软件安装;
  • 💨【vim】—— vim的使用使我们学会了如何编辑一个代码文本;
  • 💨【gcc/g++】—— gcc是一个功能强大的编译器集合,可以帮助开发人员将源代码转换为可执行二进制文件,并提供各种选项和参数来控制编译过程;
  • 💨【make/Makefile】—— 帮助我们自动化构建代码文本。

紧接着我们面临的一个问题就是我们已经有了可以编写代码的环境还可以编写代码,但是如何对一段代码去进行调试呢?这就是本期我们将要解决的问题。对于代码调试就需要使用到 Linux下的调试器gdb。在Linux环境下调试跟我们在vs下调试是不一样的,在vs下我们是通过图形化页面进行操作,而在Linux环境下则是纯文本的操作。


(一)背景介绍

1、debug模式和release模式

在Linux环境下,Debug和Release是两种不同的构建模式。它们的主要区别在于编译器优化、符号表和调试信息等方面。

  • ⚔️ 【Debug】—— 调试版本
  • ⚔️ 【Release】—— 发布版本

Debug模式

  1. Debug模式通常被用于开发阶段,其目的是为了方便开发人员进行代码调试和错误定位;
  2. 在Debug模式下,编译器会保留符号表和调试信息,生成未优化的可执行文件;
  3. 这样可以使得程序在运行时更容易被调试,开发人员可以使用调试器(如gdb)进行单步跟踪和变量监视等操作。

Release模式

  1. Release模式则用于产品发布阶段,其目的是为了生成可执行文件的最终版本,以提高程序运行效率
  2. 在Release模式下,编译器会开启各种优化选项,如去除未使用的代码、内联函数、循环展开等,以提高程序的执行效率;
  3. 同时,也会删除符号表和调试信息,减小可执行文件的大小。

区别

在Linux环境下,Debug和Release模式的区别在于:

  1. Debug模式会生成未优化的可执行文件,并保留符号表和调试信息
  2. 而Release模式则会开启各种优化选项,删除符号表和调试信息,生成可执行文件的最终版本。


2、为什么Release不能调试但DeBug可以调试

首先大家需要知道的是在Linux下,Release模式和Debug模式的可执行文件是不同的。

  1. Debug模式生成的可执行文件包含了符号表和调试信息,可以被调试器(如gdb)使用进行调试;
  2. 而Release模式生成的可执行文件则去除了符号表和调试信息,因此不能使用调试器进行调试。

这样做主要出于两个原因:

安全性

  • 如果将符号表和调试信息包含在发布版本中,黑客可能会利用这些信息来发动攻击,因此在发布版本中删除这些信息是一个基本的安全措施。

可执行文件大小

  • 包含符号表和调试信息的可执行文件比没有这些信息的可执行文件要大得多。对于发布版本,我们希望可执行文件越小越好,这有助于提高程序启动速度和运行效率。

综上所述,在Linux下,我们通常在Debug模式下进行程序的开发和调试,然后在Release模式下进行最终的编译和发布。这样可以保证程序在开发过程中易于调试和定位问题,同时在发布时也能够提供更加高效和安全的可执行文件。


3、初步见识

接下来,我带大家简单的看看在Linux下调试的时候是否如上述我所说的一样

  • 以下是本次调试所要使用到的代码
#include <stdio.h>

int addToTop(int top)
{
    printf("enter addToTop\n");
    int sum = 0;
    for(int i = 1; i <= top; i++)
    {
        sum += i;
    }
    printf("quit addToTop\n");
    return sum;
}

int main()
{
    int top = 100;

    int result = addToTop(top);
    printf("result:%d\n", result);
    return 0;
}
  • Makefile中的内容:
mytest:test.c
	gcc -o mytest test.c  -std=c99
.PHONY:clean
clean:
	rm -f mytest

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


有了上述的代码之后,如果要进入gdb开始调试,只需 gdb + 可执行文件 即可实现;

 解释说明

  1.  从上述大家可以发现,当我们执行相应的指令之后,在显示出的内容中出现了【no debugging symbols found)】 这样的字眼,大概意思就是没有调试信息。
  2. 因此,我们不难看出这可能不是一个【DeBug】版本的可执行程序,就验证了我们讨论的话题——默认是在releas版本下执行的。

那么如何解决这个问题呢?

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

  • 紧接着我们再去执行相关指令,看最终的结果是否还会出现上述那样的情况:
  • 运行之后,我们可以发现此时则没有上述的显示无调试信息的字眼了

 

  • 🅰️ 为了让之前的【Release】版本不被覆盖,我们将其重命名一下为 【mytest-release】
  • 🅱️ 同时在生成【DeBug】版本后一样对其进行一个重命名为 【mytest-debug

 


上述我们只是简单的说明一点,那就是调试是在debug环境下进行的。此时,大家可能会有这样的疑惑,上述所说的我也能懂,但是在调试信息这个层面上 加和不加调试信息有区别呢?

接下来,我就给演示一下这个可执行文件里的调试信息究竟是怎样的:

  • 首先,如果我们想读取一个二进制文件的一个构成,此时就需要一个指令——【readelf】

1️⃣readelf

简单介绍

  1. readelf 是Linux下的一个命令行工具,用于查看可执行文件、共享库和目标文件的详细信息
  2. 它可以显示这些文件的ELF头部、节(Section)头部、符号表、重定位表等相关信息。

readelf命令的常见用法如下

 💨 查看ELF头部信息

  • 使用 “-h” 选项可以查看可执行文件、共享库或目标文件的ELF头部信息,包括文件类型、目标体系结构、入口地址等。
  • 例如:
  •  readelf -h mytest
    

 💨 查看节(Section)头部信息

  • 使用 “-S” 选项可以查看可执行文件、共享库或目标文件的所有节头部信息。
  • 例如:
  •  readelf -S mytest
    

 💨 查看符号表

  • 使用 “-s” 选项可以查看可执行文件、共享库或目标文件的符号表。符号表是一个记录了程序中函数、变量等符号信息的表格,可以帮助调试器进行调试。
  • 例如:
  •  readelf -s mytest
    

 💨 查看重定位表

  1. 使用 “-r” 选项可以查看可执行文件、共享库或目标文件的重定位表。重定位表是一个记录了需要进行地址重定位的符号信息的表格,用于在程序加载时修改符号地址。
  2. 例如:
  3.  readelf -r mytest
    

⚔️ 除此之外,readelf还有许多其他选项和用法,可以根据需要进行深入学习和掌握 ⚔️ 


接下来,根据上述对 readelf 的介绍,我们执行以下指令:

 readelf -S mytest_debug

  • 此时上述可执行文件中的所有内容有30几条,若是我们只想查看一些关于debug的调试信息,就要对这些东西进行一个筛选;
  • 此时就可以使用【grep】命令来进行一个筛选,便可以查看到所有的debug调试信息(以后大家做信息筛选的时候就可以用到这个指令)

 

 

其实,对于【可执行文件】它是一个二进制文件,若是查看它的源码就可以发现里面都是一堆乱码:

 

【小结】

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

(二)调试代码

有了上述的基本认识之后,接下来我们就可以上手去进行操作了。

1、命名大全

在正式的进行代码前,我们需要知道基本的调试命名吧!以下是我整理的关于调试相关的基本指令:

  • list/l 行号:显示binFile源代码,接着上次的位置往下列,每次列10行。
  • list/l 函数名:列出某个函数的源代码。
  • r或run:运行程序。
  • n 或 next:单条执行。
  • s或step:进入函数调用
  • break(b) 行号:在某一行设置断点
  • break 函数名:在某个函数开头设置断点
  • info break :查看断点信息。
  • finish:执行到当前函数返回,然后停下来等待命令
  • print(p):打印表达式的值,通过表达式可以修改变量的值或者调用函数
  •  p 变量:打印变量值。
  • set var:修改变量的值
  • continue(或c):从当前位置开始连续而非单步执行程序
  • run(或r):从开始连续而非单步执行程序
  • delete breakpoints:删除所有断点
  • delete breakpoints n:删除序号为n的断点
  • disable breakpoints:禁用断点
  • enable breakpoints:启用断点
  • info(或i) breakpoints:查看当前设置了哪些断点
  • display 变量名:跟踪查看一个变量,每次停下来都显示它的值
  • undisplay:取消对先前设置的那些变量的跟踪
  • until X行号:跳至X行
  • breaktrace(或bt):查看各级函数调用及参数
  • info(i) locals:查看当前栈帧局部变量的值
  • quit:退出gdb

2、具体演示

大家看到上述的指令那么多,就产生了恐惧的心理,其实大家不要害怕,Linux本来就是命令行的形式,我们多敲上个几遍,自然而然的就记住了。

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

 

0️⃣行号显示

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

  • 当我们想从开头显示时,即【L 0】或者是【L 1】的话那就是从第一行开始往下列10行的内容(其余的同理)

 

  • 若是想要看到我们所写的全部代码,只需要多按几次 【enter】键即可

 

 


1️⃣断点设置

  • break(b) 行号:在某一行设置断点

  • info break :查看断点信息。

 

  • run(或r):从开始连续而非单步执行程序(无端点直接运行结束)

  •  delete breakpoints n:删除序号为n的断点

 

 

 

  •  break 函数名:在某个函数开头设置断点

  •  delete breakpoints:删除所有断点

注意: 

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

 

  • disable breakpoints:禁用断点

 

  • enable breakpoints:启用断点

  • disable breakpoints n:使一个断点无效

 

 

 


2️⃣逐过程(逐语句)演示

为了方便演示,我退出调试后在重新来过。

  • n 或 next:逐过程单条执行【相当于F10,为了查找是哪个函数出错了】

在vs下不知道是否可以区分逐过程和逐语句之间的差别,如果忘了大家下去自己试试看具体是怎么样的。

现在,我要做的就是不进入我们定义的【addToTop】函数里面去,直接把这个函数跑完

 

  • s或step:进入函数调用 ,逐语句执行【相当于F11】

  • 紧接着可以就继续【n】,然后进行逐过程调试,来到for循环中,那么逐过程也就是变量i的累加和计数器count的累加,所以会反复执行;
  • 后面我没有再按【n】了,但是依旧会执行上面的步骤,因为gdb会自动化记忆你上一次执行过的命令,所以若是不想再敲了,直接【enter】即可

 在vs中,会有一个对话框,当我们想看哪个变量都可以从上观察到。那在Linux下怎么查看程序中【sum】以及【i】的值呢?

但是这样是不是觉得很“嘚儿”呀看着,本来在linux 下就是纯命令跟以前用的图形化比起来,我想查变量是想一直看到它的变化,而不是像现在这样。那有没有办法呢?其实是有的。

  • display 变量名:跟踪查看一个变量,每次停下来都显示它的值

 

  • undisplay:取消对先前设置的那些变量的跟踪

  •  bt看到底层函数调用的过程

AddToTop()  和 主函数 main() 函数呈现这样的关系,可以通过【bt】这个指令来查看函数压栈的过程

  •  until X行号:跳至X行

当前在for循环内容执行累加的逻辑,但若是我们一直这么执行下去,就会非常耗时,【until】其实起到直接结束当前循环的作用,即进行指定行号跳转

 

  • print(p):打印表达式的值,通过表达式可以修改变量的值或者调用函数

 


3️⃣强制执行

  • finish:执行到当前函数返回,然后停下来等待命令

 💨  在Linux下的gdb中,我们可以使用【finsh】指令来直接使一个函数执行完毕;

 💨 从下图我们可以看到,首先【s】进到函数内部,接下去我直接使用【finish】,可以看到它直接回到了调用函数的位置,returned了一个返回值

 


4️⃣跳转到下一断点

  • continue(或c):从当前位置开始连续而非单步执行程序

💨 在VS中,我们要直接跳转到下一个断点处只修要按下F5即可,那在gdb中该如何操作呢,只需要敲个【c】就可以了

 


(三)总结

到此,便是关于调试器gdb的全部内容了。接下来,我们简单回顾一下本期都学到了什么

  1. 首先,我们通过对比在vs下调试与linux下调试的区别,引出了可执行程序的【DeBug】版本和【Release】版本,并指出了默认情况下是release版本,我们也通过具体的代码来带大家验证了这一现象。我们要加上一个-g命令选项使其在make之后生成一个【DeBug】版本的可执行程序,这样就可以进行调试了;
  2. 接着我们就介绍了很多相关的指令,并且通过一一举例来大家认识这些指令。小伙伴们不要害怕,只要多加练习,自然就会熟记于心的。

以上便是本文的全部内容了,感谢您的观看和支持!!!

 

  • 35
    点赞
  • 58
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 44
    评论
资源大于15MB分2次上传。 清晰度一般。加到11章 第12,13章没有。 第1章 嵌入式系统基础. 1.1 嵌入式系统简介 1.1.1 嵌入式系统定义 1.1.2 嵌入式系统与PC 1.1.3 嵌入式系统的特点 1.2 嵌入式系统的发展 1.2.1 嵌入式系统现状 1.2.2 嵌入式系统发展趋势 1.3 嵌入式操作系统与实时操作系统 1.3.1 Linux 1.3.2 uC/OS 1.3.3 Windows CE 1.3.4 VxWorks 1.3.5 Palm OS 1.3.6 QNX 1.4 嵌入式系统选型 第2章 基于ARM9处理器的硬件开发平台 2.1 ARM处理器简介 2.1.1 ARM公司简介 2.1.2 ARM微处理器核 .2.2 ARM9微处理器简介 2.2.1 与ARM7处理器的比较 2.2.2 三星S3C2410X处理器详解 2.3 FS2410开发平台 第3章 创建嵌入式系统开发环境 3.1 嵌入式Linux的开发环境 3.2 Cygwin 3.3 虚拟机 3.4 交叉编译的预备知识 3.4.1 Make命令和Makefile文件 3.4.2 binutils工具包 3.4.3 gcc编译器 3.4.4 Glibc库 3.4.5 GDB 3.5 交叉编译 3.5.1 创建编译环境 3.5.2 编译binutils 3.5.3 编译bootstrap_gcc 3.5.4 编译Glibc 3.5.5 编译完整的gcc 3.5.6 编译GDB 3.5.7 成果 3.5.8 其他交叉编译方法 3.6 通过二进制软件包创建交叉编译环境 3.7 开发套件 第4章 调试嵌入式系统程序 4.1 嵌入式系统调试方法 4.1.1 实时在线仿真 4.1.2 模拟调试 4.1.3 软件调试 4.1.4 BDM/JTAG调试 4.2 ARM仿真器 4.2.1 techorICE ARM仿真器 4.2.2 ARM仿真器工作原理 4.2.3 ARM仿真器的系统功能层次 4.2.4 使用仿真器和ADS Debugger调试ARM开发板 4.3 JTAG接口 4.3.1 JTAG引脚定义 4.3.2 通过JTAG烧写Flash 4.3.3 烧写Flash技术内幕 第5章 Bootloader 5.1 嵌入式系统的引导代码 5.1.1 初识Bootloader 5.1.2 Bootloader的启动流程 5.2 Bootloader之vivi 5.2.1 vivi简介 5.2.2 vivi的配置与编译 5.2.3 vivi代码导读 5.3 Bootloader之U-Boot 5.3.1 U-Boot代码结构分析 5.3.2 编译U-Boot代码 5.3.3 U-Boot代码导读 5.3.4 U-Boot命令 5.4 FS2410的Bootloader 第6章 Linux系统在ARM平台的移植 6.1 移植的概念 6.2 Linux内核结构 6.3 Linux-2.4内核向ARM平台的移植 6.3.1 根目录 6.3.2 arch目录 6.3.3 arch/arm/boot目录 6.3.4 arch/arm/def-configs目录 6.3.5 arch/arm/kernel目录 6.3.6 arch/arm/mm目录 6.3.7 arch/arm/mach-s3c2410目录 6.4 Linux-2.6内核向ARM平台的移植 6.4.1 定义平台和编译器 6.4.2 arch/arm/mach-s3c2410/devs.c 6.4.3 arch/arm/mach-s3c2410/mach-fs2410.c 6.4.4 串口输出 6.5 编译Linux内核 6.5.1 代码成熟等级选项 6.5.2 通用的一些选项 6.5.3 和模块相关的选项 6.5.4 和块相关的选项 6.5.5 和系统类型相关的选项 6.5.6 和总线相关的选项 6.5.7 和内核特性相关的选项 6.5.8 和系统启动相关的选项 6.5.9 和浮点运算相关的选项 6.5.10 用户空间使用的二进制文件格式的选项 6.5.11 和电源管理相关的选项 6.5.12 和网络协议相关的选项 6.5.13 和设备驱动程序相关的选项 6.5.14 和文件系统相关的选项 6.5.15 和程序性能分析相关的选项 6.5.16 和内核调试相关的选项 6.5.17 和安全相关的选项 6.5.18 和加密算法相关的选项 6.5.19 库选项 6.5.20 保存内核配置 第7章 Linux设备驱动程序开发 7.1 设备驱动概述 7.1.1 设备驱动和文件系统的关系 7.1.2 设备类型分类 7.1.3 内核空间和用户空间.. 7.2 设备驱动基础 7.2.1 设备驱动中关键数据结构 7.2.2 字符设备驱动开发 第8章 网络设备驱动程序开发 8.1 网络设备驱动程序简介 8.1.1 device数据结构 8.1.2 sk_buff数据结构 8.1.3 内核的驱动程序接口 8.2 以太网控制器CS8900A 8.2.1 特性 8.2.2 工作原理 8.2.3 电路连接 8.2.4 引脚 8.2.5 操作模式 8.3 网络设备驱动程序实例 8.3.1 初始化函数 8.3.2 打开函数 8.3.3 关闭函数 8.3.4 发送函数 8.3.5 接收函数 8.3.6 中断处理函数 第9章 USB驱动程序开发 9.1 USB驱动程序简介 9.1.1 USB背景知识 9.1.2 Linux内核对USB规范的支持 9.1.3 OHCI简介 9.2 Linux下USB系统文件结点 9.3 USB主机驱动结构 9.3.1 USB数据传输时序 9.3.2 USB设备连接/断开时序 9.4 主要数据结构及接口函数 9.4.1 数据传输管道 9.4.2 统一的USB数据传输块 9.4.3 USBD数据描述 9.4.4 USBD与HCD驱动程序接口 9.4.5 USBD层的设备管理 9.4.6 设备类驱动与USBD接口 9.5 USBD文件系统接口 9.5.1 设备驱动程序访问 9.5.2 设备拓扑访问 9.5.3 设备信息访问 9.6 设备类驱动与文件系统接口 9.7 USB HUB驱动程序 9.7.1 HUB驱动初始化 9.7.2 HUB Probe相关函数 9.8 OHCI HCD实现 9.8.1 OHCI驱动初始化 9.8.2 与USBD连接 9.8.3 OHCI根HUB 9.9 扫描仪设备驱动程序 9.9.1 USBD接口 9.9.2 文件系统接口 9.10 USB主机驱动在S3C2410X平台的实现 9.10.1 USB主机控制器简介 9.10.2 驱动程序的移植 第10章 图形用户接口 10.1 嵌入式系统中的GUI简介 10.1.1 MicroWindows 10.1.2 MiniGUI 10.1.3 Qt/Embedded 10.2 MiniGUI编程 10.2.1 MiniGUI移植 10.2.2 MiniGUI编程 10.3 初识Qt/Embedded 10.3.1 Qt介绍 10.3.2 系统要求 10.3.3 Qt的架构 10.4 Qt/Embedded嵌入式图形开发基础 10.4.1 建立Qt/Embedded 开发环境 10.4.2 认识Qt/Embedded开发环境 10.4.3 窗体 10.4.4 对话框 10.4.5 外形与感觉 10.4.6 国际化 10.5 Qt/Embedded实战演练 10.5.1 安装Qt/Embedded工具开发包 10.5.2 交叉编译Qt/Embedded库 10.5.3 Hello,World 10.5.4 发布Qt/Embedded程序到目标板 10.5.5 添加一个Qt/Embedded应用到QPE 第11章 Java虚拟机的移植 11.1 Java虚拟机概述 11.1.1 Java虚拟机的概念 11.1.2 J2ME 11.1.3 KVM 11.2 Java虚拟机的移植 11.2.1 获得源码 11.2.2 编译环境的建立 11.2.3 JDK的安装 11.2.4 KVM的移植及编译 11.2.5 KVM的测试 11.3 其他可选的虚拟机 11.4 性能优化

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 44
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

起飞的风筝

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值