欢迎光临
我们一直在努力

庖丁解CORE · 初见全牛:vmcore前世今生

庖丁解CORE · 第一篇 初见全牛:vmcore 是什么,从哪里来

庖丁始解牛时,所见无非全牛。三年之后,未尝见全牛也。——《庄子·养生主》

在这里插入图片描述
初学者打开一个 vmcore,往往被它的体量震慑——动辄数 GB,文件类型标注 ELF,file 命令返回的是 core file,却和应用程序的 core dump 截然不同。你不知道它从何而来,也不知道里面藏着什么。这就是庖丁最初所见:一头完整的、令人茫然的全牛。

本文将讲清楚三件事:vmcore 是什么、它如何被生成、影响它质量的关键参数与常见故障。文章也顾及了架构差异(x86_64 与 ARM64),会在相关环节单独说明。


一、vmcore 内容及格式

1.1 vmcore 是什么

vmcore 是 Linux 内核在崩溃瞬间的内存快照,以 ELF 格式存储。更准确地说,它是一个经过筛选的内存转储,包含:

  • 内核崩溃时所有 CPU 的寄存器状态(通过 ELF PT_NOTE 段存储)
  • 内核使用的物理内存页(按过滤级别选择性保留)
  • vmcore-dmesg:崩溃前的内核日志缓冲区

它不是:

  • 完整的物理内存镜像(用户态进程内存默认被过滤掉)
  • 应用程序的 core dump(那是单个进程的地址空间快照)
  • 实时系统状态(它是崩溃那一刻的冻结帧,之后的一切都不存在)

1.2 vmcore 的常见存储形式

根据其获取与生成方式、数据过滤程度以及应用场景的不同,业界主要将其划分为以下六大核心类型:

完整内存转储 (Full Dump):最原始、最完整的转储方式。它不经过任何过滤,直接将系统崩溃瞬间的全部物理内存数据全量写入存储。其优点在于保留了百分之百的内存现场(包括所有的用户态、内核态数据及缓存),信息最为完整;缺点是体积庞大(与物理内存大小等同),在数百 GB 或 TB 级大内存服务器上,采集与写盘时间极长。

kdump 压缩转储 (Compressed Dump):目前生产环境最常用的标准格式(通常由 makedumpfile 配合 kdump 生成)。它在捕获阶段对内存数据进行压缩(支持 zlib、lzo、lz4、zstd 等算法),并在保留核心内核元数据的同时过滤掉无关数据,极大地节省了磁盘空间,提升了转储效率。

makedumpfile 过滤转储 (Filtered Dump):通过特定的过滤规则(如 high、low 或自定义级别),有选择地剥离掉对内核调试无用的数据页(例如零页、用户态进程数据、文件缓存等)。这使得生成的 vmcore 体积显著减小,兼顾了快速落盘与核心内核现场的保留。

远程传输转储 (Remote Dump):适用于本地磁盘空间不足或无盘服务器。捕获内核通过网络协议(如 NFS、SSH、Netcat、TFTP 等)将转储数据实时流式传输到远程存储服务器。其不依赖本地存储,但转储成功率受制于网络架构的稳定性。

分区转储 (Partial Dump):在极端或受限场景下,仅捕获指定的一部分物理内存范围,或只读取特定的物理内存区域。其体积极小、捕获极快,但由于丢弃了大量内存数据,无法进行全栈的深度死锁和链路还原。

dmesg / vmcore-dmesg (日志提取):这是一种极其轻量级的"降级转储"。它仅保存并提取内核崩溃前的 dmesg 环形缓冲区日志(Log Buffer)。它几乎不占用空间,在无法生成完整 core 的极端硬件故障场景下,作为纯文本提供最后的线索,但无法进行寄存器、内存块等高级调试。

此外,在虚拟化平台(如 KVM/QEMU、VMware ESXi)上,根据崩溃主体的不同(Host 崩溃、Guest 内部 panic、或者通过宿主机 virsh dump/QEMU Monitor 强行导出的物理镜像),vmcore 还会衍生出虚拟机内核 ELF 镜像和宿主机层面的 Raw 原始镜像等形态。

1.3 ELF 格式的 vmcore 内部结构综述

