返回全部文章
IMPLEMENTATION / 03虚拟化 · 稳定性

minidump 支持

围绕 Guest Panic 场景,给出 vmcore 过滤、压缩、内核符号准备与不同内存规格下的实测数据。

1. 前言

我们希望在 guest panic 时自动打 coredump,但是现有 coredump 文件太大,与当前 VM 的规格相同,希望通过 minidump 技术减小 vmcore 文件的大小。

2. 前期分析

2.1 现有工具的 minidump 调研

kdump

kdump 是提供崩溃转储机制的服务。该服务可以保存系统内存内容进行分析。

kdump 使用 kexec 系统调用在不重启的情况下引导至第二个内核,然后捕获崩溃内核的内存的内容将其保存到文件中。这个第二个内核位于系统内存保留的一部分。

kdump 内核崩溃转储指南

virsh dump

virsh dump是 libvirt的一个coredump 工具。virsh 会向 libvirt 发送 API 请求,libvirt 调用QEMU执行dump-guest-memory 命令开始实际的内存拷贝工作。

libvirt 通过调用 makedumpfile工具智能分析 vmcore 文件,实现过滤和压缩 vmcore 文件。

virsh dump指令

使用 virsh dump创建 guest 虚拟机内核的转储文件

2.2 文件大小控制策略

内存过滤

makedumpfile使用 5 个 bit 位来设置过滤 5 种内存类型:

选项描述
1(0x00001)零页
2(0x00010)缓存页
4(0x00100)缓存私有页
8(0x01000)用户页
16(0x10000)可用页
文件压缩

virtio dump 提供了多种压缩格式:

您可以使用 --format=格式 选项保存只内存转储。可用的格式如下:

ELF - 默认未压缩格式

kdump-zlib - kdump 压缩格式 zlib 压缩

kdump-lzo - kdump 压缩格式使用 LZO 压缩

kdump-snappy - kdump 压缩格式使用 Snappy 压缩

使用压缩后只能使用 crash工具分析(默认格式),要使用 GDB 分析需指定格式为 ELF。

2.3 makedumpfile 原理

上述两个工具对 vmcore 文件减小文件体积的处理都是使用makedumpfile,这里简单介绍一下原理。

a. 读取 coredump 和 vmlinux 数据

makedumpfile 首先解析coredump文件头,了解内存布局。

makedumpfile 解析 vmlinux 的内核符号地址(例如内核代码段地址/全局变量地址),数据结构定义(struct page/pg_data_t/zone/mem_map)以及内核配置(PAGE_SIZE/MAX_PHYSMEM_BITS)

b. 建立内存位图

初始化位图:开始时,位图被初始化,标记所有内存页为“待移除”。

标记基础保留页,其中包括 kernel 的代码段/静态数据段/栈段/dmesg buffer/其他kernel 数据结构

c. 遍历和过滤内存页

根据用户指定的转储级别 (-d level) 来决定过滤策略。转储级别是一个 4 bit 位的整数,其中每一位都代表一种类型的页面是否要被排除。

这里分为几个部分:

  • 识别空闲页 (Zero Pages):这是最基础的过滤。makedumpfile 会逐页读取 vmcore 源,如果一个页面的所有字节都是零,就在位图中将该页标记为“移除”。这是 -d 1 的功能。

  • 遍历页帧数组 (mem_map):为了进行更高级的过滤,makedumpfile 必须模拟内核的 page 管理机制。它会定位到内核的 mem_map 数组(在较新的内核中,这是通过 pg_data_t 结构体链表来管理的)。这个数组包含了描述每一个物理页帧状态的 struct page 结构体。

  • 分析 struct page:通过检查每个 struct page 结构体的标志位 (flags) 和引用计数 (_refcount),makedumpfile 可以判断出这个页的类型:

    • 缓存页 (Cache Pages):如果页面被标记为在 radix tree 或 xarray 中用于文件缓存,并且没有被进程映射(page->mapping != NULL 且 page->_refcount 符合特定条件),则可以被标记为“移除”。这是 -d 2 的功能。
    • 私有缓存页 (Cache Private):同上,但包含了一些私有的、可能非共享的缓存页。这是 -d 4 的功能。
    • 用户态数据页 (User Data Pages):如果页面被识别为属于某个用户进程的私有数据(例如,通过反向页表映射 rmap 信息判断),则可以被标记为“移除”。这是 -d 8 的功能。
    • 空闲页 (Free Pages):通过分析伙伴系统(Buddy Allocator)数据结构,可以直接识别出哪些页面是空闲的,并将它们标记为“移除”。这是 -d 16 的功能。

