gdb\cgdb调试器

📅 2026/7/15 21:47:03
gdb\cgdb调试器
一、快速认识gdbGDB 调试器详解要将代码设置为debug模式gcc SRC(源文件)-oBIN(可执行文件)-stdc99(设置为c99版本编译器)-g为什么需要 GDB初学者调试代码最常用的方式是printf大法——在代码各处插入printf打印变量值来定位问题printf(a %d\n,a);// 临时调试printf(here1\n);// 看程序走到了哪printf(here2\n);// ... 调试完还得一个个删掉烦不胜烦编译形成的文件默认是release模式加上-g选项就是debug模式这种方式在简单场景下能应付但面对复杂 bug 时力不从心源代码效率低每加一个打印就要重新编译、运行循环往复信息有限只能看到你主动打印的变量无法查看调用栈、寄存器、内存布局影响程序行为多线程程序中插入printf可能改变时序导致 bug “消失”Heisenbug无法回溯程序崩溃后没法倒带查看崩溃前的状态GDBGNU Debugger解决了这些问题——它让你能在程序运行时暂停、检查、修改变量、单步执行像时间暂停器一样观察程序的每一个细节。二、快速上手第一个调试会话2.1 编译 Debug 版本GDB 只能调试带有调试信息的程序。编译时必须加-g选项# Release 模式默认—— 不含调试信息GDB 无法识别源码gcc code.c-ocode# Debug 模式 —— 包含源码映射GDB 可以工作gcc code.c-ocode-g-stdc99编译选项含义说明-g生成调试信息在可执行文件中嵌入源码行号、变量名、类型等映射关系-O0关闭优化建议调试时关闭优化否则变量可能被优化掉单步顺序可能错乱-ggdbGDB 专用调试信息比-g包含更丰富的 GDB 特有调试数据2.2 启动 GDB# 方式一启动时指定程序$ gdb ./code# 方式二先启动 gdb再加载程序$ gdb(gdb)file./code# 方式三调试带参数的程序$ gdb--args./code arg1 arg22.3 示例源码以下示例贯穿全文#includestdio.hintsum(intstart,intend)intsum(intstart,intend){intsum0;for(intistart;iend;i){sumi;}returnsum;}intmain(){intasum(1,100);printf(%d,a);return0;}假设我有一个可执行文件debug模式code。执行gdb code进入调试2.4 最基本操作查看、运行、退出查看源文件$ gdb code(gdb)l0# 从第 0 行开始查看源码list(gdb)l# 继续列出后续代码回车重复上一条命令(gdb)b main# 在 main 函数打断点(gdb)r# 运行程序run(gdb)q# 退出 GDBquitlist或者简写l后面跟数字表示从哪一行开始阅读如l 0表示从头开始我们会发现l指令不会以下列出所有代码但是我们接着执行l就会按顺序继续列出代码直至代码完结技巧在 GDB 中直接按回车键会重复执行上一条命令大幅减少输入量。退出gdb三、查看源码在调试前通常需要先浏览源码定位关键行。cgdb命令简写说明listl显示当前位置周围 10 行源码list 行号l 20从第 20 行开始显示list 函数名l main显示指定函数的源码list 文件名:行号l code.c:10显示指定文件的指定行list /list -显示下一段 / 上一段源码set listsize 20设置一次显示的代码行数(gdb)l main# 查看 main 函数(gdb)l0# 从头开始(gdb)l# 回车继续往下看(gdb)l# 再回车继续...# 直到显示 Line number xxx out of range 表示已经到文件末尾四、断点管理断点是调试的核心——让程序在指定位置暂停以便检查状态。4.1 设置断点# 在指定行打断点(gdb)b12## 二、调试命令------cgdb# 在函数入口打断点(gdb)b main(gdb)bsum# 在指定文件的某行打断点多文件项目常用(gdb)b code.c:154.2 查看断点(gdb)info b# 或者 info breakpointsNum Type Disp Enb Address What1breakpoint keep y 0x0000000000401136insumat code.c:72breakpoint keep y 0x000000000040116ainmain at code.c:16字段含义Num断点编号后续删除/禁用时使用Type类型breakpoint / watchpoint / catchpointDisp命中后行为keep 保留 / del 自动删除 / dis 自动禁用Enb是否启用y 启用 / n 禁用What断点位置打断点4.3 删除与禁用断点b 文件名:行号(或函数名)# 删除指定编号的断点(gdb)d1# 删除编号为 1 的断点(gdb)d# 删除所有断点需确认info b# 禁用/启用断点不删除只是暂时关闭(gdb)disable1# 禁用 1 号断点(gdb)enable1# 重新启用 1 号断点# 一次性禁用/启用所有断点(gdb)disable(gdb)enable⚠️注意d后面跟的是info b显示的断点编号不是行号初学者容易搞混。删除断点4.4 条件断点d 文件名:编号只在满足条件时才触发断点避免在循环中反复停下# 方式一新建条件断点(gdb)b10ifi50# 当 i 等于 50 时才触发# 方式二为已有断点添加条件(gdb)condition1i50# 给 1 号断点附加条件### 调试# 查看断点条件(gdb)info b# 会显示 stop only if i 50# 移除条件(gdb)condition1# 不带条件参数 清除条件4.5 临时断点启动gdb并没有启动调试的程序当输入r或run之后才会让源程序执行起来在没有断点的情况下run就是执行程序命中一次后自动删除的断点(gdb)tb main# temporary breakpoint(gdb)tbreak20# 等价写法4.6 断点命令列表调试流程可以在断点触发时自动执行一系列命令单步执行不进入函数逐过程n单步执行进入函数逐语句s(gdb)b15(gdb)commands1Type commandsforbreakpoint1, one per line. End with a line saying justend.p ipsumcend# 每次命中 1 号断点时自动打印 i 和 sum然后继续运行五、程序执行控制监视变量GDB 启动后程序并不会自动运行需要输入r或run才会真正执行。在没有断点的情况下run就是让程序从头运行到结束。p 变量名5.1 命令速查常显示dispaly 变量名命令简写说明runr启动程序可带参数r arg1 arg2start启动并停在main函数第一条语句nextn单步执行不进入函数内部逐过程 / step oversteps单步执行进入函数内部逐语句 / step intocontinuec继续执行直到下一个断点finishfin执行完当前函数并跳出返回到调用者until 行号u 30执行到指定行跳出循环的利器return强制当前函数立即返回可带返回值quitq退出 GDB调试中的程序会被终止kill终止当前调试的程序但不退出 GDB5.2 调试流程示意r (启动) program ────────────────────── ● breakpoint │ n (不进函数) │ s (进入函数) ▼ 下一行代码 ── finish (跳出) │ ▼ c (继续) 下一个断点 ── ... ── 程序结束删除监视变量5.3 典型调试节奏undisplay 编号(gdb)b main# 1. 在入口打断点(gdb)r# 2. 启动停在 main(gdb)n# 3. 逐过程单步看看执行流(gdb)s# 4. 进入可疑函数内部(gdb)p var# 5. 查看变量确认值对不对(gdb)n# 6. 继续单步...(gdb)c# 7. 确认没问题全速继续六、查看与修改变量跳出循环6.1 打印变量值until 行号 一直执行到这一行(gdb)p a# 打印变量 a 的值$15050(gdb)psum# 打印函数返回值如果刚执行完 sum$25050(gdb)p i# 打印循环变量$350### 直接跳转到下一个断点(gdb)p/x a# 以十六进制格式打印$40x13bac6.2 打印格式格式符含义示例p/d有符号十进制p/d flag→-1p/x十六进制p/x 255→0xffp/o八进制p/o 8→010p/t二进制p/t 5→101p/c字符p/c 65→Ap/s字符串p/s buf→hellop/a地址以地址形式打印6.3 常显示display跳出函数print每次都要手动输入display会在每次单步后自动打印指定变量跳出函数print每次都要手动输入display会在每次单步后自动打印指定变量finish(gdb)display i# 注册常显示变量 i会分配一个编号(gdb)displaysum# 再注册一个(gdb)n# 单步执行自动显示 i 和 sum(gdb)info display# 查看所有常显示变量Auto-display expressions: Num Enb Expression1: y i2: ysum### 显示临时变量(gdb)undisplay1# 按编号删除常显示变量(gdb)delete display# 删除所有常显示变量6.4 查看局部变量(gdb)info locals# 显示当前栈帧的所有局部变量i10sum55### watch功能(gdb)info args# 显示当前函数的参数start1end100watch 变量6.5 查看寄存器和内存(gdb)info registers# 查看所有 CPU 寄存器(gdb)p$rax# 查看单个寄存器(gdb)x/10x$rsp# 查看栈顶 10 个四字节十六进制(gdb)x/s 0x7fffffffe180# 以字符串形式查看某内存地址6.6 修改变量值set varset var当已经确定问题根因想在调试过程中临时修改值来验证修复思路set var 变量值(gdb)setvar i100# 将 i 的值改为 100(gdb)setvar flag1# 修改标志位(gdb)p i# 确认修改生效$1100设置值如果已经有一个问题比较确定了可以直接调试过程修改值⚠️ 这只修改内存中的值不会修改源码。调试结束后记得去改源代码七、监视点watchpoint条件断点监视点让程序在指定变量的值发生变化时自动暂停是追踪谁改了我的变量问题的利器。条件满足则触发断点(gdb)watchvar# 当 var 被写入时暂停硬件监视点性能好(gdb)rwatch var# 当 var 被读取时暂停(gdb)awatch var# 当 var 被读或被写时暂停b 行号 if条件 ----新增条件断点(gdb)info watchpoints# 查看所有监视点(gdb)d3# 删除 3 号监视点和删除断点方式相同condition 编号 条件 —为已经存在的断点添加条件使用场景// 某个全局变量莫名其妙被改了不知道是谁干的intglobal_counter0;(gdb)watchglobal_counter(gdb)r#Hardware watchpoint1:global_counter#Old value0#New value42#0x0000000000401152insome_function()at bug.c:23// ↑ 精准定位bug.c 第 23 行把 global_counter 改成了 42限制硬件监视点数量有限通常 4 个用完会自动降级为软件监视点速度慢很多。八、函数调用栈程序崩溃或停在断点时最需要知道的是我是怎么走到这里的。(gdb)bt# backtrace打印完整调用栈#0 sum (start1, end100) at code.c:7#1 0x000000000040116a in main () at code.c:16(gdb)bt full# 打印调用栈 每层帧的局部变量(gdb)frame0# 切换到 #0 栈帧(gdb)frame1# 切换到 #1 栈帧调用者(gdb)up# 上移一层栈帧向调用者方向(gdb)down# 下移一层栈帧向被调用者方向(gdb)info frame# 查看当前栈帧的详细信息命令说明bt/backtrace显示调用栈bt full显示调用栈 所有局部变量frame N切换到 N 号栈帧up/down在栈帧间上下移动info frame当前栈帧详情地址、返回地址、保存的寄存器等info locals当前栈帧的局部变量info args当前栈帧的函数参数九、cgdb —— 带源码窗格的 GDBcgdb是 GDB 的一个轻量级前端在终端中提供分屏显示上半部分是源码窗格下半部分是 GDB 命令行。# 安装 cgdb$sudoaptinstallcgdb# Debian/Ubuntu$sudoyuminstallcgdb# CentOS/RHEL# 使用方式与 GDB 完全相同$ cgdb ./codecgdb 操作技巧按键操作i从源码窗格切换到 GDB 命令行Insert 模式Esc从命令行切回源码窗格↑ ↓在源码窗格中上下滚动空格键在当前行设置/取消断点PageUp/PageDown翻页浏览源码:进入 GDB 命令行模式本质上 cgdb 就是 GDB 外面包了一层可视化界面所有 GDB 命令在 cgdb 中完全适用。十、GDB 常用命令速查表10.1 启动与退出命令简写说明gdb ./prog启动 GDB 并加载程序gdb --args ./prog a b启动 GDB 并传递命令行参数file ./prog在 GDB 内加载程序quitq退出 GDBkill终止当前调试的程序10.2 源码查看命令简写说明listl显示源码list 行号l 20从指定行开始显示list 函数名l main显示函数源码list 文件名:行号l code.c:20显示指定文件的指定行search 正则向前搜索源码reverse-search 正则反向搜索源码10.3 断点命令简写说明break 行号b 20在指定行打断点break 函数名b main在函数入口打断点break 文件名:行号b code.c:15在指定文件的指定行打断点break 行号 if 条件b 10 if i100条件断点tbreak 行号tb 20临时断点命中一次自动删除info breakpointsinfo b查看所有断点delete 编号d 1删除指定断点deleted删除所有断点disable 编号disable 1禁用断点enable 编号enable 1启用断点condition 编号 条件cond 1 i50为已有断点附加条件ignore 编号 N忽略此断点 N 次后再触发10.4 程序执行命令简写说明runr运行程序run arg1 arg2r a b带参数运行start启动并停在main入口nextn逐过程不进入函数steps逐语句进入函数continuec继续执行到下一断点finishfin跳出当前函数until 行号u 30执行到指定行return强制当前函数立即返回return 值return 0强制返回并指定返回值jump 行号j 25跳转到指定行继续执行10.5 查看数据命令简写说明print 变量p var打印变量值print /x varp/x var十六进制打印display 变量disp var每次单步自动显示变量info display查看所有常显示变量undisplay 编号undisp 1删除常显示变量info locals查看当前栈帧局部变量info args查看当前函数参数info registersinfo reg查看 CPU 寄存器print $rax查看单个寄存器watch 变量设置写监视点rwatch 变量设置读监视点awatch 变量设置读写监视点info watchpoints查看所有监视点10.6 查看内存命令说明x/NFU addr查看内存见下方格式说明x/10x $rsp查看栈顶 10 个四字节十六进制x/s addr以字符串形式查看内存x/i addr以汇编指令形式查看内存x/NFU格式说明参数含义可选值N显示数量正整数F显示格式x(十六进制)d(十进制)s(字符串)i(指令)c(字符)U单位大小b(1字节)h(2字节)w(4字节)g(8字节)10.7 调用栈命令简写说明backtracebt显示调用栈backtrace fullbt full显示调用栈 局部变量frame Nf 0切换栈帧up上移栈帧down下移栈帧info frame当前栈帧详情10.8 其他实用命令命令简写说明set var 变量值修改变量值disassembledisas反汇编当前函数shell 命令shell ls执行 shell 命令不退出 GDBshow args显示程序的命令行参数set args a b设置程序的命令行参数help 命令h b查看命令帮助apropos 关键词搜索相关命令十一、调试技巧与常见场景11.1 调试段错误Segfault$ ./program Segmentation fault(core dumped)$ gdb ./program core# 用 core dump 文件调试(gdb)bt# 直接看到崩溃位置和调用栈(gdb)frame0# 切换到崩溃帧(gdb)p ptr# 检查指针通常是 NULL 或野指针如果系统没有生成 core dump执行ulimit -c unlimited开启。11.2 调试正在运行的程序# 找到目标进程 PID$psaux|grepmyprogram# 附加到运行中的进程$ gdb-p12345# 或者$ gdb ./myprogram12345# 调试完后用 detach 分离不会终止目标进程(gdb)detach11.3 循环调试技巧# 在循环中逐次迭代太慢用 until 跳出(gdb)b5(gdb)r(gdb)u20# 直接执行到第 20 行循环结束之后# 或者用条件断点 continue(gdb)b8ifi99(gdb)c# 直达第 99 次循环11.4 观察点追踪变量修改# 场景某个变量被意外修改不知道是谁干的(gdb)watchsuspicious_var(gdb)r# 每次 suspicious_var 被写入都会停下显示旧值 → 新值11.5 反向调试Reverse DebuggingGDB 支持时光倒流——回退执行需要 GDB 7.0(gdb)target record# 开启记录模式(gdb)r# 正常运行...# ...碰到 bug(gdb)reverse-next# 反向单步rn(gdb)reverse-step# 反向进入函数rs(gdb)reverse-continue# 反向继续rc(gdb)record stop# 关闭记录11.6 TUI 模式GDB 自带文本界面无需安装额外工具(gdb)tuienable# 或 gdb -tui ./code# 上下分屏上 源码下 命令# TUI 快捷键Ctrl x, a# 切换 TUI 模式Ctrl x, o# 切换焦点窗格Ctrl x,2# 双窗格源码 汇编Ctrl l# 刷新屏幕11.7 保存与恢复断点(gdb)save breakpoints gdb.bp# 保存断点到文件# ...下次调试时恢复(gdb)sourcegdb.bp# 从文件加载断点11.8 打印数组和结构体(gdb)p *array10# 打印数组前 10 个元素(gdb)p *ptrlen# 打印动态数组指针 长度(gdb)p/s str# 以字符串形式打印(gdb)p(struct Foo)var# 强制类型转换后打印十二、总结概念一句话理解-g选项编译时嵌入调试信息GDB 可以工作的前提断点breakpoint让程序在指定位置暂停方便检查状态单步执行next/step一行一行执行代码n不进入函数s进入函数打印print查看变量当前值比printf灵活一万倍常显示display每次单步自动打印变量解放双手监视点watchpoint变量值变化时自动暂停追踪谁改了它调用栈backtrace回答我是怎么走到这里的——调试的指南针条件断点只在满足条件时停下避免循环中反复中断cgdb / TUI自带源码窗格的终端调试器比纯 GDB 直观掌握 GDB 是从猜测式调试走向精确式调试的关键一步。虽然现代 IDEVS Code、CLion都有图形化调试界面但它们底层调用的仍然是 GDB或 LLDB。理解 GDB 命令意味着你在任何 Linux 环境——哪怕是最简朴的远程服务器——都能高效地定位问题。