ELF 格式的 vmcore 是 Linux 标准转储体系(kdump)的核心产物。它严格遵循标准的 64 位 Executable and Linkable Format (ELF64) 规范,将崩溃瞬间的物理内存空间映射、CPU 寄存器状态以及内核元数据,组织成一个标准化、可被 crash、gdb 等工具直接解析的核心转储文件(ET_CORE 类型)。

标准 ELF64 规范的通用度,堪称现代计算底座的"硬通货"。它超越了物理硬件的藩篱,在 x86_64、ARM64、RISC-V 乃至 LoongArch 等截然不同的芯片架构之间,构建了一套高度统一的二进制格式标准。从磁盘上的可执行程序、运行时的动态链接库,到内核动态加载的 .ko 驱动模块,直至系统崩溃瞬间凝固的 vmcore 转储现场,皆由同一套 Elf64_Ehdr 和 Elf64_Phdr 结构体作为其元数据骨架(实际存储时可能包含 makedumpfile 私有头)。这种将"通用的数据外壳"与"异构的体系现场"完美分离的解耦设计,使得 ELF64 成为纵穿操作系统从编译到崩溃、横跨所有主流处理器架构的内核转储通用语言。

其内部拓扑结构由外至内,主要由以下四个关键部件无缝交织构成:

+————————————————————————+
| 1. ELF Header (ELF头部) |
| – 标识魔数(e_ident)、机器架构(e_machine: x86_64/AARCH64)、e_phoff 等 |
+————————————————————————+
| 2. Program Header Table (PHT / 程序头表) |
| – 整个文件的索引总纲,描述并指向所有 PT_NOTE 与 PT_LOAD 段的物理偏移|
+————————————————————————+
| 3. PT_NOTE Segment (元数据元段) |
| +—————————————————————-+ |
| | NT_PRSTATUS : 捕获崩溃瞬间每个 CPU 核心的通用寄存器快照 | |
| | (x86_64: RIP/RSP/CR3; ARM64: PC/SP/TTBR0/1) | |
| +—————————————————————-+ |
| | NT_VMCOREINFO : 存放内核符号(如 init_task)、KASLR 偏移、页表 | |
| | 级数、内核版本等关键调试索引的“黑盒子” | |
| +—————————————————————-+ |
| | NT_AUXV / NT_FILE / NT_TASKSTRUCT 等其他辅助元数据 | |
| +—————————————————————-+ |
+————————————————————————+
| 4. PT_LOAD Segments (物理内存段) |
| – 包含1到N个连续或断续的物理地址映射块,直接映射崩溃瞬间的真实物理内存 |
+————————————————————————+

ELF Header(ELF 头部):位于文件起始位置,固定大小。它定义了文件的基本元数据,包括 ELF 魔数(Magic)、文件类别(64 位)、大端/小端序、目标机器架构(例如 EM_X86_64 或 EM_AARCH64),以及程序头表(PHT)在文件中的偏移量(e_phoff)和数量(e_phnum)。

Program Header Table(PHT / 程序头表):整个文件的"活地图"。它由一系列程序头项(Program Header Entries)组成,不包含具体的物理内存数据,而是精确描述了后续所有段(Segment)的类型、文件偏移(p_offset)、物理/虚拟地址基址(p_paddr/p_vaddr)以及段大小(p_memsz)。

PT_NOTE Segment(元数据段):存放系统关键状态和调试上下文的黄金核心区。它内部通过 Name-Type-Value 的内嵌结构包裹了多个至关重要的 Note 节点:

  • NT_PRSTATUS(进程/核心状态):最为关键。它保存了崩溃瞬间,全机每一个 CPU 核心的实时运行快照,保存寄存器集及少量 task 上下文信息,以及最重要的处理器寄存器状态(Register Sets)。在 x86_64 上表现为 RIP、RSP、CR3(页表基址)等;在 ARM64 上则表现为 PC、SP、PSTATE 以及 TTBR0/TTBR1。这是 crash 工具重建崩溃现场函数调用栈(Backtrace)的唯一数学依据。
  • NT_VMCOREINFO(内核关键元数据):存放内核的核心符号表物理地址(保存内核关键符号、结构偏移、页模型参数等)、内存模型的配置参数(如九级或三级页表)、Linux 内核版本号,以及对抗安全攻击的 KASLR(内核地址空间布局随机化)偏移量(kernel_offset)。调试工具必须读取此节点,才能正确消除地址随机化带来的偏移,把二进制内存反向推演回 C 语言源码。
  • 其他 Note(如 NT_AUXV、NT_FILE、NT_TASKSTRUCT):保存辅助向量、内存映射文件信息等扩展上下文。