d. 压缩文件

此时 makedumpfile知道最终输出文件应该包含哪些页,接下来就是生成最终的 vmcore 文件。

  • 读取-过滤-压缩-写入:makedumpfile 会再次从头读取原始的 vmcore 源文件。

    • 对于每一个页,它会查询之前建立的内存位图。
    • 如果位图显示该页需要“保留”就将该页的数据读入缓冲区。
    • 如果位图显示该页需要“移除”就直接跳过不读取该页。
    • 收集到一定数量的“保留”页(形成一个数据块,Block)后,调用指定的压缩库(如 zlib, lzo, snappy)对这个数据块进行压缩。
    • 将压缩后的数据块写入到新的输出文件中。
  • 生成新文件头:输出的文件也是有特定格式的。makedumpfile 会生成一个新的文件头(可以是 kdump 的压缩格式头,也可以是 ELF 格式头),这个头中包含了如何解压和重构内存布局的元数据。分析工具(如 crash)读取这个文件时,会首先解析头部,然后按需解压数据块来还原出分析所需的内存内容。

3. 使用条件

3.1 kernel 符号信息

生成 minidump 需要符号表信息,需要为 vmlinux 开启 CONFIG_DEBUG_INFO 配置,这会导致 vmlinux 文件大小从 50M 增加到 200 M。但 minidump 对 vmlinux 不是强依赖,可以使用 vmliux 生成一份 vmcoreinfo,文件大小为 2K。

部署时可继续使用精简的 vmlinux,同时额外构建一个带 debug_info 的匹配版本,将其导出 minidump 要用的 vmcoreinfo 信息:

CODE
makedumpfile -g vmcoreinfo -x vmlinux

3.2 磁盘占用

生成 minidump 需要先生成完整的 coredump,原始的 coredump 文件大小 > VM 内存规格,在 minidump 生成结束前磁盘瞬时占用会较大,minidump 生成成功后会删除完整的 coredump 文件。

4. 测试

4.1 性能测试:minidump 速度和压缩大小测试

guest 启动后,在内核态和用户态分别申请 1/3 内存并写入非 0 随机值

guest 执行:

CODE
root@ubuntu-fc-uvm:~# insmod mem_occupier.ko # 分配1/3的内核态内存
root@ubuntu-fc-uvm:~# ./user_memset &        # 分配1/3的用户态内存
root@ubuntu-fc-uvm:~# 

不同规格下的测试

