Skip to content

崩溃定位、core dump、GDB 工作流(小林「问题排查」组全覆盖)

一、崩溃的“第一现场”:信号

Linux 进程出问题内核发信号,默认动作终止并(可能)产生 core:

崩溃信号说明
段错误(访问非法/越界写只读)SIGSEGV (11)最常见;野指针/越界/悬垂/解引用空指针
非法指令SIGILL (4)代码损坏 / -march 跑在旧 CPU / 未定义行为
浮点异常SIGFPE (8)除零/溢出(实为整数除零也常归此)
总线错误SIGBUS (7)对齐错误 / mmap 越界
abortSIGABRT (6)assert/std::abort/异常失败/new失败(默认)/被安全库检测死
栈溢出SIGSEGV(guard 页)无限递归;ulimit -s 与 gdb bt 见栈全是 f
被 kill -9SIGKILL常 OOM killer / 手动杀

先答「看它是哪个信号」——决定方向(段错误查内存访问,abort 查断言/异常路径)。

二、崩溃定位的标准流程(必背链条)

  1. 复现与初步信息(无 core 时):
    • dmesg/journalctl 尾部能看到 segfault at <addr> ip <ip> sp <sp> error 6 + 触发进程 → 第一线索。
    • 若是偶发/只线上:抓日志、缩小输入、开 ASan 本地复现。
  2. 开 core 并收集
    bash
    ulimit -c unlimited        # 允许产生 core(默认可能 0)
    cat /proc/sys/kernel/core_pattern   # 看 core 写到哪/格式
    生产常 systemd-coredump。core 含崩溃瞬间进程内存+寄存器+栈 → 脚本不需要重跑现场。
  3. 用 gdb 打开 core 看栈
    bash
    gdb ./program core.xxx
    (gdb) bt            # backtrace,最常用:崩在哪、谁调的
    (gdb) frame 3       # 切到第 3 帧看调用者局部
    (gdb) info locals   # 该帧局部变量
    (gdb) p x           # 打印变量
    (gdb) x/20gx ptr    # 按地址查看内存(g 8字节, x hex)
    (gdb) list          # 看源码行(需带 -g 编译)
    (gdb) thread apply all bt   # 多线程全栈(找哪个线程崩)
    解释 core 里有什么(小林原题):“栈里是调用链 + 每帧的返回地址/局部(若帧指针/符号保留)、寄存器快照、当时的堆内存内容;配合 debug 符号(-g)能还原到源码行 + 变量。”
  4. 判类型:越界读 vs 写只读(error code)、空指针 $pc 附近 → 推理越界来源。
  5. 缩小定位
    • -g -O0/-fno-omit-frame-pointer 重新编译让 bt/局部可读(生产用带符号分发包)。
    • 用地址消毒器快速圈定(见下)。
    • 二分注释可疑段 / 给字段加边界断言 / 提日志。
  6. 定位到具体内存错误再修。

三、调试符号与优化框架(release 怎么调)

  • 要看到函数/行/变量需 -g。release 常同时 -O2 导致:
    • 内联把栈“压扁”→ bt 看不到调用者;
    • 变量被优化成寄存器/算掉 → info locals 显示 optimized out。
  • 生产建议:编 debug-info 分发包-g 但不 -O0,或不带优化用另一份原 version 符号),线上 core + 对应符号文件离线分析。
  • 想保帧指针便于 bt:加 -fno-omit-frame-pointer(默认 -O 下 frame pointer 被省略,用寄存器做地址会很乱)。

四、Sanitizers(源码级快速圈内存错误)

工具-fsanitize=抓什么
AddressSanitizeraddress堆越界/栈越界/use-after-free/double-free/野指针(会打印精确行)
UndefinedBehaviorSanitizerundefined有符号溢出、除零、越界、对齐、非法转换等 UB
ThreadSanitizerthread数据竞争(见 concurrency/atomic 相关)
LeakSanitizer(ASan 自带 leak泄漏
bash
g++ -g -fsanitize=address,undefined -fno-omit-frame-pointer main.cpp -o t
./t    # 崩溃时给出崩溃点在源码的详细报告

(HPC/Infra:CI/本地调试开 ASan,性能库有时用 ASAN_OPTIONS=detect_leaks=1。)

五、Valgrind(不重编程序)

  • valgrind --tool=memcheck ./a:运行时检测越界/未初始化/泄漏(比 ASan 慢 10~50x,无需重编,可测别人的库)。ASan 对传统 heap 越界更精准更常用;Valgrind 兼容旧/无 sanitizer 环境。

六、GDB 其它高频能力

  • 起在崩溃前看现场:
    bash
    gdb ./a; (gdb) run <args>; 崩了 bt; 或先 break 某处再 n/s/next-step/step
    (gdb) p type var / info registers / disassemble
  • attach 线上进程:gdb -p PID(遇权限回 ptrace 限制需 root/同 user);受限环境可用 gcore PID 取 core 再离线看,避免停服。
  • catch 信号断点:catch signal SIGSEGVhandle SIGSEGV stop 控制是否停。

七、多线程/定位线上现偶现 & 复杂内存

  • thread apply all bt 找真正崩的线程与各线程是否等锁(配合 info threads)。
  • 内存越界难排时:ASan 优先;进程内堆损坏(前向/后向 cookie 校验失败报 malloc(): corrupted top size abort)→ 常由 buffer overflow 导致,回溯最近谁写该块周边,开 ASan 复核。
  • 偶现/时序 → 先查并发共享(TSan/数据竞争)+ 崩溃现场一致性(内存模型)是否相关。

八、给面试官一段权威叙述

「先看信号定类;开 core(ulimit)保留现场;用 gdb bt/frame/info locals 还原;如果是 release 内联难读就先转 debug 符号/加 -fno-omit-frame-pointer;难缠内存错误直接 ASan/UBSan 复现定位行;多线程用 TSan 与 thread apply all bt。真正根因看访问违例的对象是越界还是悬垂/重复释放,从缓存对象池、引用计数生命周期入手。」

九、自测

  1. 段错误 vs abort 各自暗示什么?
  2. 为何 release 下 bt 常只剩深度很浅?→ 内联 + 帧指针省略。
  3. core 文件没符号怎么最大化利用?→ addr2line(用地址+符号表)、readelf -n、backtrace symbols。

C++ 面试八股 · VitePress 版