GDB 调试实战:启动调试、附加调试与 core 分析全解析
发布时间:2026/11/2 11:18:12 锦皓数字建站

前置知识一、启用 GDB 调试编译期前提1. 编译必须带 -g作用生成调试信息变量名、行号、类型映射没有 -gGDB 只能看到机器指令地址看不到源码行号和变量名g -g -O0 -o app main.cpp-g生成调试信息-O0不优化变量不会被优化掉info locals才能看到值2. 用 CMake 构建时记得显式指定构建类型cmake -DCMAKE_BUILD_TYPEDebug .. # 自动 -g -O0 cmake -DCMAKE_BUILD_TYPERelease .. # 自动 -O3 -DNDEBUG调试困难 make调试用 Debug发布用 Release。3. MakefileCFLAGS -Wall -g CXXFLAGS -Wall -g二、启用 core dump崩溃时留下现场Linux 默认不生成 coreulimit -c为 0。三步开限制、设路径、关拦截。第一步开发板上开启 core 大小限制ulimit -c # 查看0 表示禁止生成 ulimit -c unlimited # 临时开启当前终端及其子进程有效.....剩下的用到了询问 ai 即可要点一、三种调试模式对比方式命令进程状态是否 attachGDB 控制活进程启动调试gdb ./apprun由 GDB 启动否是自己启动的附加调试gdb -p pid/attach pid已在运行是是接管的程序崩溃后 coregdb ./app core已崩溃否只读 core否无活进程补充完整崩溃定位还有dmesg addr2line这条不用 GDB 的路径适合“没 core、不能复现”的场景。二、三种模式的本质区别启动调试GDB --fork/exec-- 被调试进程GDB 是父进程被调试进程是子进程GDB 控制进程的创建和运行。附加调试GDB 使用-p或attach接管已运行进程GDB 接管一个“别人启动的”活进程。程序崩溃后 coreGDB --加载文件-- core 文件进程已死GDB 只读内存快照不控制任何进程。关键前两种 GDB 都控制一个进程启动的 or 接管的第三种 GDB 不控制任何进程只分析文件三、每种模式什么时候用场景用哪种理由开发阶段自己跑程序复现 bug启动调试能从头控制、设断点、单步板端程序已跑起来卡住/CPU 高/内存涨附加调试不能重启会丢现场只能接入看程序崩了有 core 文件程序崩溃后 core进程已死只能分析快照程序崩了没 core但能复现启动调试 run复现崩溃bt 看栈程序崩了没 core不能复现dmesg addr2line用 ip 地址反查源码行四、三种调试方法的写法方法 1启动调试gdb ./app (gdb) run (gdb) run --device /dev/video0 # 带参数启动说明GDB 自己 forkexec 启动被调试进程GDB 是父进程。能从头设断点、单步、控制运行。方法 2附加调试两种入口效果相同# 写法 A启动时直接指定 pid gdb -p 12345 写法 B先启动 gdb再 attach gdb (gdb) attach 12345说明GDB 接管一个已在运行的活进程ptrace PTRACE_ATTACH。两种入口效果完全一样只是命令行入口不同。附加后进程默认暂停看完用detach让进程继续不要 kill。方法 3事后 core 分析gdb ./app core # 或 gdb ./app core.12345说明进程已崩溃GDB 只加载 core 内存快照文件进行分析。不 attach 任何活进程不控制进程只读文件。五、调用栈与三个核心命令三种模式通用调用栈每调用一个函数栈上压一帧frame函数返回弹出帧编号从 0 开始#0 当前正在执行 / 崩溃发生的函数最顶层数字越大越靠外层调用者最底层是 mainbt / backtrace作用打印整个调用栈谁调了谁、崩在哪行用途崩溃后第一件事看调用链frame N作用把视角切到第 N 层看那一层的现场用途根因常在上层上层传了坏参数必须切上去看注意GDB 默认在 #0变量检查只能看当前帧info locals作用一次性列出当前帧所有局部变量及值用途快速看函数内状态比逐个 p 快注意只能看当前帧要先frame N切过去相关info args看参数info globals看全局变量六、三者配合流程崩溃定位标准动作# 1. 崩溃后先看调用栈 (gdb) bt #0 memcpy () #1 capture_frame (buf0x0, size614400) at v4l2_capture.cpp:88 #2 capture_thread () at v4l2_capture.cpp:150 2. 当前在 #0看不到 capture_frame 的局部变量切到 #1 (gdb) frame 1 #1 capture_frame (buf0x0, size614400) at v4l2_capture.cpp:88 3. 看 capture_frame 的局部变量 (gdb) info locals frame 0x7fff... ret -1 buf 0x0 - 找到根因buf 是空指针 4. 看源码第 88 行前后确认 buf 为什么是 0 (gdb) list结论memcpy 往空指针写 → 段错误根因是 buf 没分配成功mmap 失败没判空。七、附加调试结合 Linux 进程的四个场景场景 1进程卡住看它卡在哪ps aux | grep capture_app # 找 PID gdb -p pid # 附加 (gdb) info threads # 看所有线程状态 (gdb) thread 1 (gdb) bt # 看该线程卡在哪个函数 (gdb) frame 1 (gdb) info locals # 看卡住时变量值场景 2CPU 100%看哪个线程空转top -H -p pid # 按线程看 CPU gdb -p pid (gdb) info threads (gdb) thread 高 CPU 线程号 (gdb) bt # 定位到 spin_wait 等函数 (gdb) thread apply all bt # 一次看所有线程栈核心命令场景 3内存一直涨GDB 附加后用call malloc_stats()看堆统计真正泄漏定位靠 ASan-fsanitizeaddress或 Valgrindx86GDB 用途看可疑对象被谁持有(gdb) p some_object-buffer (gdb) p some_object-ref_count场景 4崩溃但没 coredmesg | tail -20 # 看 segfault 地址 # capture_app[1234]: segfault at 0 ip 0000000000401234 ... addr2line -e capture_app -f -C 0x401234 # 根据 ip 地址反查源码行 # 输出capture_frame /path/v4l2_capture.cpp:88 # 能复现则 gdb ./app 跑崩后 bt八、活进程调试与事后调试的区别对比项事后调试core活进程attach 附加调试栈的含义崩溃那一刻当前这一刻多线程崩溃线程的栈所有线程都有栈bt 范围默认当前线程默认当前线程thread apply all bt看全部变量值崩溃时的值活进程当前值可能被其他线程改附加后状态已死默认暂停需 continue 恢复退出quit 即可detach 让进程继续quit 退出关键附加调试时 GDB 暂停整个进程bt 看的是暂停那一刻的快照。看完用detach不要 kill 活进程。九、完整流程活进程多线程卡死排查# 1. 进程在 ps aux | grep capture_app top -H -p pid # 看线程某线程 0% CPU 卡住 2. 附加 gdb -p pid 3. 看所有线程一次看全部栈 (gdb) info threads (gdb) thread apply all bt 4. 选可疑线程 (gdb) thread 2 (gdb) bt #0 pthread_mutex_lock () #1 encoder::encode () at mpp_h264_encoder.cpp:200 (gdb) frame 1 (gdb) info locals mutex_ 0x... # 等这个锁 5. 找持锁线程 (gdb) thread 3 (gdb) bt #0 sleep () #1 capture_thread () at capture.cpp:80 - 持锁却在 sleep死锁 6. 进程继续跑 (gdb) detach (gdb) quit结论线程 2 等锁线程 3 持锁却 sleep → 死锁。修复调整锁的持有范围别在持锁时 sleep。十、常用命令速查命令说明info threads列出所有线程thread N切到第 N 个线程bt当前线程调用栈thread apply all bt所有线程调用栈多线程排查核心frame N切到栈第 N 层info locals当前帧所有局部变量info args当前帧函数参数p var打印变量list看当前源码detach脱离进程让活进程继续continue让暂停的进程继续十一、常见误区只在 #0 看变量 → 根因常在上层要 frame 切上一层frame 后忘了自己在哪层 → frame 会打印当前帧号info locals 看到空 → 被 -O2 优化掉用 -O0/-O1 编译egg编译使用g -g -O2 -o app main.cpp以为 bt 的 #0 是 main → #0 是当前/崩溃帧数字越大越靠外层附加后直接 quit 杀掉进程 → 先 detach 让程序继续运行再 quit 杀死 GDB 进程事后 core 当成 attach → core 分析不碰活进程只读文件十二、一句话总纲GDB 三种模式启动调试GDB 启动进程、附加调试attach 活进程、事后 core 分析只读崩溃快照不碰活进程。前两种 GDB 控制活进程第三种只分析文件。补充dmesg addr2line应对“没 core、不能复现”。核心命令bt打印整个调用栈frame N切到第 N 层info locals列当前帧局部变量。三者顺序配合bt 看全景 → 发现可疑层 → frame N 切过去 → info locals 看那层变量 → 找到根因常是上层传了坏参数。附加调试扩展info threads→thread N→bt→frame N→info locals。多线程用thread apply all bt一次看全快速发现“谁等锁、谁持锁、谁空转、谁阻塞 read”。附加调试记得 detach 而不是 kill。本质沿调用栈往上追看坏状态从哪层开始。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。