PT_LOAD Segments(物理内存段):占据了 vmcore 99% 以上体积的实体内容。每一个 PT_LOAD 段对应一段连续的物理内存银行(Memory Bank)。程序头表中的每一项分别映射到这些段,调试工具通过读取这些段的内容,在应用层虚拟重构出系统崩溃那一刻的全局物理内存全景。
在这里插入图片描述

二、生成机制:kexec + kdump

vmcore 的生成依赖两个机制的协同:kexec(内核执行链)和 kdump(崩溃转储框架)。

2.1 两个内核的世界

Linux 系统正常运行时,内存中实际上准备着两套内核:捕获内核(capture kernel / crash kernel)在系统启动时由 kdump 服务通过 kexec -p 预加载进保留区域,处于待命状态。
在这里插入图片描述

2.2 崩溃触发到 vmcore 的完整路径

① 生产内核触发 panic


② 调用 crash_kexec()
├── 保存当前 CPU 寄存器到 crash_notes 区域
├── 向其他 CPU 发送 IPI/NMI,令其保存寄存器后停机
└── 通过 kexec 机制热切换到捕获内核


③ 捕获内核启动(极简环境,无需完整初始化)
├── 通过 /proc/vmcore 接口访问生产内核的物理内存
└── 捕获内核的内存在保留区域内,与生产内核的内存不重叠


④ makedumpfile 读取 /proc/vmcore
├── 按过滤级别丢弃无关页(零页、用户态页等)
├── 压缩剩余内存页
└── 写入目标路径(本地磁盘 / NFS / SSH 远端)


⑤ 捕获内核重启,系统恢复正常启动

2.3 /proc/vmcore 是桥梁

/proc/vmcore 是捕获内核暴露出的一个特殊虚拟文件,它将生产内核的物理内存映射为可读的 ELF 文件视图。makedumpfile 读它,就像读一个普通文件——但背后是对整个物理内存的访问。


三、vmcore 的产生方式

内核 panic 并非只有一条触发路径。从生产实践来看,vmcore 的来源可以分为四类:内核自发崩溃、看门狗超时、手动触发、虚拟化平台导出。理解这四类来源,在分析时就能判断崩溃是"系统自己倒下的"还是"被外力推倒的"——二者的根因分析思路截然不同。

3.1 内核自发崩溃

这是最常见的 vmcore 来源。内核检测到自身状态异常时,主动调用 panic() 终止运行。主要包含如下几种:

Oops 升级为 panic

内核遇到可恢复错误时产生 Oops(如非法内存访问、除零),默认情况下 Oops 只打印错误信息,不触发 panic。但开启以下参数后,Oops 会直接升级为 panic:

# 查看当前值(0=只打印,1=触发panic)
sysctl kernel.panic_on_oops

# 永久开启(写入 /etc/sysctl.conf)
kernel.panic_on_oops=1

生产环境强烈建议开启此参数。因为 Oops 之后内核状态已不可信,继续运行可能导致数据损坏,且不会留下 vmcore 供分析。

BUG() / BUG_ON() / WARN_ON()

内核代码中的断言宏,触发时的行为(需 panic_on_oops=1):

宏默认行为受参数控制
BUG() 触发 Oops,直接 panic 否,属于必然 panic 行为
BUG_ON(cond) 条件成立时触发 panic 否,属于确定性断言机制
WARN() / WARN_ON() 打印警告,系统继续运行 kernel.panic_on_warn=1 升级

# WARN 升级为 panic(有助于捕获早期异常)
kernel.panic_on_warn=1

空指针解引用(NULL pointer dereference)

访问虚拟地址 0(或极低地址)触发 page fault,内核无法处理后产生 Oops,进而(开启 panic_on_oops 时)触发 panic。这是生产环境中最高频的 vmcore 来源之一。

