当前位置: 首页 > news >正文

[Linux基础——Lesson9.调试器GDB]

前言

   GDB是Linux下非常好用且强大的调试工具。GDB可以调试C、C++、Go、java、 objective-c、PHP等语言。对于以后想称为一个Linux下工作的c/c++程序员GDB是必不可少的工具所以本篇来从零讲解GDB在LInux的调试。

        对于GDB调试器来说,不像VS编译器中那样的图形化界面形式,而是采用纯命令行的形式进行调试。so 在开始学习的时候,大家可能会感觉晦涩难懂,但是这是C/C++程序员必须要掌握的技能,所以我将手把手进行零基础的讲解,本篇以C语言来讲解和调试。

一、什么是GDB 

        GDB,全称 GNU Debugger,是 GNU 开源组织发布的一款功能强大的程序调试工具。它是 Linux 和许多类 Unix 系统的标准开发环境,常与 GCC 配套使用,组成一套完整的开发环境

  • 发展历程:GDB 最初由理查德・斯托曼(Richard Stallman)于 1986 年编写,是其 GNU 系统的一部分,现由自由软件基金会任命的 GDB 指定维护人员维护。
  • 支持语言:GDB 支持多种编程语言,包括 C、C++、Fortran、Ada、Objective-C、Go、D 等,能与 GCC、Clang、LLVM 等主流编译器无缝集成。
  • 主要功能:GDB 可以启动程序并按自定义要求运行;能让程序在指定断点处停住,断点可以是条件表达式;程序停住时,可检查程序中的相关情况,如查看变量值、观察内存等;还可以改变程序,修正某个 BUG 产生的影响,以测试其他 BUG。
  • 工作方式:包括本地调试和远程调试。本地调试直接在本地环境对程序进行调试;远程调试则适用于跨平台和多架构场景,GDB 运行在一台机器上,被调试程序运行在另一台机器上。
  • 优势特点:GDB 功能丰富,提供了设置断点、单步执行、回溯函数调用栈等全面的调试功能。它支持广泛的操作系统和平台,包括 Linux、Windows、macOS 以及多种嵌入式平台。此外,GDB 还支持插件机制,可通过安装第三方插件或编写 Python 脚本扩展功能,具有强大的扩展性。同时,作为开源软件,它拥有庞大的社区支持,资源丰富,且可自由获取、使用和修改源代码。

🧐何为调试

        调试是指通过一系列方法和工具,发现并修复程序或系统中的错误或异常行为的过程。它是软件开发过程中的关键环节,对确保程序质量和性能至关重要。

  • 调试目的:
    • 发现错误:找出程序中导致功能异常或结果不正确的代码段。
    • 分析原因:理解错误产生的根本原因,如逻辑错误、语法错误等。
    • 修复问题:修改代码或调整配置,使程序恢复正常运行。
    • 预防未来错误:通过调试经验优化代码结构,提高程序稳定性。
  • 调试方法
    • 日志输出:在关键位置打印变量值或执行流程信息,帮助定位问题。
    • 单步执行:使用调试器逐步执行代码,观察每一步的变化。
    • 断点设置:在特定代码行设置断点,暂停程序运行以便分析。
    • 异常捕获:捕获并记录程序运行时的异常信息,便于后续分析。
    • 回溯分析:通过调用栈回溯,找到错误发生的具体位置。

