上一篇讲了GDB的基础操作这篇来点硬核的。机器人程序几乎都是多线程的。传感器读取一个线程控制循环一个线程通信一个线程可能还有个单独的线程跑路径规划。线程一多问题就复杂了。最典型的就是程序偶尔崩溃但不知道哪个线程出的事或者程序没崩但某个线程卡死了整个系统不动了。这类问题用printf基本没戏。你得用GDB的多线程调试能力。多线程调试基础先用GDB attach到一个正在运行的进程gdb -p $(pgrep robot_node)或者启动时就带上gdb ./robot_node (gdb) run进入调试状态后按CtrlC暂停程序查看所有线程(gdb) info threads Id Target Id Frame * 1 Thread 0x7f... 0x00007f... in __poll () 2 Thread 0x7f... 0x00007f... in pthread_cond_wait () 3 Thread 0x7f... 0x00007f... in processLidarData () 4 Thread 0x7f... 0x00007f... in controlLoop ()星号标记的是当前线程。你可以切换线程查看(gdb) thread 3 [Switching to thread 3] (gdb) bt #0 processLidarData () at lidar.cpp:142 #1 0x00007f... in std::thread::_impl ()这样就能看到线程3当前执行到哪一行代码了。最实用的命令是thread apply all bt一次性打印所有线程的调用栈。程序卡死的时候这条命令能帮你看清每个线程在干什么。哪个线程在等锁哪个线程在跑计算哪个线程已经退出了一目了然。死锁排查死锁是多线程程序的经典问题。两个线程互相持有对方需要的锁谁都动不了。有一次我们团队的机器人跑着跑着就不动了。传感器数据不更新了控制指令也不发了。ssh到机器人上一看进程还在但CPU占用是0。用GDB attach上去thread apply all bt一看线程A在等mutex_A而mutex_B被线程B持有线程B在等mutex_B而mutex_A被线程A持有。经典死锁。排查死锁的关键是看每个线程的调用栈里卡在哪个mutex上。GDB会显示pthread_mutex_lock的调用往上翻一层就能看到是在哪个函数里加的锁锁的名字是什么。预防死锁的办法说起来简单所有线程按相同顺序获取锁。比如规定先拿sensor_mutex再拿data_mutex所有地方都遵守这个顺序就不会死锁。但实际项目中代码量一大很难保证每个人都遵守。所以有些团队用std::scoped_lock同时获取多个锁由标准库来保证不出现死锁。core dump分析core dump是程序崩溃时的内存快照。它记录了崩溃那一刻程序的所有状态寄存器值、内存内容、调用栈。首先要确保系统允许生成core文件ulimit -c unlimited然后设置core文件的生成路径默认在當前目录容易被覆盖echo /tmp/core_%e_%p | sudo tee /proc/sys/kernel/core_pattern程序崩溃后用GDB加载core文件gdb ./robot_node /tmp/core_robot_node_12345进去之后程序是停在崩溃那一刻的状态。你可以(gdb) bt # 查看崩溃时的调用栈 (gdb) info registers # 查看寄存器状态 (gdb) info locals # 查看局部变量 (gdb) p *this # 查看当前对象的成员 (gdb) frame 3 # 切换到第3层栈帧 (gdb) list # 查看崩溃位置附近的源码core dump分析最厉害的地方在于你不需要能复现问题。机器人现场崩溃了一次只要拿到了core文件就能在开发机上反复分析。这在排查偶发问题时特别有用。机器人崩溃的常见原因在机器人项目中崩溃的原因通常就这几类。空指针解引用。这是最常见的。比如传感器数据还没来你就去访问数据指针。或者动态分配的内存失败了返回nullptr你没检查就用。排查方法很简单bt找到崩溃行p那个指针看看是不是0x0。数组越界。点云数据有10000个点你访问第10001个。或者栅格地图的坐标算出来是负数直接当数组索引用。这类问题有时候不崩溃只是数据错了更难排查。用AddressSanitizer能帮你抓住这类问题g -fsanitizeaddress -g -o robot_node robot_node.cpp运行后一旦越界程序会立刻停下来并报告具体位置。内存泄漏导致的延迟崩溃。每次循环都new一块内存但忘了delete跑几个小时后内存耗尽程序被系统kill掉。这类问题用valgrind排查valgrind --leak-checkfull ./robot_nodevalgrind会报告每一处未释放的内存分配包括是在哪个文件的哪一行new出来的。不过valgrind会让程序慢10到20倍只适合在开发机上测试。Use-After-Free。内存释放了之后还继续访问。在机器人项目里典型场景是一个线程释放了数据缓冲区另一个线程还在读。这类问题用AddressSanitizer也能抓到。实战排查流程总结一下遇到机器人程序崩溃时的排查流程。第一步看有没有core文件。有的话直接GDB加载bt看调用栈定位崩溃位置。第二步如果没有core文件用GDB重新跑程序看能不能复现。能复现的话在可疑的地方设断点单步走。第三步如果偶发崩溃难以复现加上AddressSanitizer重新编译跑。如果怀疑内存泄漏用valgrind。第四步如果以上都搞不定在关键位置加日志。不是printf是用spdlog或者ROS2的rclcpp_logging带时间戳和线程信息。第五步分析日志缩小范围再回到GDB精确定位。这个流程不是万能的但大部分问题都能搞定。关键是别慌一步一步来。面试中怎么聊面试官问调试经验讲具体案例最加分。比如有一次机器人运行时导航节点崩溃了我拿到core文件用GDB分析bt发现崩溃在点云回调里检查发现是点云为空的时候没做判空就访问了。修复后加了防御性检查和单元测试。这种回答好在有场景、有方法、有结果。面试官听完就知道你真正干过这事。多线程调试与core dump分析多线程程序的调试是GDB的强项。用info threads查看所有线程用thread N切换到指定线程在每个线程上都可以独立设断点。core dump分析是排查崩溃问题的关键技能首先用ulimit -c unlimited开启core dump崩溃后用gdb加载core文件然后用bt full查看完整的调用栈和变量值。面试时如果能描述一个实际的core dump排查案例会非常有说服力。还有一个重要技巧用AddressSanitizer编译时加-fsanitizeaddress可以在运行时检测内存错误比如缓冲区溢出、使用已释放内存等。很多难以复现的bug用这个工具一跑就能定位。给你的建议学会用AddressSanitizer。编译时加一个-fsanitizeaddress就能在运行时检测大部分内存问题几乎零学习成本。很多开源项目默认在CI里开着ASAN你开发的时候也应该开。core dump分析一定要会。面试的时候如果问到程序崩溃了怎么排查你说core dump加GDB比说加printf强太多。最后养成看调用栈的习惯。不管是GDB的bt还是日志里的stack trace调用栈是定位问题最快的线索。从崩溃点往上看一层一层找到你的代码问题通常就在那里。另外Valgrind也是排查内存问题的利器和AddressSanitizer互补使用效果更好。上一篇第101篇 GDB调试基础——断点、单步、内存查看下一篇预告第103篇 性能分析工具——perf/flamegraph定位机器人性能瓶颈