内核栈溢出

内核默认为每个线程分配 8KB(x86_64)或 16KB(ARM64)的内核栈。栈末尾有 canary 保护字;溢出时 canary 被破坏,内核检测后触发 panic。若开启了 CONFIG_VMAP_STACK(虚拟地址映射栈),溢出会触发硬件 page fault,更早被捕获。

RCU stall

RCU(Read-Copy-Update)是内核的关键同步机制。若某个 CPU 在读侧临界区内停留时间过长(默认 21 秒),内核打印 RCU stall 警告:

# 开启后 RCU stall 直接触发 panic(便于捕获死循环或中断关闭过久的场景)
kernel.panic_on_rcu_stall=1

3.2 看门狗超时触发

内核内置多层看门狗,监控不同粒度的"卡死"状态,触发后产生 panic 并生成 vmcore。

软锁(Soft Lockup)

监控对象:调度器是否在预期时间内运行。默认超时:20 秒内若某 CPU 未调度过其他任务,判定为 soft lockup。

kernel.softlockup_panic=1 # 检测到后触发 panic(默认关闭)
kernel.watchdog_thresh=10 # 超时阈值(秒),soft lockup = 2×此值

硬锁(Hard Lockup)

监控对象:CPU 是否在关中断状态下长时间运行(NMI 看门狗)。默认超时:watchdog_thresh 秒(默认 10 秒)内若 CPU 未响应 NMI,判定为 hard lockup。实现机制:x86_64 使用 PMU(性能计数器)产生 NMI;ARM64 使用 arch timer 产生 FIQ(EL3)。

kernel.hardlockup_panic=1 # 检测到后触发 panic(生产环境建议开启)
kernel.nmi_watchdog=1 # 启用 NMI 看门狗

hung task(D 状态进程)

监控对象:长期处于不可中断睡眠(D 状态)的进程。默认超时:120 秒。常见于 NFS 挂起、存储设备无响应等场景。

kernel.hung_task_panic=1 # D 状态超时后触发 panic
kernel.hung_task_timeout_secs=120

OOM killer

内存耗尽时,OOM killer 选择进程并将其杀死。若开启以下参数,OOM 事件直接升级为 panic:

vm.panic_on_oom=1
# OOM 触发 panic(适合内存泄漏定位)
# 0 = 只杀进程(默认)
# 1 = 内存耗尽时触发 panic
# 2 = 强制 panic(忽略 cpuset/mempolicy 限制)

各类看门狗的超时对比

时间轴(秒)
0────5────10────20────30────..────120
│ │ │
│ │ └── hung_task(D 状态进程,默认 120s)
│ └── soft lockup(默认 20s = 2 × watchdog_thresh)
└── hard lockup(默认 10s = watchdog_thresh)

3.3 手动触发

某些场景下系统处于"活锁"状态——没有触发 panic,但已经失去响应。此时需要人工强制触发 vmcore。

SysRq 触发(最常用)

# 方式一:直接写入(需要 root 权限)
echo c > /proc/sysrq-trigger

# 方式二:键盘(物理或串口控制台)
Alt + SysRq + C

# 需要满足以下前提条件:
# 确认 SysRq 已启用(0=禁用,1=全部启用,或按位组合)
sysctl kernel.sysrq
# 至少需要开启 crash dump 位(bit 7 = 128)
kernel.sysrq = 1

echo c 向内核写入字符 c(crash),内核调用 panic("sysrq triggered crash\\n"),之后走标准 kdump 流程生成 vmcore。这是最干净、最常用的手动触发方式。

NMI 按钮(物理服务器)

部分服务器主板提供物理 NMI 按钮。按下后 CPU 收到 NMI,触发 nmi_panic()。适用于系统完全失去网络响应、SysRq 也无法操作的场景。需要提前开启:

kernel.unknown_nmi_panic=1 # 收到未知 NMI 时触发 panic

IPMI/BMC 注入 NMI(远程触发)

无需物理接触服务器,通过带外管理接口远程触发:

# 通过 ipmitool 发送 NMI(需要 BMC 支持)
ipmitool chassis power diag

