ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

GDB 调试手册

GDB 调试手册 程序崩溃或行为异常时GDB 能把你直接送到出错的那一行。本手册按真实排查流程组织从可调试构建、复现崩溃到读栈、查内存再用断点与观察点逼出逻辑错误。关键词bt看崩溃栈 ·watch抓变量改写者 ·core事后验尸 · C / C · Linux / WSL / MSYS2目录00 症状速查01 可调试的构建02 启动与复现03 五步定位崩溃点04 Core dump 事后分析05 逻辑异常排查06 多线程调试07 难复现问题与进阶08 命令速查表09 效率配置00 症状速查拿到一个问题先判断它属于哪一类再走对应章节的流程。你看到的现象多半是第一步动作章节Segmentation fault (core dumped)非法内存访问空指针、野指针、越界、栈溢出run复现 →bt§03Aborted/assert失败 /double free or corruption断言失败、double free、堆被写坏bt找到 abort 的调用者§03程序不崩但结果不对逻辑异常分支走错、变量被意外改写条件断点 watch§05偶发崩溃难以复现时序、竞争条件、依赖外部输入带条件的断点/观察点或挂 ASan 长跑§07多线程程序卡死不动死锁 / 阻塞式等待gdb -p挂上去 →thread apply all bt§06程序 hang 住、CPU 打满或无响应死循环 / 阻塞 IOgdb -p挂上去 →bt看停在哪§0601 可调试的构建GDB 的一切能力都建立在符号信息之上。编译时不带-g后面所有技巧都无从谈起。gcc-g-O0-oapp main.c util.c# 调试版黄金组合g-g-O0-oapp *.cpp gcc-ggdb3-O0-oapp main.c# -ggdb3 连宏定义都能展开p MACRO_NAME-g生成符号与行号信息。可以-g3加满。-O0关闭优化。-O2会内联函数、重排代码、优化掉变量调试时会看到optimized out栈帧也与源码对不上。不要strip调试用二进制线上版本可以另编一个带符号的同版本二进制用于分析 core。判断符号是否就位file app输出应含with debug_info如果bt只有地址没有文件名行号就是没带-g。Windows 用户优先在 WSL 里sudo apt install gdb或 MSYS2 装mingw-w64-ucrt-x86_64-gdb。MSVCPDB 符号编译的程序 GDB 读不了请用 Visual Studio 或 WinDbg。02 启动与复现进入 GDB 的几种方式gdb ./app# 最常用gdb--args./app-cprod.yaml# 命令行直接带程序参数gdb-p12345# 挂到正在运行的进程gdb ./app core.12345# 分析崩溃转储见 §04gdb-tui./app# 带源码窗口的界面见 §07在 GDB 里运行程序(gdb) run -c prod.yaml # run 后面的参数传给你的程序 (gdb) set args -c a.yaml # 或预先设置参数之后直接 run (gdb) start # 启动并停在 main 入口临时断点 (gdb) continue # 从暂停处继续直到断点或崩溃程序在 GDB 里崩溃时不会直接退出而是停在出事的那一行——这就是最完整的现场。此时 GDB 会打印Program received signal SIGSEGV, Segmentation fault. 0x0000555555555151 in fill (dst0x0) at app.c:5 5 strcpy(dst, prod);信号处理info signals查看 GDB 对每种信号的处理方式。网络程序常被SIGPIPE打断调试可以让 GDB 忽略(gdb) handle SIGPIPE nostop noprint pass # 不拦截直接交给程序处理03 五步定位崩溃点以这个会崩溃的小程序为例fill()收到一个 NULL 指针。/* app.c */voidfill(char*dst){strcpy(dst,prod);}// 第 1 行崩溃发生在 strcpy 内部intmain(void){char*nameNULL;fill(name);// 第 4 行祸根在这里}第一步复现并看调用栈Backtracerun触发崩溃后第一件事永远是btbacktrace。栈顶是崩溃点常在 libc 内部往下找到第一个属于你自己代码的帧。bt full会连带打印每一帧的局部变量。第二步切换到自己的栈帧Frameframe 1跳到 1 号帧也可up/down逐层移动。切过去之后GDB 会显示该帧对应的源码行。第三步检查这一帧的变量Inspectinfo args看入参info locals看局部变量p 变量逐个确认。本例中p dst显示0x0——空指针实锤。第四步追问坏值从哪里来Trace back崩溃帧只是终点坏值往往产生在更早的地方。用up回到调用方帧继续检查本例main帧里能看到name NULL如果来源更远在赋值处设断点重新run或直接用 §05 的watch。第五步验证假设Verify修复后重新编译再跑一遍确认。也可以先用set var name buf在 GDB 里临时改值继续运行验证「改对就不崩」来佐证判断再动代码。读懂内存里的值第三步里怎么判断一个指针「坏在哪」经验法则看到的值通常含义排查方向0x0或极小地址 0x1000NULL 指针 成员偏移如p-field找到该指针被赋值的地方为何没初始化/分配失败0xcccccccc、0xcdcdcdcd等整齐填充值未初始化内存 / 已释放内存调试堆的填充标记变量未初始化或 use-after-free地址看似合理内容却是垃圾指针指向的对象已被释放或从未构造对照分配与释放的调用时序bt中同一组帧无限重复栈溢出几乎必是递归没有出口检查递归终止条件bt地址混乱、帧不可读返回地址被写坏典型是栈上缓冲区溢出用x/32a $rsp看栈内存找越界的写操作查看数据的命令(gdb) p node # 打印变量自动按类型 (gdb) p *node # 解引用看指向的对象 (gdb) p node-next-val # 表达式随便写 (gdb) p/x value # 十六进制还有 /d 十进制 /t 二进制 /c 字符 (gdb) ptype node # 看类型定义 (gdb) p arr[2]5 # 打印数组中连续 5 个元素 (gdb) x/16xw buf # 按内存看16 个 word十六进制 (gdb) x/4i $pc # 反汇编当前指令附近的 4 条指令 (gdb) x/s str # 按字符串看一段内存x的格式x / 数量 格式 大小如x/16xw 16 个 word 十六进制大小可选b(1B)h(2B)w(4B)g(8B)常见崩溃信号对照信号含义最常见的原因SIGSEGV (11)非法内存读写空/野指针解引用、数组越界、写只读段SIGABRT (6)程序主动 abortassert 失败glibc 检测到 double free / 堆破坏未捕获异常SIGFPE (8)算术异常除以零、INT_MIN / -1溢出SIGBUS (7)总线错误未对齐访问mmap 的文件被截断后继续访问SIGILL (4)非法指令函数指针被写坏跳到非代码区、二进制损坏SIGTRAP (5)陷阱正常命中断点时也是它不必惊慌04 Core dump 事后分析程序已经崩了、进程没了只要留下 core 转储文件就能事后还原完整现场——所有寄存器、栈、内存都冻结在崩溃瞬间。ulimit-cunlimited# 允许生成 core默认常为 0 不生成cat/proc/sys/kernel/core_pattern# 看 core 写到哪里./app# Segmentation fault (core dumped)gdb-q./app core# 加载二进制 core(gdb)bt# 之后与 §03 完全相同systemd 系统上 core 常被systemd-coredump收走用coredumpctl list查找coredumpctl gdb PID直接进入调试。二进制必须与 core 同一次构建。分析线上崩溃时把对应版本的带符号二进制-g未 strip找来再加载。core 里不能run/continue但可以随意bt、切帧、p/x——只读现场随便翻。⚠️注意core 文件可能包含敏感数据内存里的密钥、用户数据分析完按需清理不要随手提交进仓库。05 逻辑异常排查程序不崩只是算错了、走错分支、状态被改坏。思路把程序停在你怀疑的位置检查状态或者设下陷阱等错误发生的那一刻自动停下。断点停在关键位置(gdb) break main.c:42 # 文件:行号 (gdb) break parse_header # 函数名C 可写完整签名区分重载 (gdb) break process if len 1024 # 条件断点只在可疑输入时停 (gdb) condition 2 i 100 # 给 2 号断点补条件 (gdb) ignore 2 50 # 前 50 次命中直接跳过循环第 51 次才停 (gdb) tbreak oneshot_init # 一次性断点命中后自动删除 (gdb) info breakpoints # 列表delete 2 删除 / disable 2 禁用断点还能自动执行命令——把 GDB 变成条件日志打印器比插 printf 重编译快得多(gdb) break send_packet (gdb) commands silent printf send: len%d seq%d\n, len, seq continue end每次命中send_packet就打印一行并继续跑程序几乎不受打扰。单步控制命令缩写行为什么时候用step (s)单步进入函数内部怀疑当前调用的函数有问题next (n)单步跨过函数调用逐行走查当前函数finish跑完当前函数并停在返回处误入不想看的函数赶紧出去until (u)跳出当前循环循环第 3 次迭代才出错不想手动 n 三百次continue (c)跑到下一个断点—return强制当前函数立即返回跳过可疑代码段做对照实验观察点谁改了我的变量逻辑错误最经典的问法是「这个变量是什么时候、被谁改成这个值的」。观察点就是为此而生变量一被写或被读就自动断下来并显示动手的那行代码。(gdb) start (gdb) watch total # total 一被写入就停 (gdb) rwatch buffer # buffer 被读取时停 (gdb) awatch flags # 读或写都停 (gdb) watch *(int*)0x7fff5ab0 # 也可以盯一个内存地址 (gdb) watch arr[3] if i 10 # 观察点同样支持条件 (gdb) info watchpoints (gdb) continue # 命中后 bt 立刻看到是谁写的提示x86 硬件观察点通常只有 4 个且必须在变量作用域内设置先走到它已分配的位置。数量超限或对象过大时 GDB 退回软件轮询实现会显著变慢——用完记得delete。每次停下自动显示(gdb) display state # 每次程序停下都打印 state (gdb) display/i $pc # 也可以显示寄存器、反汇编 (gdb) info display # undisplay 1 取消二分定位法毫无头绪时的通用打法先确认输入在函数入口是对的再确认输出在出口是错的然后在中间打断点二分。break 可疑函数p所有入参——错在入口前还是入口后在可疑区段中点设断点检查关键状态。错在前半段就往前二分错在后半段就往后二分。每轮排除一半代码几轮就能把问题压缩到十几行以内。其他陷阱(gdb) catch throw # C任何 throw 时停下 (gdb) catch syscall write # 调用 write 系统调用时停下 (gdb) set var retry 3 # 现场直接改变量值做实验 (gdb) p (void)dump_state() # 现场调用函数查看内部状态06 多线程调试多线程程序的崩溃和死锁关键是把每个线程各自停在哪、在等谁看清楚。(gdb) info threads # 所有线程* 号是当前线程 (gdb) thread 3 # 切到 3 号线程再 bt/p 就是它的现场 (gdb) thread apply all bt # 所有线程的调用栈一次打完——死锁排查第一招 (gdb) set scheduler-locking on # 单步时冻结其他线程防止它们抢跑破坏现场 (gdb) set scheduler-locking step # 折中仅 step/next 期间锁定死锁 / hang 住的标准流程gdb -p pid挂到卡住的进程也可gdb ./app后run到卡住再Ctrl-C。thread apply all bt找停在futex_wait/__lll_lock_wait/pthread_mutex_lock的线程——它们在等锁。看这些线程的栈往上在哪个函数里加的锁对照出两个线程以相反顺序抢同一组锁的模式。p mutex.__data.__owner可查 glibc 互斥锁当前持有者的线程 ID。竞态数据竞争导致的偶发崩溃给共享变量的写入加watch或在可疑临界区两端设断点配合scheduler-locking手动构造交错。更省事的做法见 §07 的 ThreadSanitizer。07 难复现问题与进阶逆向调试让时间倒流「变量变错了但不知道是哪一步改的」——录下执行过程然后倒着单步(gdb) start (gdb) record full # 从此刻开始录制程序会变慢录满会停可 set record full limit 调大 (gdb) continue # 正常跑直到发现状态不对 (gdb) p counter $1 -1 # 什么时候变负的 (gdb) reverse-step # 单步倒退 (gdb) reverse-next # 倒退但跨过函数调用 (gdb) reverse-continue # 倒退到上一个断点/观察点配合watch更佳record 期间设watch counter再reverse-continue直接倒回它被改写的那一行。支持 Linux x86性能开销大适合缩小范围后使用。GDB Sanitizer偶发内存错误的最佳搭档ASan/TSan 负责在第一时间报告非法访问/竞争的精确位置和分配释放历史GDB 负责在原地检查具体变量值gcc-g-O1-fsanitizeaddress-oapp main.c# 内存错误越界/use-after-free/double freegcc-g-O1-fsanitizethread-oapp main.c# 数据竞争(gdb) run # Sanitizer 触发 abort 后bt 看两份调用栈出错点 当初的分配/释放点Valgrindvalgrind --toolmemcheck ./app不需要重新编译适合不能改构建的场景但慢得多。挂到活进程 / 远程调试gdb-p12345# 挂到运行中的进程结束时 detach 放行# 远程机器目标机gdbserver :1234 ./app# 开发机gdb ./app-extarget remote 目标机IP:1234TUI 源码窗口模式gdb -tui ./app # 或运行中按 Ctrl-x a 切换 (gdb) layout src # 源码命令双窗格layout split 再加反汇编focus cmd 切键盘焦点嫌终端不够用就换图形前端gdb -imi之上接 VS CodeCodeLLDB/cppdbg、CLion、DDD 等命令内核完全一样。08 命令速查表启动 / 运行 / 停止命令作用run [args]/r启动程序可带参数start启动并停在 main 开头continue/c继续运行到下一个断点step/s单步进入函数next/n单步跨过函数finish运行至当前函数返回until跳出当前循环Ctrl-C中断正在运行的程序quit/q退出 GDB断点 / 观察点 / 捕获点命令作用break 位置 [if 条件]设断点位置可为 file:line、函数名、*地址tbreak一次性断点condition N 表达式给 N 号断点加条件ignore N 次数前 N 次命中跳过commands N ... end命中断点 N 时自动执行的命令watch / rwatch / awatch写 / 读 / 读写 观察点catch throw / syscall捕获异常抛出、系统调用等事件info breakpoints列出所有断点delete / disable / enable 管理查看数据 / 栈 / 内存命令作用p 表达式打印值p/x十六进制、p/t二进制ptype查看类型定义display 表达式每次停下自动打印x/NFS 地址检查内存如x/16xw、x/sbt/bt full调用栈含局部变量frame N/up/down切换栈帧info locals / args当前帧的局部变量 / 参数info functions / source / line符号、源文件、行号信息disas反汇编当前函数线程 / 录制 / 杂项命令作用info threads/thread N列出 / 切换线程thread apply all bt所有线程打栈死锁必用set scheduler-locking on|step|off单步时是否冻结其他线程record full/record stop开始 / 停止录制执行reverse-step / reverse-next / reverse-continue逆向单步 / 逆向继续set var 名 值修改变量值set args / show args设置 / 查看程序参数handle SIGXXX nostop noprint让 GDB 忽略某信号attach pid/detach挂到 / 离开进程09 效率配置把常用设置写进用户目录下的~/.gdbinit每次启动自动生效# ~/.gdbinit set history save on # 保存命令历史↑ 能翻上次会话的命令 set history size unlimited set print pretty on # 结构体换行缩进打印 set print array on # 数组元素换行打印 set pagination off # 长输出不再 --Type RET-- 分页STL 容器std::vector/std::string等在较新的 GDB GCC 环境下自带 pretty printer直接p vec就能看到内容。项目目录里的.gdbinit默认不会自动加载安全考虑需要时set auto-load local-gdbinit on。GDB 内建 Pythonpython print(hi)复杂场景可以写脚本批量分析例如遍历链表节点、dump 整块结构。收尾最常用的三连永远是bt→frame N→info locals。先看清自己在哪、手里有什么再决定下一步。
返回列表