简介笔者此前一直做互联网应用层开发最近换工作新工作的方向更加偏向底层以前通过Xcode、Visual Studio进行调试的伎俩在这里不能再用故有了这篇文章希望看完这篇文章大家可以跟着笔者一起入门gdb调试熟悉gdb调试的一些基础概念和调试手法在碰到相关问题时能够使用掌握的调试手段进行解决调试编译配置并不是任何应用在编译完成后即可进行调试你需要对一些编译选项进行配置最终的编译产物才能很好的被调试器跟踪。下面笔者将会通过cmake进行编译前的编译选项配置配置Debug模式在cmake中可以通过CMAKE_BUILD_TYPEDebug设置编译产物为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这会让被调试程序直接从头开始运行它不会等你startstart命令这会让执行流在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表示断点类型普通断点类型为breakpointdelete删除所有断点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这三个命令downdown命令会切换到下一层的栈帧例如当前栈帧为0这已经是最下一层的栈帧了栈地址由上到下进行增长所以不能再切换、这个时候p n将会输出1upup命令会切换到上一层的栈帧例如当前栈帧为0所以上一层栈帧为1。这个时候p n将会输出2frame 指定层这将会切换到指定层切换完成后print就可以打印对应栈帧中的局部变量以上三个命令即为栈帧切换的核心命令值得注意的是刚才提到的切换栈帧都是建立在不改变程序运行状态的前提下再此前提下让你“穿越”到调用链的不同层次中去观察对应层次中的变量和上下文观察点watch观察点watch类似一种断点但是和普通断点break不同观察点不是监视某一行代码而是监视某个变量或者内存地址当这个变量发生变化时程序会立即停止进入断点状态然后打印新值和旧值watch global_counter监控global_counter变量被写watch g_sensor.temperature监控某个结构体实例的某个字段被写rwatch读观察点监控变量被读取awatch读写观察点监控变量被读或者写总结这篇文章主要一起学习了gdb入门的基础概念包括断点和观察点、栈帧切换等内容还有信号的调试、多线程调试、内存问题 ASan的调试以及Core Dump离线分析的内容笔者还在学习中这部分后续会继续更新一起无限进步PS今年对于笔者来说注定是不平凡的一年笔者毅然结束了九年的杭州生活和学习之旅来到重庆开启下一段旅行。九年间发生了好多事情那年吃完三个菜的“年夜饭”去逛街过年的街上空荡荡的也很冷一家人被突如其来的经济变故打击得手足无措沉默。就这样毫无目地逛着也不知道要到哪里去要怎么走那年母亲为了省一两块钱不坐公交走高速公路四个小时上下班的日子那年母亲第一次出门打工上夜班白天睡不着但是每次视频都说很适应都说挺好的那年全家在外打工妹妹没钥匙回家进不了屋楼下远远的哭着看了一眼这个苦苦支撑的家就又回了学校那年为了能够把这个家拉回正轨不允许自己有一刻的歇息毕业典礼结束两天马上进入工作换工作只谈待遇不谈强度中间只让自己休息一周手术后一个月回到杭州每天杵拐上下班下班后腿已经肿了一圈那堪回首时间来到2026年母亲有了自己的小事业妹妹找到了稳定体面待遇可观的工作我紧绷的弦可以松了做好自己当下力所能及的一切剩下的就交给时间下个九年、二十年、亦或一生才不会后悔我有尽力没有让生命中最重要的人失望