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

gdb调试基础

简介

笔者此前一直做互联网应用层开发,最近换工作,新工作的方向更加偏向底层,以前通过Xcode、Visual Studio进行调试的伎俩在这里不能再用,故有了这篇文章

希望看完这篇文章大家可以跟着笔者一起入门gdb调试,熟悉gdb调试的一些基础概念和调试手法,在碰到相关问题时能够使用掌握的调试手段进行解决

调试编译配置

并不是任何应用在编译完成后即可进行调试,你需要对一些编译选项进行配置,最终的编译产物才能很好的被调试器跟踪。下面笔者将会通过cmake进行编译前的编译选项配置

  • 配置Debug模式:在cmake中可以通过CMAKE_BUILD_TYPE=Debug设置编译产物为Debug包,这样产物中将会有大量调试信息,这很重要
  • 通过CMAKE_CXX_FLAGS_DEBUG和CMAKE_C_FLAGS_DEBUG这两个cmake变量添加编译选项
    • -g3:生成最详细的调试信息(包括宏定义)
    • -O0:禁止进行代码优化,保证源码和指令的一致性
    • -fno-omit-frame-pointer:强制编译器保留函数调用时的“栈帧指针”,栈帧指针是一个指向当前函数栈帧起始位置的指针,调试器通过栈帧指针可以方便的进行调用链回溯。不过GCC在某些优化等级下会使用-fomit-frame-pointer,忽略栈帧指针,通过其它方式计算得到起始地址,这样可以优化性能,但是不利于调试器进行调试
    • -fno-inline:禁止编译器对所有函数进行内联优化。编译器在对某些短小的函数进行内联优化之后,无法在内联函数内添加断点,也看不到对该函数的调用栈帧,所以在编译调试版本时需要关闭内联优化

完成以上编译配置后即可编译生成产物

调试

笔者这里在main函数中写了13个调试的demo,这些demo里面涉及各个方面的调试手段,我将会选择一些相对重要的调试手段进行分享

加载程序

gdb build/gdb_lab

笔者这里的被调试的程序为./build/gdb_lab,以上命令为加载被调试程序,它作用有以下三个

  • 启动GDB调试器
  • 读取./build/gdb_lab的符号表和调试信息
  • 进入GDB的交互式命令行提示符

除此之外它什么也不做。我们将会看到如下输出

程序不会运行,等待我们触发

触发程序运行

在进入GDB交互式命令行之后,需要我们去触发程序运行。一般有如下命令

  • run(r):run命令(或者其缩写r),这会让被调试程序直接从头开始运行,它不会等你
  • start:start命令,这会让执行流在main函数入口处暂停,等候你的发落
  • next(n):进行下一步,这很简单,执行流会运行到下一个指令处,然后在控制台输出这期间的打印信息
  • step(s):步入命令,和next类似,不同点在于,next遇到函数时不会进入函数,但是step步入指令遇到函数时会进入函数;s 5表示连续执行5次步入
  • continue(c):continue命令,如果是通过start命令触发的可执行程序,在next一段代码之后,如果想快速的执行完后续的代码或者快速的运行到下一个断点处,那么这个命令符合你的需求,它将快速执行完剩下的代码,或者执行到下一个断点处

断点

gdb中,通过break(b)设置断点,设置断点的方式有多种

  • break 行号:在指定行号处设置断点。如果该行是一个函数调用,那么执行流不会进入函数,next时执行流会执行该行后面的代码。值得注意的是,如果指定的行没有代码,那么gdb会将断点设置到后面最近的代码行处
  • break 函数名:在指定函数处设置断点,这会让执行流进入该函数,然后不执行任何其它指令立刻停止,等待后续命令
  • break 文件名:行号:和上一个指令效果相同,不同点在于这里指定了行号所属的文件,而上一个命令默认为当前文件中的行号
  • break 位置 if 条件:即在某个位置满足某个条件时执行流才停止,如果想在循环中进行断点,这将会很有用。值得注意的是,if条件中的变量需要在指定位置是可见的

