可直接调试带-g的Release程序;若已strip,可用objcopy分离并链接debug信息,gdb通过.gnu_debuglink自动加载符号,但需确保dbg文件路径正确、Build ID匹配且非PIE导致符号丢失。

不能直接调试纯Release程序——它没有调试信息,gdb连函数名、行号、变量都看不到。但你不需要重编Debug版,只要在构建阶段保留调试信息(哪怕最终交付的是strip过的Release二进制),就能用gdb挂上去看死锁、查线程栈、定位崩溃点。
为什么-g和-O2/O3可以共存
很多人误以为“Release = 无-g”,其实不是。-O2或-O3控制优化级别,-g控制是否嵌入调试信息,两者互不冲突。加-g3还能保留宏定义和内联展开信息,对分析复杂逻辑很有用。
-
gcc -O2 -g3 -o app app.c:这是带全量调试信息的Release程序,gdb ./app可直接调试 - 若体积敏感,可后续用
objcopy --strip-debug移除部分段,但别动.debug_*和.gnu_debuglink - 注意:
strip app默认会删掉所有调试相关section,不可逆;必须用--strip-unneeded或明确指定保留项
调试已部署的strip版Release程序
线上跑着一个被strip过的app,没源码、没-g?只要当初编译时生成过带-g的原始二进制,就能补救。
- 用
objcopy --only-keep-debug app.debug app.dbg从原始debug版抽离.debug_*段到独立文件 - 用
objcopy --add-gnu-debuglink=app.dbg app给当前strip版打上debug链接(不改程序行为) -
gdb ./app启动后,gdb会自动按路径找app.dbg或app.debug,加载符号 - 验证是否生效:
readelf -S app | grep debug应看到.gnu_debuglink段;objdump -s -j .gnu_debuglink app能读出dbg文件名
gdb attach时看不到源码或变量
即使有.gnu_debuglink,gdb attach后仍显示No symbol table is loaded,常见原因有三个:
- dbg文件不在
gdb搜索路径里:用set debug-file-directory /path/to/dbg手动指定 - dbg文件与strip版不匹配(build ID不一致):运行
readelf -n app | grep Build.ID比对两端Build ID - 程序是PIE(Position Independent Executable)且未启用
set follow-fork-mode child,fork子进程后符号丢失——加set follow-fork-mode child再attach
死锁现场抓取的关键命令
Release程序卡死,gdb attach进去第一件事不是bt,而是确认线程状态和锁持有关系:
-
info threads:列出所有LWP,标出哪个是当前活跃线程(带*) -
thread apply all bt:对每个线程打印调用栈,重点看哪些线程停在pthread_mutex_lock、__lll_lock_wait等位置 -
info registers配合x/10i $pc:在汇编级确认是否真卡在锁指令,排除信号阻塞或系统调用挂起 - 若用C++ std::mutex,
print *(std::mutex*)0xADDR可查看内部__data.__lock值(0=空闲,1=已锁,-1=争用中)
最易被忽略的一点:objcopy --add-gnu-debuglink写入的只是dbg文件名,不是绝对路径。如果把app拷到另一台机器调试,app.dbg必须放在同一目录,或通过set debug-file-directory显式告知位置——否则gdb静默失败,不报错也不加载符号。