🧐GDB调试工具---提供的帮助

        GDB(GNU Debugger)是一款功能强大的程序调试工具,主要能提供以下四个方面的帮助:

  • 自定义程序启动可以按照开发者自定义的要求运行程序。例如,使用set args命令可设置程序运行参数,通过相关环境变量设置命令能配置程序运行的环境变量,让程序在特定环境下启动,便于模拟不同运行场景来查找问题。
  • 支持断点调试:能够在指定的代码处设置断点,使程序暂停运行。可使用b +行号在某一行打断点,也可通过b 源文件:函数名在函数的第一行设置断点等。当程序运行到断点处时,可查看当前运行状态,如查看当前变量的值、函数的执行结果等,帮助开发者确定程序是否按预期执行。
  • 检查程序状态:当程序被停住时,可利用 GDB 命令检查当前程序中变量的状态。例如,使用print命令(简写为p)可打印变量的值,whatis命令能显示变量的类型,还可以通过相关命令查看内存、寄存器等信息,以便深入分析程序运行情况,找出潜在问题。
  • 修改程序运行:在程序执行过程中,GDB 允许开发者改变某个变量的值,通过print 变量=值的方式即可实现。此外,还能改变代码的执行顺序,如使用finish命令可让程序在当前函数执行完毕后停下来,从而可以跳过一些不必要的代码段,或者通过设置条件断点等方式改变程序执行路径,以此尝试修改程序中出现的逻辑错误,验证不同修改方案的效果。

    二、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.c2     gcc -o mytest test.c -std=c99                                                                      3 .PHONY:clean4 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版本的可行性程序给到测试人员继续测试
    最后只有当测试通过了,再将生成的【单元测试报告】与产品经理进行核对之后没有问题,那这个软件才可以真正地面向市场👉

    1️⃣先明确:Debug 与 Release 模式的核心差异(为何开发用 Debug,交付用 Release?)

             VS 中两种模式的本质区别,在于编译器对代码的 “处理策略” 不同,最终导致生成的可执行程序(.exe)在 “调试能力” 和 “运行效率” 上完全对立,这也是企业选择不同场景用不同模式的核心原因。具体差异可通过下表清晰区分:

    对比维度Debug 模式(调试模式)Release 模式(发布模式)
    核心用途开发者自测、定位代码错误(如逻辑错、变量异常)交付给测试 / 客户,用于实际运行或正式发布
    是否包含调试信息是(嵌入大量调试符号,如变量地址、代码行映射)否(删除所有调试符号,无法断点调试)
    代码优化无优化(代码按编写顺序执行,便于跟踪)有优化(编译器自动精简代码、提升效率,如循环展开、变量复用)
    运行效率较低(调试信息占用资源,无代码优化)较高(体积更小、运行更快,符合用户使用需求)
    是否暴露内部信息是(可查看所有变量值、函数调用栈)否(隐藏内部逻辑,避免安全风险,也减少资源占用)

            简单说:Debug 是 “给开发者用的‘透明版’”,方便找错;Release 是 “给用户用的‘精简版’”,追求实用—— 这就是为什么开发者自测必须用 Debug(能断点、看变量,快速定位 “代码没跑对” 的问题),而交付测试 / 客户必须用 Release(模拟真实用户的使用环境,避免 Debug 模式的 “特殊逻辑” 掩盖真实问题)。

    2️⃣再拆解:企业级 “开发 - 测试 - 反馈 - 迭代” 流程的细节补充

    1. 开发者自测:不止 “跑一遍 Debug”,还要输出 “单元测试报告”

    开发者用 Debug 模式验证 “代码没报错” 只是基础,真正的 “自测” 需要针对单个功能模块(如一个函数、一个接口) 设计测试用例(比如输入正常值、异常值、边界值),验证功能是否符合需求,再将测试结果整理成 “单元测试报告”(包含测试模块、用例、结果、是否通过)。这份报告的作用是:让测试人员明确 “开发者已验证哪些场景”,避免重复测试;也让后续核对(如与产品经理确认)时有明确的 “功能验证依据”。

    2. 测试反馈后的 “迭代”:不是直接生成 Release 给测试,而是 “Debug 修错→自测→再发 Release”

    当测试人员用 Release 模式发现问题(比如某个功能崩溃、结果错误),不会直接让开发者 “改了就发 Release”—— 因为 Release 模式无法调试,开发者无法定位错误原因。正确的流程是:

    1. 测试人员提交 “缺陷报告”(明确问题场景:如 “输入 100 时程序闪退”“点击按钮后无响应”);
    2. 开发者先用Debug 模式复现问题(模拟测试的操作步骤),通过断点、查看变量等定位错误代码;
    3. 修复后,先用 Debug 模式自测验证 “问题已解决”,再生成新的 Release 版本
    4. 将新 Release 和 “修复说明” 一起交给测试人员,测试人员验证 “问题是否真的解决”,同时确认没有引入新问题(即 “回归测试”)。

    3. 最终发布:“单元测试报告 + 测试通过报告” 双核对,确保符合需求

    最后与产品经理核对时,不止需要开发者的 “单元测试报告”,还需要测试人员的 “测试通过报告”(包含所有测试用例、缺陷修复情况、是否符合发布标准)。产品经理会对照 “原始需求文档”,确认两个报告中的功能验证结果与需求一致(比如 “用户登录功能” 的所有场景都已测试通过、无遗留问题),才会允许软件正式面向市场 —— 这一步是避免 “开发 / 测试跑偏”,确保交付的产品符合用户真实需求。

    3️⃣总结:流程的核心逻辑 ——“分层验证,降低风险”

            整个过程本质是通过 “Debug 模式保障开发效率(快速找错)”“Release 模式模拟真实环境(避免假阳性 / 假阴性测试)”,再通过 “开发者自测→测试人员专测→产品经理核对” 的分层验证,层层过滤错误(从代码逻辑错,到功能场景错,再到需求匹配错),最终确保交付给用户的软件 “既没 bug,又符合需求”—— 这也是企业级软件开发中 “质量管控” 的基础逻辑。

    🎈Linux中开发环境的转换

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

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

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

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

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

    一方面,用户下载需要时间了
    另一方面,用户下载好之后将软件启动、运行都需要更多的时间,体验不好。一般能不加就不加
    ⚡ :但是对于DeBug来说会自动加调试信息,容量体积比Release大

    🎈GDB 调试核心要点总结

            🎯程序发布的两种核心模式:debug 模式与 release 模式是程序从开发到交付的两个关键形态。debug 模式为开发者服务,会保留调试符号(如变量地址、代码行映射)、不做代码优化,方便通过工具追踪程序运行;release 模式为用户 / 测试场景服务,会删除调试符号、进行代码优化(如循环精简、变量复用),以减小程序体积、提升运行效率,二者分别对应 “开发调试” 与 “实际使用” 的不同需求。

            🎯Linux 下 gcc/g++ 的默认编译行为:gcc/g++ 作为 Linux 下主流的 C/C++ 编译器,默认遵循 “交付优先” 原则,直接生成 release 模式的二进制程序。这是因为多数场景下,编译完成后程序需直接运行,而非调试,默认生成 release 模式可避免调试符号占用额外存储空间、降低运行效率,符合 Linux 环境 “轻量高效” 的设计思路,但也导致默认编译的程序无法直接用 GDB 调试。

            🎯GDB 调试的必要前提:GDB 调试依赖程序中的 “调试符号” 来关联源代码与二进制指令(如定位某行代码对应的内存地址、识别变量含义)。由于 gcc/g++ 默认不生成调试符号,因此必须在编译时主动添加-g选项 —— 该选项会让编译器在二进制程序中嵌入完整的调试符号,使 GDB 能识别代码行、变量信息,从而支持断点设置、变量查看、单步执行等调试操作,是开启 GDB 调试的 “必选项”。

            简言之这三点的核心逻辑是Linux 编译器默认生成 “不可调试” 的 release 程序,若需用 GDB 调试,需通过-g选项主动生成 “可调试” 的 debug 程序,本质是通过编译选项控制程序是否包含调试所需的关键信息。


    四、使用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 #include <stdio.h>2 3 void swap(int* x, int* y)4 {5     int t = *x;6     *x = *y;7     *y = t;                                                                                            8 }9 int main(void)10 {11     int a = 10;12     int b = 20;13 14     printf("a = %d, b = %d\n", a, b);15 16     swap(&a, &b);17 18     printf("a = %d, b = %d\n", a, b);19     return 0;20 }

    首先先来看一下运行后的结果:

    接下里,我们进入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函数,就可以清楚地看到函数内部的修改带动了函数外部值的变化,真正地通过【传址调用】交换了两个数

    整体的调试结束!!!!


     六、GDB 调试学习脉络总结:从问题解决到实战巩固

    最后来总结一下本文所学习的内容📖

    1️⃣始于问题:从【no debugging symbols】报错切入核心概念

    学习的起点是 GDB 调试时的典型报错【no debugging symbols found】,这个报错直接指向 “程序版本与调试需求不匹配” 的核心矛盾,也由此自然引出两个关键概念:

    • Debug 版本:为开发者设计,包含完整的调试符号(如代码行映射、变量类型 / 地址信息),支持断点、变量查看、单步执行等调试操作,是定位代码错误的 “工具包”;
    • Release 版本:为测试 / 用户设计,删除调试符号并做代码优化(如精简冗余指令、提升运行效率),体积更小、运行更快,但无法被 GDB 调试,对应真实使用场景。

    而 gcc/g++ 的 “默认生成 Release 版本” 这一特性,恰好解释了报错的根源 —— 默认编译的程序缺少调试符号,因此必须通过-g选项主动指定生成 Debug 版本,从 “解决报错” 过渡到 “理解编译选项的作用”,完成了 “问题→原因→解决方案” 的第一步认知。

    2️⃣深入工具:聚焦 GDB 常用指令,掌握调试核心能力

    解决版本问题后,学习重心转向 GDB 本身的使用,核心是 “抓重点、轻冗余”:

    • 指令学习逻辑:没有陷入 GDB 的所有指令,而是聚焦 “高频常用指令”(如设置断点b、单步执行n/s、查看变量p、运行程序r、退出调试q等),这些指令覆盖了 “启动调试→暂停程序→观察状态→控制执行” 的全流程,是应对多数调试场景的 “刚需技能”;
    • 跨工具迁移价值:明确 GDB 指令的学习不仅服务于 Linux 环境,还能反哺 VS 等 IDE 的调试 —— 无论是 GDB 的单步执行,还是 VS 的鼠标操作,核心逻辑都是 “控制程序运行节奏、观察关键状态”,熟练 GDB 后能更理解调试的本质,提升跨工具的调试思维。

    3️⃣终于实战:通过【Swap Two Numbers】完成知识串联与巩固

    最后的 “交换两个数” 实战是学习的关键闭环,实现了 “工具使用” 与 “底层知识” 的双重巩固:

    • 巩固 GDB 指令:在实际场景中运用断点、查看变量值、跟踪函数调用等指令 —— 比如在swap函数处设断点,观察实参地址传递到形参的过程,验证 “传址调用如何实现值交换”,让指令不再是孤立的 “命令”,而是 “验证逻辑、定位问题的工具”;
    • 联动 C 语言底层:调试过程中重温 “传址调用的本质”(通过指针操作原变量内存)与 “函数栈帧的创建 / 销毁”(进入swap函数时栈帧的开辟、返回时栈帧的释放),将 GDB 调试与 C 语言核心知识绑定,既加深了对调试的理解,也强化了编程语言的底层认知。

    🎈总结:整个学习路径的核心逻辑

            从 “解决报错”(发现版本问题)→“理解原理”(Debug 与 Release 的差异、-g选项的作用)→“掌握工具”(常用 GDB 指令)→“实战落地”(通过案例串联知识),每一步都围绕 “解决实际问题” 展开,最终实现 “会用调试工具” 到 “理解调试本质” 的进阶,同时联动编程语言底层知识,形成了 “工具 - 原理 - 实战” 三位一体的学习效果,为后续更复杂的调试场景(如多文件项目、多线程调试)打下基础。


    结束语

     以下就是我对【Linux基础】GDB调试器保姆级的理解

    感谢你的三连支持!!!

    http://www.dtcms.com/a/438097.html

    相关文章:

  • 网站 推送中国万网域名官网
  • 主窗口(QMainWindow)如何放入文本编辑器(QPlainTextEdit)等继承自QWidget的对象--(重构版)
  • 和 AI 一起修 Bug 心得体会
  • 网站建设科技公司外部环境分析网站首页没有权重
  • 【大语言模型】—— Transformer的QKV及多头注意力机制图解解析
  • SYN VISION韩国发布会:获评非小号Alpha,战略合作PrompTale
  • 安徽工程建设造价信息网站html网站开发视频
  • 图书馆自习室|基于SSM的图书馆自习室座位预约小程序设计与实现(源码+数据库+文档)
  • Ollama 使用详解:本地部署大语言模型的指南
  • 手机上哪个网站浙江省嘉兴市建设局网站
  • 秒杀系统崩溃?Redis分片+Sentinel熔断架构设计指南
  • 【开题答辩全过程】以 J2EE技术在在线购物分享应用中的应用为例,包含答辩的问题和答案
  • 【深入理解计算机网络03】计算机网络的分层结构,OSI模型与TCP/IP模型
  • 网站怎么做移动的窗口达州市建设规划网站
  • #计算 c^d mod n
  • AI与现代数据科学的融合
  • Kaggle医学影像识别(二)
  • 第8篇|特殊环境下的设计挑战:把“风机体质”重塑成“台风型、抗寒型、M型与 Class S 定制款”
  • 别让AI成为“技术债加速器”:敏捷设计习惯如何约束智能开发
  • [论文阅读] AI+软件工程 | AI供应链信任革命:TAIBOM如何破解AI系统“可信难题“
  • 【论文阅读】-《Sparse and Imperceivable Adversarial Attacks》
  • SNN论文阅读——In the Blink of an Eye: Event-based Emotion Recognition
  • 焦作做网站最专业的公司访问wordpress时失败
  • K8s学习笔记(十二) volume存储卷
  • 十分钟搭建thinkphp开发框架
  • JVM中的内存区域划分
  • 做网站用小图标在什么网下载电脑如何做网站
  • FFmpeg 全面教程:从安装到高级应用
  • 10月3号
  • QT肝8天15--左侧静态菜单