以上是断点的设置,在调试中,难免想删除一些断点,或者临时开关一些断点,这在gdb中也有对应的工具可以用

  • info break:列出所有已经设置好的断点,Num列表示断点编号Enb列表示断点是否启用,y表示打开,n表示关闭Address表示断点地址What表示断点所在文件的具体位置Type表示断点类型,普通断点类型为breakpoint
  • delete:删除所有断点
  • delete 断点编号:delete或者d删除指定编号的断点
  • disable 断点编号:临时关闭指定断点
  • enable 断点编号:重新打开某个断点
  • clear 函数名:删除通过break 函数名设置的断点

在进入断点后,如果想查看变量信息,可以使用print(p)指令查看

  • p i:打印变量i的值
  • p &i:打印变量i的地址
  • p array[5]:打印数组array的第五个元素
  • p *ptr:打印指针ptr指向的内容
  • p ds.id:打印某个结构体的成员变量,ds为该结构体的一个实例
  • p func():调用函数,并打印函数的返回值。这很有意思,这里只是print调用了函数,真正的执行流其实还没有变化,同时你还可以通过print多次调用func(),如果函数内有静态变量,对于静态变量的修改也会表现出来

断点后,如果想查看当前的调用栈,那么可以使用backtrace(bt)进行查看

另外bt full命令在bt命令的基础上会额外打印每一层栈帧中局部变量的当前值

进入断点后,还可以通过list命令查看当前断点位置附近的代码

  • list:列出当前断点附近的代码
  • list 行号:列出指定行附近的代码
  • list 文件名:行号:同上
  • list 函数名:列出指定函数附近的代码
  • list ,:继续显示后面的代码,即翻页

栈帧切换

当函数调用栈层次比较深时,调用栈往往比较复杂,在print当前的变量时,我们可能又忘了再上一层某个变量传入时的值为多少,这个时候就需要有栈帧切换的能力,这能够方便的让我们看到多层调用栈中,不同层次的上下文情况

下面笔者以一个递归调用为例讲解栈帧切换

上图为一个递归函数,现在笔者经过多次步入之后,调用栈如下

可以看到,已经有很多层的调用栈了,这个时候使用print打印变量n时,n在当前调用层次中为1。这是一个简单的例子,当我们现实中想要查看多层调用栈中,不同层次中的变量值时,就需要进行栈帧切换,主要涉及up、down和 frame这三个命令

  • down:down命令会切换到下一层的栈帧,例如当前栈帧为0,这已经是最下一层的栈帧了(栈地址由上到下进行增长),所以不能再切换、这个时候p n将会输出1
  • up:up命令会切换到上一层的栈帧,例如当前栈帧为0,所以上一层栈帧为1。这个时候p n将会输出2
  • frame 指定层:这将会切换到指定层,切换完成后print就可以打印对应栈帧中的局部变量

以上三个命令即为栈帧切换的核心命令,值得注意的是,刚才提到的切换栈帧都是建立在不改变程序运行状态的前提下,再此前提下让你“穿越”到调用链的不同层次中去观察对应层次中的变量和上下文

观察点(watch)

观察点(watch)类似一种断点,但是和普通断点(break)不同,观察点不是监视某一行代码,而是监视某个变量或者内存地址,当这个变量发生变化时,程序会立即停止进入断点状态,然后打印新值和旧值

  • watch global_counter:监控global_counter变量被写
  • watch g_sensor.temperature:监控某个结构体实例的某个字段被写
  • rwatch:读观察点,监控变量被读取
  • awatch:读写观察点,监控变量被读或者写

总结

这篇文章主要一起学习了gdb入门的基础概念,包括断点和观察点、栈帧切换等内容

还有信号的调试、多线程调试、内存问题( ASan)的调试以及Core Dump离线分析的内容笔者还在学习中,这部分后续会继续更新,一起无限进步!!!