# 或通过 Redfish API(现代服务器)
curl -X POST https://<BMC_IP>/redfish/v1/Systems/System.Embedded.1/Actions/ComputerSystem.Reset \\
-d '{"ResetType": "Nmi"}'

收到 NMI 后,若内核配置了 unknown_nmi_panic=1,触发 panic 并生成 vmcore。这是生产环境中处理"系统失联"最常用的手段。

注意:手动触发的 vmcore,crash 报告中 panic 原因会显示为 sysrq triggered crash 或 NMI: IOCK error,而非真实业务错误。分析时要结合崩溃前的 dmesg 日志判断真实根因。

3.4 虚拟化平台的 vmcore 导出

在虚拟化或云环境中,获取 vmcore 的路径有所不同。

KVM/libvirt(virsh dump)

# 在宿主机上导出虚拟机内存快照
virsh dump <domain-name> /tmp/vm.dump –memory-only

# 导出为 kdump 兼容格式
virsh dump <domain-name> /tmp/vm.dump –format kdump-zlib

virsh dump 导出的是虚拟机的物理内存视图,格式与 kdump 生成的 vmcore 兼容,可直接用 crash 分析。但它不依赖虚拟机内部的 kdump 配置,即使虚拟机内部 kdump 未启用也可导出。适用场景:虚拟机内核卡死、无法自动触发 kdump 时,由宿主机介入强制导出。

VMware(内存快照)

VMware 的虚拟机内存快照(.vmem 文件)格式与 ELF vmcore 不同,需要转换工具(如 vmss2core)转为 crash 可读格式后才能分析。

云平台(ECS/EC2 等)

公有云厂商通常提供控制台的"发送 NMI"功能(等同于 IPMI NMI 注入),触发后走虚拟机内部的 kdump 流程,vmcore 写入虚拟机本地磁盘。部分云厂商还支持将 vmcore 直接上传到对象存储(OSS/S3)。

3.5 产生方式汇总与对比

产生方式触发者需要预配置vmcore 完整性典型场景
内核 panic(自发) 内核自身 需 kdump 就绪 空指针、BUG()、栈溢出
soft/hard lockup 看门狗 需开启 panic 参数 死循环、关中断过久
hung task 看门狗 需开启 panic 参数 NFS 挂起、存储无响应
SysRq 运维人员 需 sysrq 启用 系统无响应但 SSH 可达
IPMI/BMC NMI 运维(带外) 需 unknown_nmi_panic 系统完全失联
virsh dump 宿主管理员 无需客户机配置 中(无寄存器) 虚拟机卡死
物理 NMI 按钮 运维(现场) 需 unknown_nmi_panic 现场处理

四、x86_64 架构下的工作细节

4.1 CPU 停机机制

x86_64 上,生产内核 panic 时:

  • 触发 panic 的 CPU(BSP 或任意 CPU)调用 crash_kexec()
  • 通过 APIC IPI(处理器间中断)向所有其他 CPU 发送 REBOOT_VECTOR
  • 其他 CPU 收到中断后执行 crash_nmi_callback(),将自身寄存器保存到 crash_notes(每 CPU 一块区域)后进入 cpu_idle 等待
  • 若某颗 CPU 在规定时间内未响应 IPI,主 CPU 通过 NMI 强制触发

crash_notes 的物理地址通过内核符号 crash_notes 导出,捕获内核启动时从 elfcorehdr= 参数获取这块区域的位置。

APIC 的全称是 Advanced Programmable Interrupt Controller(高级可编程中断控制器)。简单来说,它是现代多核(Multi-core)计算机中负责管理和分发"中断信号"的核心硬件机制。如果把 CPU 的每个核心比作流水线上的工人,那么 APIC 就是那个精密的"任务调度员",决定哪个紧急任务(中断)该发给哪个工人、谁的优先级更高、以及工人之间如何通过暗号(核间中断)协同工作。

4.2 内存布局与 KASLR

x86_64 默认开启 KASLR(内核地址空间随机化)。每次启动时,内核的虚拟地址基址会随机偏移。crash 工具需要通过以下方式确定偏移量:

  • vmcore 的 PT_NOTE 中记录了 KERNELOFFSET
  • crash 工具读取该值后,配合未随机化的符号表(vmlinux)计算真实地址
  • 若 vmlinux 符号地址与 vmcore 中的地址对不上,通常是 KASLR 偏移未被正确处理,而非 vmlinux 版本不匹配

