GDB线上崩溃定位与核心转储实战

Feng 33 阅读 测试运维

线上进程突然挂掉,日志里只剩一行退出码,这时候能救命的往往是核心转储加 GDB。下面按实际排障顺序讲清楚怎么把现场抓下来、怎么在分析机上还原崩溃点。

先把转储拿到手

核心转储是进程崩溃瞬间的内存快照。前提是系统允许生成:ulimit -c unlimited 只对当前 shell 生效,要持久化得写进 limits.conf 或 systemd 单元配置。转储落盘位置由 /proc/sys/kernel/core_pattern 决定,常见写法是 core.%e.%p,%e 是进程名,%p 是 PID。

这里有个容易翻车的点:不少发行版默认把 core_pattern 配成管道形式,比如 |/usr/lib/systemd/systemd-coredump %P %u %g %s %t %c %h,转储不会落在你指定的目录,而是被 systemd-coredump 收走,得用 coredumpctl list 和 coredumpctl dump <pid> 取出来。另外 core_uses_pid 为 1 时文件名会自动追加 PID,和你在 pattern 里手写的 %p 叠加,命名会和预期对不上。先 cat /proc/sys/kernel/core_pattern 确认实际行为,再决定去哪找文件。

如果进程还活着但状态可疑,不必等它崩。gcore <pid> 可以在线抓一份转储,代价是进程会短暂停顿,高并发服务要挑低峰做。

附加生产进程为什么总失败

直接 gdb /path/to/executable <pid> 附加运行中进程,报 ptrace: Operation not permitted 是常态。根因在 yama 模块:/proc/sys/kernel/yama/ptrace_scope 为 1 时只允许父进程附加子进程,为 2 时仅 root 可附加,为 3 则完全禁止。临时放开用 echo 0 > /proc/sys/kernel/yama/ptrace_scope,但这是全局开关,调完记得改回去。容器环境还要额外确认是否给了 SYS_PTRACE capability,否则容器内怎么调都没用。

附加成功后,break、continue、step、next、print 这套组合照常用。生产上建议少用单步,多用 thread apply all bt 一次性把全部线程栈打出来,再挑可疑线程 thread <id> 切过去看局部变量。

分析机上还原崩溃现场

把转储和二进制一起拷到分析环境,gdb /path/to/executable /path/to/coredump 加载。第一件事是 bt 或 where 看崩溃点,然后 info threads 摸清线程分布,print 查关键变量,x/10x <address> 看内存原始内容。

有个隐性坑:二进制和转储必须严格对应。线上做了热更新、重新编译过、或者符号被 strip 掉,加载时会提示 warning: core file may not match specified executable file,栈帧全是问号。用 readelf -n 比对两者的 build-id,不一致就说明拿错文件了。发布流程里保留带调试符号的二进制副本,比事后猜要省事得多。

崩溃之外的用法

GDB 不只处理已经死掉的进程。线上偶发卡顿、死锁、内存异常增长,都可以先 gcore 抓一份现场,再离线慢慢看,避免长时间 attach 影响业务。多线程死锁的典型特征是所有线程都停在 futex 等待上,thread apply all bt 一打就能看出来谁持锁谁等锁。

分析完 quit 退出即可。整套流程的关键不在 GDB 命令本身,而在转储能不能稳定拿到、二进制和符号能不能对上、附加权限有没有放开——这三件事理顺了,剩下的就是看栈。

ID:7417

Feng
这位作者很神秘,还没有填写简介。