PS
今年对于笔者来说注定是不平凡的一年,笔者毅然结束了九年的杭州生活和学习之旅,来到重庆开启下一段旅行。九年间发生了好多事情

那年吃完三个菜的“年夜饭”去逛街,过年的街上空荡荡的也很冷,一家人被突如其来的经济变故打击得手足无措,沉默。就这样毫无目地逛着,也不知道要到哪里去,要怎么走;
那年母亲为了省一两块钱不坐公交,走高速公路四个小时上下班的日子;
那年母亲第一次出门打工,上夜班,白天睡不着,但是每次视频都说很适应,都说挺好的;
那年全家在外打工,妹妹没钥匙回家进不了屋,楼下远远的哭着看了一眼这个苦苦支撑的家,就又回了学校;
那年为了能够把这个家拉回正轨,不允许自己有一刻的歇息,毕业典礼结束两天马上进入工作;换工作只谈待遇,不谈强度,中间只让自己休息一周;手术后一个月回到杭州每天杵拐上下班,下班后腿已经肿了一圈
那堪回首!

时间来到2026年,母亲有了自己的小事业,妹妹找到了稳定体面待遇可观的工作,我紧绷的弦可以松了
做好自己当下力所能及的一切,剩下的就交给时间,下个九年、二十年、亦或一生,才不会后悔,我有尽力,没有让生命中最重要的人失望

http://www.cnnetsun.cn/news/3359048.html

相关文章:

  • C++封装libpng与SDL2实现轻量PNG图像处理类
  • C盘清理全攻略:从基础操作到进阶技巧,安全释放30G+空间
  • 项目实战:从零构建Java+MySQL图书借阅系统(Swing版)
  • 智能代理技能(Agent Skills)开发指南与技术解析
  • 企业级AI Agent落地:从模型能力到工程体系的跨越
  • 单片机DAY_2
  • L9958与TM4C1294NCZAD电机控制方案解析
  • Chapter 2 The Well: Navigating Parasitics and Performance in CMOS Fabrication
  • Lua C接口封装设计:构建安全高效的C/C++与Lua双向通信桥梁
  • RoPE旋转位置编码原理与工程实践:从数学本质到CUDA优化
  • 本地AI音乐生成实战:为游戏开发打造史诗级战斗配乐
  • Mythos:AI原生安全时代的数字安全分析师
  • 通义灵码(阿里云)配置和streamlit实现小交互聊天界面
  • Unity插件缺失报错全解析:从编译错误到运行时依赖的完整解决方案
  • 压电蜂鸣器驱动方案设计与PIC18LF47K40实现
  • 【Java实习面试算法冲刺】回溯
  • 鸿蒙ArkTS实战:实现任务数据的保存与启动恢复
  • 虚幻引擎资源热更新实战:HotPatcher避坑指南与解决方案
  • ComfyUI中Z-Image-Turbo+ControlNet+Krea2高清修复方案详解
  • Anthropic TAI #200:Mythos能力驱动的价值对齐与分阶段开放实践
  • SHP文件纯前端处理:从上传到下载的完整实战指南
  • AI应用做出来怎么收费?很多人按次或月收费,亏了?这个技能帮你10分钟生成按词元计费方案
  • 残差扩散桥模型RDBM:一种面向通用图像恢复的统一扩散框架详解 (去雨、去雾、去雪等)
  • UE5 Lumen硬件光追与Nanite协同渲染的五大核心问题与实战解决方案
  • 【JAVA毕设源码分享】基于springboot宾馆客房管理系统的设计与实现(程序+文档+代码讲解+一条龙定制)
  • 低代码平台横评:Dify、RAGFlow、MaxKB、FastGPT 谁更适合你
  • AI助手在企业政策执行中的偏科现象与优化方案
  • SIEMENS 7396257 D514 印刷电路板
  • 三线模型:AI可解释性的创新框架与实践
  • SL6Pro频谱仪与SDR6软件无线电联动实战:从频谱分析到信号解调