KASLR 的核心逻辑在于:以开机时的动态信息熵,打破黑客对静态内存地址的绝对预测。当开启 KASLR 后,系统每次引导启动(Boot)时,内核的早期引导加载程序(Decompressor)会利用硬件随机数生成器(如 CPU 的 RDRAND 指令)产生一个随机的偏移量(Offset),称为 Entropy(熵值)。内核会利用这个随机偏移量,对核心区域的虚拟地址(有时也包括物理地址)进行整体平移和打乱。

4.3 crashkernel 参数(x86_64)

要保证 kdump 机制正常工作,需要在 GRUB 启动参数中设置 crashkernel 参数。crashkernel 是 Linux 内核启动参数,其核心功能是:为 kdump 机制预留一块物理内存,用于在系统崩溃时运行捕获内核并生成 vmcore。

x86_64 上保留区域默认放在物理内存高端(>4G 处),避免与 DMA 区域冲突。也可显式指定物理地址:

# 方式 1:静态指定大小(不限位置)
crashkernel=256M

# 方式 2:静态指定大小 + 强制指定物理起始地址
crashkernel=256M@2G

# 方式 3:阶梯式自动匹配(根据总内存大小决定预留大小)
crashkernel=512M-2G:64M,2G-8G:128M,8G-:256M

# 方式 4:高级拆分配置(区分高低端内存,常见于大内存服务器)
crashkernel=512M,high
crashkernel=128M,low

关键字由发行版预设规则自动计算:

crashkernel=auto

让系统根据总内存大小,全自动、阶梯式地在物理内存(大内存时会自动区分高/低端)中预留 kdump 空间,优点是免维护、省心,缺点是大小由内核黑盒决定、容易隐形侵占业务内存。

推荐值参考(x86_64):

物理内存总量推荐 crashkernel 大小
< 4 GB 128M
4 ~ 64 GB 256M
64 ~ 1 TB 512M
> 1 TB 1G 或更大

注意:保留区域过小会导致捕获内核无法启动(最常见的 kdump 失败原因之一)。


五、ARM64 架构下的工作细节

5.1 异常级别与 panic 路径

ARM64 使用异常级别(Exception Level)模型:

EL3 — 安全监控(Secure Monitor,固件层)
EL2 — Hypervisor(虚拟化层)
EL1 — 内核(Kernel) ← Linux 运行于此
EL0 — 用户态(User)

内核 panic 发生在 EL1。ARM64 没有 x86_64 的 APIC 机制,CPU 停机通过以下路径实现:

  • PSCI(Power State Coordination Interface):通过 CPU_OFF 调用让其他 CPU 下线,由固件层(EL3)执行
  • IPI via GIC(Generic Interrupt Controller):向其他 CPU 发送处理器间中断,功能类似 x86_64 的 APIC IPI
  • 若 PSCI 不可用(如裸板环境),则使用 smp_send_stop() 发 IPI 后等待 CPU 进入 WFI(Wait For Interrupt)状态

5.2 设备树 vs ACPI

ARM64 平台存在两类固件描述方式,影响 kdump 的配置方式:

设备树(Device Tree)平台(嵌入式、服务器早期):

  • crashkernel= 参数行为与 x86_64 基本一致
  • 捕获内核通过 DTB(设备树二进制)获取硬件信息
  • 需确保捕获内核使用的 DTB 与生产内核一致

ACPI 平台(现代 ARM64 服务器,如鲲鹏、飞腾、Ampere):

  • 行为更接近 x86_64,crashkernel=auto 通常可用
  • 捕获内核通过 ACPI 表发现硬件,无需额外 DTB 配置

注意:区分 ACPI 和 APIC

  • ACPI → 系统配置和硬件发现(“系统长什么样”)
  • APIC → 中断控制和 CPU 通信(“中断怎么发、CPU 怎么聊”)

在 vmcore 生成链路上:

  • ACPI:决定 crashkernel 能不能正常分配(ARM64 更敏感)
  • APIC:决定 panic 时能不能让所有 CPU 停下并保存寄存器(x86_64 特有)

