Appearance
崩溃定位、core dump、GDB 工作流(小林「问题排查」组全覆盖)
一、崩溃的“第一现场”:信号
Linux 进程出问题内核发信号,默认动作终止并(可能)产生 core:
| 崩溃 | 信号 | 说明 |
|---|---|---|
| 段错误(访问非法/越界写只读) | SIGSEGV (11) | 最常见;野指针/越界/悬垂/解引用空指针 |
| 非法指令 | SIGILL (4) | 代码损坏 / -march 跑在旧 CPU / 未定义行为 |
| 浮点异常 | SIGFPE (8) | 除零/溢出(实为整数除零也常归此) |
| 总线错误 | SIGBUS (7) | 对齐错误 / mmap 越界 |
| abort | SIGABRT (6) | assert/std::abort/异常失败/new失败(默认)/被安全库检测死 |
| 栈溢出 | SIGSEGV(guard 页) | 无限递归;ulimit -s 与 gdb bt 见栈全是 f |
| 被 kill -9 | SIGKILL | 常 OOM killer / 手动杀 |
先答「看它是哪个信号」——决定方向(段错误查内存访问,abort 查断言/异常路径)。
二、崩溃定位的标准流程(必背链条)
- 复现与初步信息(无 core 时):
dmesg/journalctl尾部能看到segfault at <addr> ip <ip> sp <sp> error 6+ 触发进程 → 第一线索。- 若是偶发/只线上:抓日志、缩小输入、开 ASan 本地复现。
- 开 core 并收集:bash生产常
ulimit -c unlimited # 允许产生 core(默认可能 0) cat /proc/sys/kernel/core_pattern # 看 core 写到哪/格式systemd-coredump。core 含崩溃瞬间进程内存+寄存器+栈 → 脚本不需要重跑现场。 - 用 gdb 打开 core 看栈:bash解释 core 里有什么(小林原题):“栈里是调用链 + 每帧的返回地址/局部(若帧指针/符号保留)、寄存器快照、当时的堆内存内容;配合 debug 符号(-g)能还原到源码行 + 变量。”
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 # 多线程全栈(找哪个线程崩) - 判类型:越界读 vs 写只读(error code)、空指针
$pc附近 → 推理越界来源。 - 缩小定位:
- 加
-g -O0/-fno-omit-frame-pointer重新编译让 bt/局部可读(生产用带符号分发包)。 - 用地址消毒器快速圈定(见下)。
- 二分注释可疑段 / 给字段加边界断言 / 提日志。
- 加
- 定位到具体内存错误再修。
三、调试符号与优化框架(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= | 抓什么 |
|---|---|---|
| AddressSanitizer | address | 堆越界/栈越界/use-after-free/double-free/野指针(会打印精确行) |
| UndefinedBehaviorSanitizer | undefined | 有符号溢出、除零、越界、对齐、非法转换等 UB |
| ThreadSanitizer | thread | 数据竞争(见 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 SIGSEGV;handle SIGSEGV stop控制是否停。
七、多线程/定位线上现偶现 & 复杂内存
thread apply all bt找真正崩的线程与各线程是否等锁(配合info threads)。- 内存越界难排时:ASan 优先;进程内堆损坏(前向/后向 cookie 校验失败报
malloc(): corrupted top sizeabort)→ 常由 buffer overflow 导致,回溯最近谁写该块周边,开 ASan 复核。 - 偶现/时序 → 先查并发共享(TSan/数据竞争)+ 崩溃现场一致性(内存模型)是否相关。
八、给面试官一段权威叙述
「先看信号定类;开 core(ulimit)保留现场;用 gdb bt/frame/info locals 还原;如果是 release 内联难读就先转 debug 符号/加 -fno-omit-frame-pointer;难缠内存错误直接 ASan/UBSan 复现定位行;多线程用 TSan 与 thread apply all bt。真正根因看访问违例的对象是越界还是悬垂/重复释放,从缓存对象池、引用计数生命周期入手。」
九、自测
- 段错误 vs abort 各自暗示什么?
- 为何 release 下 bt 常只剩深度很浅?→ 内联 + 帧指针省略。
- core 文件没符号怎么最大化利用?→ addr2line(用地址+符号表)、
readelf -n、backtrace symbols。