coredump的那些事:02.coredump的分析
前言
在上一篇文章中,我们讲解了 如何配置 Linux 系统,让程序在崩溃时生成 coredump 文件。
那么,生成了 coredump 之后,我们该如何使用它来定位问题?
在这一篇文章里,我们就来回答这个问题,在本文中你会了解到:
- gdb 调试器的基本使用
- coredump 文件的二进制细节
- 为什么 coredump 能够辅助调试器重建现场
- 内核源码如何生成 coredump
1. 从实验开始:生成并使用 coredump
我们先写一个最简单的崩溃程序,模拟空指针异常:
// heap_crash.cpp
void corrupt_heap()
{
int *p = nullptr;
*p = 10; // 崩溃点
}
int main()
{
corrupt_heap();
return 0;
}
编译并加上 -g 保留调试信息:
g++ -g -o heap_crash heap_crash.cpp
启用 coredump:
ulimit -c unlimited
echo "/tmp/core.%e.%p" | sudo tee /proc/sys/kernel/core_pattern
运行:
./heap_crash
段错误 (核心已转储)
在 /tmp 目录下,我们得到了一个 core 文件:
ls /tmp/core.heap_crash.*
/tmp/core.heap_crash.19642
此时我们就有了三样「调试三件套」:
- 可执行文件
heap_crash - coredump 文件
core.heap_crash.19642 - 调试信息(通过
-g选项保留在 ELF 的.debug_*段)
2. gdb 调试 coredump
调试之前,需要确认本地已安装 gdb。
我们通过 man gdb 查看手册,可以找到如下说明:
You can also start with both an executable program and a core file specified:
gdb program core
这意味着我们可以用「可执行文件 + core 文件」启动 gdb:
gdb ./heap_crash /tmp/core.heap_crash.19642
输出结果类似:
Core was generated by `./heap_crash'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0 0x0000562a3c6e113d in corrupt_heap () at heap_crash.cpp:4
4 *p = 10;
此时,gdb 已经准确还原了崩溃时的现场。
常用调试命令
- 查看调用栈:
(gdb) bt full
#0 0x0000562a3c6e113d in corrupt_heap () at heap_crash.cpp:4
p = 0x0
#1 0x0000562a3c6e1153 in main () at heap_crash.cpp:9
- 查看当前帧:
(gdb) f 0
#0 0x0000562a3c6e113d in corrupt_heap () at heap_crash.cpp:4
4 *p = 10;
- 查看局部变量:
(gdb) info locals
p = 0x0
- 查看寄存器:
(gdb) info registers
rip 0x562a3c6e113d 0x562a3c6e113d <corrupt_heap()+20>
rax 0x0
rsp 0x7ffd8a0e6ce0
...
注意:这些寄存器值,正是从 coredump 的 NOTE 段 里读取出来的。
3. coredump 文件的结构
coredump 文件的本质,其实就是一个 特殊类型的 ELF 文件。
用 readelf -h 查看头部:
readelf -h core.heap_crash.19642 | grep Type
Type: CORE (Core file)
用 readelf -l 查看程序头:
Elf 文件类型为 CORE (Core 文件)
Entry point 0x0
There are 24 program headers, starting at offset 64
程序头:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
NOTE 0x580 0x0 0x0
0x15d0 0x0 0x4
LOAD 0x2000 0x562a3c6e1000 0x0
0x1000 0x1000 R 0x1000
LOAD 0x3000 0x562a3c6e2000 0x0
0x1000 0x1000 R E 0x1000
...
其中:
-
PT_NOTE 段
保存寄存器、信号、线程信息:NT_PRSTATUS→ 寄存器状态NT_SIGINFO→ 崩溃信号NT_AUXV→ 程序辅助向量NT_FPREGSET→ 浮点寄存器
-
PT_LOAD 段
保存进程的内存快照(代码段、堆、栈等)。
4. 内核源码如何生成 coredump
核心逻辑在 fs/binfmt_elf.c 的 elf_core_dump() 函数里。
简化版结构如下:
// fs/binfmt_elf.c
static int elf_core_dump(struct coredump_params *cprm)
{
// 1. 收集线程/寄存器/信号等信息 → NOTE 段
fill_note_info(&elf, e_phnum, &info, cprm);
// 2. 遍历进程的虚拟内存区域 → LOAD 段
for (i = 0; i < cprm->vma_count; i++) {
struct core_vma_metadata *meta = cprm->vma_meta + i;
dump_user_range(cprm, meta->start, meta->dump_size);
}
// 3. 将 ELF header、Program Header、NOTE、LOAD 写入文件
dump_emit(...);
}
4.1 填充 NOTE 段
NOTE 段的数据来源于 fill_note_info():
static int fill_note_info(struct elfhdr *elf, int phdrs,
struct elf_note_info *info,
struct coredump_params *cprm)
{
// 进程基本信息
fill_psinfo(psinfo, ...); // NT_PRPSINFO
// 信号信息
fill_siginfo_note(&info->signote, ...);// NT_SIGINFO
// 辅助向量
fill_auxv_note(&info->auxv, ...); // NT_AUXV
// 每个线程的寄存器
for (t = info->thread; t != NULL; t = t->next) {
fill_thread_core_info(t, ...); // NT_PRSTATUS, NT_FPREGSET
}
}
其中 fill_thread_core_info() 专门负责写入线程的相关信息,其中就包括寄存器:
static int fill_thread_core_info(struct elf_thread_core_info *t,
const struct user_regset_view *view,
long signr,
struct elf_note_info *info)
{
// 写入通用寄存器
regset_get(t->task, &view->regsets[0],
sizeof(t->prstatus.pr_reg),
&t->prstatus.pr_reg);
fill_note(&t->notes[0], "CORE", NT_PRSTATUS,
sizeof(t->prstatus), &t->prstatus);
// 写入浮点寄存器、扩展寄存器
for (view_iter = 1; view_iter < view->n; ++view_iter) {
const struct user_regset *regset = &view->regsets[view_iter];
if (regset->core_note_type)
regset_get_alloc(t->task, regset, ~0U, &data);
fill_note(...);
}
}
这正是 gdb 能够在 info registers 中显示寄存器值的原因。
4.2 填充 LOAD 段
LOAD 段就是「内存快照」:
......
for (i = 0; i < cprm->vma_count; i++) {
struct core_vma_metadata *meta = cprm->vma_meta + i;
struct elf_phdr phdr;
phdr.p_type = PT_LOAD;
phdr.p_vaddr = meta->start;
phdr.p_filesz = meta->dump_size;
phdr.p_memsz = meta->end - meta->start;
......
// 实际写入内存内容
dump_user_range(cprm, meta->start, meta->dump_size);
}
当 gdb 执行 p variable 时,它会根据调试信息定位变量地址,再到对应的 PT_LOAD 段读取值。
5. 总结
在本文中,我们通过实验和源码分析,完整走了一遍 “如何利用 coredump 调试程序崩溃” 的流程:
-
实验
- 使用
gdb program core即可重现崩溃现场。 bt、info locals、info registers等命令帮助定位问题。
- 使用
-
ELF 结构
- core 文件本质是 ELF,包含 NOTE 段和 LOAD 段。
- NOTE 段保存寄存器、信号、线程信息。
- LOAD 段保存内存快照(代码段、堆、栈等)。
-
内核源码
elf_core_dump()调用fill_note_info()写入 NOTE 段。- 遍历 VMA,写入 PT_LOAD 段。
-
调试器原理
- gdb 读取 NOTE 段,恢复寄存器和信号状态。
- gdb 结合 LOAD 段和 DWARF 调试信息,重建调用栈和变量信息。
一句话总结:
coredump 是进程在崩溃瞬间的“冻结快照”,NOTE 段提供状态,LOAD 段提供内存,调试器则负责把这些还原成人类可理解的源码现场。

浙公网安备 33010602011771号