5.3 crashkernel 参数(ARM64)

ARM64 的内存布局限制比 x86_64 更严格:

  • 保留区域必须位于内核映像可寻址的范围内
  • 部分平台要求保留区域对齐到 2MB 边界
  • 高端内存分离写法同样适用:

crashkernel=256M,high
crashkernel=128M,low

ARM64 特有问题:部分平台(尤其是内存超过 256GB 的大型服务器)需要同时指定 high 和 low,否则捕获内核的 DMA 操作会失败:

crashkernel=512M,high
crashkernel=128M,low

推荐值参考(ARM64):

物理内存总量推荐 crashkernel 大小
< 4 GB 128M
4 ~ 64 GB 256M
64 ~ 256 GB 512M
> 256 GB 512M,high + 128M,low

5.4 KASLR on ARM64

ARM64 的 KASLR 实现与 x86_64 类似,但随机化范围由 VA_BITS 决定(通常 48 位或 52 位虚拟地址空间)。处理方式相同:vmcore 的 PT_NOTE 中记录偏移量,crash 工具自动处理。


六、makedumpfile 过滤参数

makedumpfile 是控制 vmcore 体积与质量的核心工具,通过 -d 参数指定过滤级别,参数总共有 32 个过滤级别,数值范围从 0 到 31,下表为部分示例:

级别参数过滤内容典型用途
0 -d 0 不过滤,完整转储 深度调试,体积最大
1 -d 1 过滤零页 轻度压缩
2 -d 2 过滤零页 + 缓存页(非私有) 常规推荐
3 -d 3 + 缓存页(私有) 进阶定制过滤
4 -d 4 + 用户态数据页 默认推荐值
5 -d 5 + 空闲页(buddy system 中的空闲内存) 体积最小
6 -d 6 + Hugepage 特殊巨页场景
31 -d 31 最大过滤,几乎只保留内核关键数据 网络传输场景

生产环境推荐:-d 31 配合压缩,体积可缩小到原始内存的 5%~15%,同时保留足够的内核分析数据。若需要分析用户态进程(如调查某个进程是否触发了内核 bug),则使用 -d 4 或更低级别。

压缩选项:
-c # zlib 压缩(最通用)
-l # lzo 压缩(速度快,CPU 开销低)
-p # 并行压缩(多核加速,推荐)

配置文件(/etc/kdump.conf 或 /etc/sysconfig/kdump)中的典型配置:

core_collector makedumpfile -l –message-level 1 -d 31


七、常见故障与解决方法

7.1 kdump 服务启动失败

现象:systemctl status kdump 显示 failed,或 /sys/kernel/kexec_crash_loaded 值为 0

# 确认保留内存是否生效
cat /proc/cmdline | grep crashkernel
cat /sys/kernel/kexec_crash_size # 应为非零值

# 查看 kdump 日志
journalctl -u kdump -b

常见原因与解法:

原因现象解法
crashkernel 参数未加入启动项 kexec_crash_size 为 0 修改 GRUB 配置文件重启生效
保留内存(Reserved)不足 kdump 日志显示 OOM 报错 增大 crashkernel 分配值
捕获内核(Capture)镜像缺失 日志提示找不到 vmlinuz 确认 kdump 核心包安装完整
SELinux 策略阻止内核加载 触发 AVC denied 日志 setenforce 0 测试或加 policy

7.2 panic 后未生成 vmcore

现象:系统崩溃重启,目标路径下无 vmcore 文件

# 确认目标路径挂载正常(NFS 场景尤其要检查)
cat /etc/kdump.conf | grep ^path
cat /etc/kdump.conf | grep ^nfs

# 检查磁盘空间
df -h /var/crash

# 查看上次崩溃的 kdump 日志
journalctl -b -1 | grep -i kdump

常见原因:

原因解法
目标路径磁盘已满 清理旧 vmcore,或更换存储目标
NFS 挂载在捕获内核中失败 检查网络设备驱动是否在捕获内核 initrd 中
makedumpfile 崩溃(OOM) 减小过滤级别,或增大 crashkernel
目标路径权限不足 确认 root 可写
二次 panic(捕获内核本身崩溃) 查看串口/BMC 日志,通常是驱动兼容问题