规格过滤页不压缩snappyzstd
文件大小压缩率耗时文件大小压缩率耗时文件大小压缩率耗时
2C1G<br/><br/>原始 vmcore 文件1.1G零页780M29.09%0.613s704M36%0.815s690M37.27%1.719s
缓存页999M9.18%0.672s699M36.45%0.752s678M38.36%1.640s
缓存私有页997M9.36%0.683s699M36.45%0.744s678M38.36%1.634s
用户页695M36.81%0.466s386M64.91%0.552s362M67.09%1.219s
可用页790M28.18%0.552s703M36.09%0.722s689M37.36%1.641s
以上全部411M62.64%0.281s356M67.64%0.390s347M68.45%0.883s
规格过滤页不压缩snappyzstd
文件大小压缩率耗时文件大小压缩率耗时文件大小压缩率耗时
2C4G<br/><br/>原始 vmcore 文件 4.1G零页2.8G31.71%2.319s2.7G34.15%2.741s2.7G34.15%5.633s
缓存页4.0G2.44%2.718s2.7G34.15%2.719s2.7G34.15%5.915s
缓存私有页4.0G2.44%2.685s2.7G34.15%2.744s2.7G34.15%5.918s
用户页2.0G51.22%1.860s1.5G63.41%1.812s1.4G65.85%3.856s
可用页2.9G29.27%1.920s2.7G34.15%2.269s2.7G34.15%5.259s
以上全部1.5G63.41%0.995s1.4G65.85%1.228s1.4G65.85%2.749s
规格过滤页不压缩snappyzstd
文件大小压缩率耗时文件大小压缩率耗时文件大小压缩率耗时
2C16G<br/><br/>原始 vmcore 文件 17G零页11G35.29%8.945s11G35.29%9.901s11G35.29%20.685s
缓存页17G010.362s11G35.29%10.065s11G35.29%22.450s
缓存私有页17G010.481s11G35.29%10.159s11G35.29%22.523s
用户页11G35.29%23.050s5.7G66.47%20.300s5.4G68.24%39.952s
可用页11G35.29%45.403s11G35.29%47.024s11G35.29%13.297s
以上全部5.7G66.47%3.641s5.4G68.24%4.345s5.3G68.82%10.142s

4.2 功能测试:panic测试

guest 内执行:

CODE
echo c > /proc/sysrq-trigger

调试信息:

CODE
crash> bt 1277
PID: 1277     TASK: ffff8880047e4200  CPU: 1    COMMAND: "bash"
 #0 [ffffc90000b2fd20] panic at ffffffff8116df49
 #1 [ffffc90000b2fda0] sysrq_handle_crash at ffffffff817ef2c9
 #2 [ffffc90000b2fdb0] __handle_sysrq at ffffffff817ef8c8
 #3 [ffffc90000b2fde8] write_sysrq_trigger at ffffffff817efef7
 #4 [ffffc90000b2fe18] proc_reg_write at ffffffff81506afe
 #5 [ffffc90000b2fe38] vfs_write at ffffffff81468edf
 #6 [ffffc90000b2fed8] ksys_write at ffffffff814693cb
 #7 [ffffc90000b2ff18] __x64_sys_write at ffffffff8146947e
 #8 [ffffc90000b2ff28] x64_sys_call at ffffffff81008d45
 #9 [ffffc90000b2ff38] do_syscall_64 at ffffffff81af84bb
#10 [ffffc90000b2ff50] entry_SYSCALL_64_after_hwframe at ffffffff81c0012f
    RIP: 00007f91bc114887  RSP: 00007ffcc392c4e8  RFLAGS: 00000246
    RAX: ffffffffffffffda  RBX: 0000000000000002  RCX: 00007f91bc114887
    RDX: 0000000000000002  RSI: 0000560587070b40  RDI: 0000000000000001
    RBP: 0000560587070b40   R8: 0000000000000000   R9: 0000560587070b40
    R10: 0000000000000077  R11: 0000000000000246  R12: 0000000000000002
    R13: 00007f91bc21b780  R14: 00007f91bc217600  R15: 00007f91bc216a00
    ORIG_RAX: 0000000000000001  CS: 0033  SS: 002b
crash> log
.......
[    0.337292] systemd-journald[766]: Received client request to flush runtime journal.
[    0.431148] virtio_net virtio1 ens2: renamed from eth0 (while UP)
[    9.507282] sysrq: Trigger a crash
[    9.508049] Kernel panic - not syncing: sysrq triggered crash
[    9.509971] Kernel Offset: disabled
[    9.510760] ---[ end Kernel panic - not syncing: sysrq triggered crash ]---

4.3 功能测试:soft lockup 测试

guest 内执行:

CODE
insmod soft_lockup.ko

调试信息:

