GDB捕获段错误实时现场需在SIGSEGV触发时立即停住:启动加-ex "handle SIGSEGV stop print nopass",崩溃后info registers和x/10i $rip定位非法地址,配合watch -l p、backtrace full及valgrind交叉验证,编译必须用-O0 -g3 -fno-omit-frame-pointer。

用 gdb 捕获段错误的实时现场
段错误(Segmentation fault)发生时,程序已崩溃退出,默认情况下 gdb 只能看堆栈快照,但实际需要的是「崩溃那一帧」的寄存器、内存和调用链。必须让 gdb 在信号触发时停住,而不是等进程终止:
- 启动时加
-ex "handle SIGSEGV stop":例如gdb -ex "handle SIGSEGV stop" --args ./myapp arg1 - 或进入
gdb后手动设置:handle SIGSEGV stop print nopass(nopass防止系统接管信号导致跳过断点) - 运行后一旦触发
SIGSEGV,gdb会立即中断在出错指令处,此时info registers和x/10i $rip能直接看到访问了哪个非法地址
定位野指针:用 watch 监控指针值变化 + backtrace full 追源头
野指针难在「指针变量还在,但指向的内存早已释放」。单纯看崩溃点(如 *p = 5)没用,得知道 p 是什么时候变脏的:
- 先在疑似野指针变量声明处设条件断点:
break myclass.cpp:42,运行到后用watch -l p(-l表示监控局部变量生命周期内的所有写入) - 若指针是类成员,用
watch this->ptr并配合commands自动打印调用栈:command 1\nbt\nend - 崩溃后立刻执行
backtrace full,重点看std::vector::operator[]或malloc/free调用是否出现在栈中——这往往意味着越界或重复释放
结合 valgrind 交叉验证:GDB 查现场,Valgrind 查根源
gdb 能抓到崩溃瞬间,但对「释放后使用」(UAF)或「未初始化指针读取」这类问题,常因内存复用而错过。必须用 valgrind 补位:
- 用
valgrind --tool=memcheck --track-origins=yes --leak-check=full ./myapp运行,重点关注Invalid read/write和Address 0x... is 8 bytes inside a block of size 16 free'd - 如果
valgrind报某次free后仍有读写,就回到gdb中对该free地址下硬件断点:watch *0x7ffff7aabc00(把地址换成本地输出的) - 注意:开启
valgrind后程序变慢且内存布局改变,某些竞态 UAF 可能不复现——这时得切回gdb+catch syscall munmap监控 mmap/munmap 调用
绕过优化干扰:编译必须带 -O0 -g3,且禁用 -fomit-frame-pointer
Release 编译(-O2 或更高)会让 gdb 显示错乱的源码行、丢失局部变量、甚至无法正确展开栈帧。这不是调试技巧问题,是编译选项硬约束:
-
-O0关闭优化(-O1已可能导致内联函数消失) -
-g3输出完整调试信息(含宏定义和模板实例化细节) - 显式加
-fno-omit-frame-pointer,否则backtrace在深层递归或尾调用时直接失效 - 若必须在接近 Release 的环境下复现(比如只在
-O2下崩溃),可用-O2 -g+set debug frame 1强制gdb尝试解析帧指针,但成功率低,优先考虑复现到-O0
最麻烦的情况是多线程环境下的野指针:一个线程释放了对象,另一个线程刚好执行到虚函数调用——此时 $rax 可能指向已回收的 vtable 内存,gdb 停在 call qword ptr [rax] 却看不出 rax 来自哪。这种必须结合 thread apply all bt 和 info proc mappings 对比各线程内存映射状态,再查 /proc/PID/maps 确认那段地址是否已被 unmapped。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