7.3 vmcore 生成但无法用 crash 打开

现象:crash vmlinux vmcore 报错退出:crash: cannot resolve "init_task" 或 crash: compressed kdump: uncompress error

常见原因与解法:

错误信息原因解法
cannot resolve “init_task” vmlinux 与 vmcore 内核版本不匹配 用 crash -s 确认版本,找到对应 debuginfo
uncompress error vmcore 损坏或压缩格式不支持 检查 makedumpfile 版本,尝试 -d 0 重新生成
ELF header is corrupted 写入中断(磁盘满、掉电) 检查生成时的错误日志
no debugging data available 缺少 debuginfo/vmlinux 安装对应内核的 debuginfo 包

7.4 x86_64 特有:APIC 超时导致 vmcore 不完整

现象:vmcore 中部分 CPU 的寄存器快照缺失(crash 中 bt -a 某些 CPU 无输出)

原因:panic 发生时,部分 CPU 未在超时时间内响应 IPI,主 CPU 放弃等待直接执行 kexec。

# 内核启动参数增加:
apic=verbose # 开启APIC详细日志,定位卡死CPU
hpet=disable # 某些平台HPET中断干扰IPI响应

# 或在 kdump 配置中允许更长的 CPU 等待时间:
# /etc/kdump.conf
extra_bins /usr/sbin/earlykdump

7.5 ARM64 特有:PSCI 调用失败导致捕获内核卡死

现象:panic 后系统无响应,串口无输出,既不重启也不生成 vmcore

原因:捕获内核尝试通过 PSCI 让其他 CPU 下线时,固件未正确响应。多见于:

  • 早期 ARMv8 平台固件 bug
  • 虚拟化层(EL2)拦截了 PSCI 调用
  • 设备树中 PSCI 描述错误

# 捕获内核启动参数中禁用其他 CPU(单 CPU 模式捕获,牺牲完整性换可靠性)
nr_cpus=1

# 或指定 PSCI 调用方式
psci=force_hvc # 强制使用HVC调用(虚拟化场景)
psci=force_smc # 强制使用SMC调用(裸机场景)

常规的处理方法是通过 /etc/kdump.conf 将参数传递给捕获内核,这种方法以牺牲性能,换取绝对的启动成功率。以此绕过 ARM64 复杂的 PSCI 多核和中断固件 Bug:

kdump_commandline_append="nr_cpus=1 irqpoll"

7.6 vmcore 体积过大,写入超时

现象:捕获内核启动正常,但 makedumpfile 运行时间过长,最终超时或磁盘写满

# 提高过滤级别
core_collector makedumpfile -l –message-level 1 -d 31

# 启用并行压缩(多核机器显著提速)
core_collector makedumpfile -l –message-level 1 -d 31 -p

# 或使用 SSH 实时传输到远端,避免本地磁盘瓶颈
ssh root@remote-host "mkdir -p /var/crash"

对于超大内存机器(>1TB),建议结合 -d 31 和并行压缩,并将 crashkernel 设置到 1G 以上,确保捕获内核本身有足够内存运行 makedumpfile。


八、小结:初见全牛的地图

看完本篇,你对 vmcore 应当已经建立这样的认知地图:

【系统启动时】
GRUB crashkernel= 参数

└→ 内核保留一块物理内存
└→ kdump 服务将捕获内核加载入保留区(kexec -p)

【panic 发生时】
生产内核触发 panic

└→ 保存各 CPU 寄存器(x86_64: IPI+NMI / ARM64: GIC IPI+PSCI)
└→ kexec 热切换到捕获内核

【捕获内核运行时】
/proc/vmcore 暴露生产内核内存

└→ makedumpfile 读取、过滤、压缩
└→ 写入目标路径 → vmcore 文件

【之后】
crash 工具 + vmlinux → 还原现场

vmcore 就是这条链路的终点产物。它的完整性取决于:保留内存够不够、过滤级别对不对、目标存储来不来得及写完。

下一篇「执刀问器」,我们将配置好分析环境,拿起工具,开始正式面对这牛头。

赞(0)
未经允许不得转载:171主机测评 » 庖丁解CORE · 初见全牛:vmcore前世今生
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址