CODE
crash> bt
PID: 220      TASK: ffff8881365dc000  CPU: 1    COMMAND: "insmod"
 #0 [ffffc900000a8e48] panic at ffffffff81a8ff0d
 #1 [ffffc900000a8ec8] watchdog_timer_fn.cold at ffffffff81a96d57
 #2 [ffffc900000a8ef8] __hrtimer_run_queues at ffffffff8110331c
 #3 [ffffc900000a8f60] hrtimer_run_queues at ffffffff81103fcc
 #4 [ffffc900000a8f80] update_process_times at ffffffff8110264d
 #5 [ffffc900000a8f98] tick_sched_handle at ffffffff811147c2
 #6 [ffffc900000a8fb0] tick_nohz_handler at ffffffff81114ba2
 #7 [ffffc900000a8fd8] smp_apic_timer_interrupt at ffffffff81c0273a
 #8 [ffffc900000a8ff0] apic_timer_interrupt at ffffffff81c01c2f
--- <IRQ stack> ---
 #9 [ffffc90000743d28] apic_timer_interrupt at ffffffff81c01c2f
    [exception RIP: delay_tsc+53]
    RIP: ffffffff81a85b75  RSP: ffffc90000743dd8  RFLAGS: 00000246
    RAX: 00000000b19e1c84  RBX: ffffffffa006c000  RCX: 0000000000000001
    RDX: 0000000000000060  RSI: 0000000000000001  RDI: 00000060b19e1ad0
    RBP: 0000000000000000   R8: 0000000000000001   R9: 0000000000006979
    R10: 0000000000000001  R11: 0000000000000000  R12: ffffc90000743e88
    R13: 0000000000000003  R14: 0000000000000000  R15: 0000000000000000
    ORIG_RAX: ffffffffffffff13  CS: 0010  SS: 0018
#10 [ffffc90000743de0] do_one_initcall at ffffffff81000e26
#11 [ffffc90000743e50] do_init_module at ffffffff811217ca
#12 [ffffc90000743e70] __do_sys_finit_module at ffffffff811230d5
#13 [ffffc90000743f38] do_syscall_64 at ffffffff81002465
#14 [ffffc90000743f50] entry_SYSCALL_64_after_hwframe at ffffffff81c000a4
    RIP: 00007fb61adf588d  RSP: 00007fff21b066a8  RFLAGS: 00000246
    RAX: ffffffffffffffda  RBX: 000055f102ecd480  RCX: 00007fb61adf588d
    RDX: 0000000000000000  RSI: 000055f10148ecd2  RDI: 0000000000000003
    RBP: 0000000000000000   R8: 0000000000000000   R9: 0000000000000000
    R10: 0000000000000003  R11: 0000000000000246  R12: 000055f10148ecd2
    R13: 000055f102ecc3f0  R14: 000055f10148d888  R15: 000055f102ecd590
    ORIG_RAX: 0000000000000139  CS: 0033  SS: 002b

小结

基于 kernel 的符号表信息,minidump 可以过滤 VM 内存中的零页/空闲页/用户页以及压缩算法来显著减少 core 文件大小,压缩算法会导致dump 耗时增加 50% 以上但文件压缩较小暂不启用。

附录

soft_lockup.c:
CODE
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/delay.h>

static int __init soft_lockup_init(void)
{
    printk(KERN_INFO "soft_lockup: Module loaded.\n");
    printk(KERN_INFO "soft_lockup: Starting an infinite loop to trigger a soft lockup...\n");

    // This is the problematic part. The while(1) loop
    // will cause the CPU to be stuck in this kernel thread
    // forever, without yielding to the scheduler or responding
    // to interrupts.
    while (1) {
        // We can add a very small delay to prevent the CPU
        // from overheating, but it won't prevent the soft lockup.
        // It just makes the loop slightly less aggressive.
        udelay(10);
    }

    return 0;
}

static void __exit soft_lockup_exit(void)
{
    // This part will never be reached because of the infinite loop
    printk(KERN_INFO "soft_lockup: Module unloaded.\n");
}

module_init(soft_lockup_init);
module_exit(soft_lockup_exit);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("A kernel module to demonstrate a soft lockup.");