Kdump 原理探秘

韩 平天, 李 骅宸, 和 王 生辉
2012 年 10 月 11 日发布

WeiboGoogle+用电子邮件发送本页面

0

系列内容:

此内容是该系列 3 部分中的第 3 部分: 深入探索 Kdump

Kdump 实现的基本原理

Kdump 的实现可以分为两个部分:内核和用户工具。内核提供机制,用户工具在这些机制上实现各种转储策略。内核机制对用户工具的接口是一个系统调用:kexec_load(),它被用于加载捕获内核和传递一些相关信息。捕获内核启动后,会像一般内核一样,去运行为它创建的 ramdisk 上的 init 程序。而各种转储机制都可以事先在 init 中实现。为了在生产内核崩溃时能顺利启动捕获内核,捕获内核(以及它的 ramdisk)是事先放到生产内核的内存中的。而捕获内核启动后需要使用的一小部分内存是通过 crashkernel=Y@X 这一内核参数在生产内核中保存的。为了生产内核的内存不被捕获内核启动时破坏,同时省去额外编译一个内核用作捕获内核的麻烦,kerenl 又实现了可重定位内核(relocatable kernel)技术。

生产内核的内存是通过 /proc/vmcore 这个文件交给捕获内核的。为了生成它,用户工具先在生产内核中分析出内存的使用和分布等情况,然后把这些信息综合起来 生成一个 ELF 文件头保存起来。捕获内核被引导时会被同时传递这个 ELF 文件头的地址,通过分析它,捕获内核就可以生成出 /proc/vmcore。有了 /proc/vmcore 这个文件,捕获内核的 ramdisk 中的脚本就可以通过通常的文件读写和网络来实现 各种策略了。同时 kdump 的用户工具还提供了缩减内存镜像尺寸的工具。这就是 Kdump 的基本设计。

Kexec 详解

用户空间工具

kdump 的很大一部分工作都是在用户空间内完成的。与 kexec 相关的集中在一个叫“kexec-tools”的工具中的“kexec”程序中。该程序主要是为调用 kexec_load() 收集各种信息,然后调用之。这些信息主要包括 purgatory 的入口地址,还有一组由 struct kexec_segment 描述的信息,该结构体定义为 :

1
2
3
4
5
6
struct kexec_segment {
        const void *buf;
        size_t bufsz;
        const void *mem;
        size_t memsz;
};

kernel 系统调用

kexec 在 kernel 里以一个系统调用 kexec_load() 的形式提供给用户。这个系统调用主要用来把另一个内核和其 ramdisk 加载到当前内核中。在 kdump 中,捕获内核只能使用事先预留的一小段内存。生产内核的内存镜像会被以 /proc/vmcore 的形式提供给用户。这是一个 ELF 格式的方件,它的头是由用户空间工具 kexec 生成并传递来的。在系统崩溃时,系统最后会调用 machine_kexec()。这通常是一个硬件相关的函数。它会引导捕获内核,从而完成 kdump 的过程。

kdump 内存处理

用于捕获内核的内存

生产内核分析 cmdline 中的 crashkernel 参数后,调用 reserve_crashkernel() 来为捕获内核保存一段内存。这是一个 arch-dependent 的 function。保存之后,在 powerpc 上可以从 /proc/device-trees/chosen/linux,crashkernel-size 和 /proc/device-trees/chose/linux,crashkernel-base 中得到大小及位置信息。

捕获内核如何为 dump 工具提供生产内核的内存镜像

/proc/vmcore ELF 格式

kexec 在加载捕获内核时,会计算并生成一个 ELF 文件头。这个 ELF 头含有生产内核的内存位置等等一系列信息。这个 ELF 头连同其他信息一起保存在由 reserve_crashkernel() 保留的那段内存中。当崩溃发生时,此 ELF 头的位置会被传给捕获内核,由它生成 /proc/vmcore 以供 保存。

/dev/oldmem raw 格式

captured kernel 启动后,还会用 raw 格式通过 /dev/oldmem 来提供生产内核的内存。用户态的工具可能要自己提取其中的 ELF header 以便得到 vmcore。但它的好处是可以只提取 vmcore 的一部分而不用 dump 出全部 vmcore。

可重定位内核(relocatable kernel)

可重定位内核的意义

在 kdump 出现之前,内核只能从一个固定的物理地址上启动。这对 kdump 来说是一种限制。因为为了收集生产内核的内存镜像,捕获内核不能从生产内核使用的启动地址上启动。因此就需要另编译一个从一个不同的地址启动的内核来作捕获内核。这就是为什么 RHEL5 中有一个包叫 kernel-kdump 的原因。技术的创新往往来自对方便的追求。开发人员为了不用费心多编译一个内核,为 kernel 实现了“可重定向”这个特性。

实现原理

x86_64: 运行时修改 text 段及 data 段的眏射

kernel 在启动以后,会检测自己被加载到了什么位置。然后根据这个来更新自己的内存页表以反映 kernel 的 text 段和 data 段中虚拟地址与物理地址之间正确的映射关系。

i386: 使用预先生成的重定位信息

i386 中的 text 和 data 段是已经写死的线性映射区的一部分,要想使用修改页表的办法支持重定向是比较困难的。于是在编译内核时,另生成一份所有需要重定位的 symbol 的位置信息,放进 bzimage 格式的内核中。内核启动解压缩后,根据加载的地址和这份表来时行重定位。

powerpc: 将 vmlinuz 链接为“position-independent executable”形式

与 x86 体系不同,在 powerpc 体系中,/boot/vmlinuz 并不是一个 bzimage 格式的文件,它就是一个 ELF 格式的文件,而且启动机理也不尽相同。因此,在 powerpc 上主要是利用了“位置无关可执行”格式这一成熟技术来实现可重定位。

makedumpfile 简介

有些服务器有着超大的内存,可能比它的硬盘的容量还大。为了转储这样的内存镜像,就有了 makedumpfile,它的最主要的用途就是减少转储的内存镜像的体积。它有两个手段达到这个目的:页面过滤和页面压缩。

页面过滤

makedumpfile 在处理 /proc/vmcore 时,能够过滤掉这样一些内存页:

  • 全是 0 的页
  • 缓存页
  • 用户进程页
  • 空闲页

这些类型的页往往是无关紧要的。通过“-d”这个选项指定一个过滤等级来去掉相应的页,如果要去掉所有这些类型,需要指定“31”。

页面压缩

makedumpfile 可以逐页地压缩 vmcore 中的内存页,在事后的分析中,crash 可以在分析到某页时才将其解压缩。

神秘的 purgatory

细心的 kdump 用户可能注意过在 kdump 刚开始运行时 console 上会出现这样一句话:“I'm in purgatory.”。这就是进入了 purgatory 时的提示。简单说,purgatory 就是一个 bootloader,一个为 kdump 定作的 boot loader。它被赋予了这样一个古怪的名字应该只是一种调侃。实事上,与其说是内核可以引导另一个内核,不如说是 purgatory 可以引导一个内核。 下面的分析完全基于 ppc64。

purgatory 和 kexec

在特定体系架构上编译 kexec 时,purgatory 会从相应特定体系的源码生成。它是一个 ELF 格式的 relocatable 文件。为了使用上的方 便,它被一个工具,“bin-to-hex”,翻译成一个数组并放在 kexec/purgatory.c 里。这样 kexec 的代码就可以直接使用它了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// kexec/purgatory.c
#iclude <stddef.h>
const char purgatory[] = {
0x7f, 0x45, 0x4c, 0x46, 0x02, 0x02, 0x01, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x01, 0x00, 0x15, 0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x0c,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xba, 0x48,
0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00,
0x00, 0x00, 0x00, 0x40, 0x00, 0x20, 0x00, 0x1d,
... ...
0x79, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x00,
};
size_t purgatory_size = sizeof(purgatory);
// kexec/arch/ppc64/kexec-elf-ppc64.c 中分读入 purgatory:
/* Add v2wrap to the current image */
elf_rel_build_load(info, &info->rhdr, purgatory,
             purgatory_size, 0, max_addr, 1, 0);

在通过 kexec_load() 将 purgatory 传给 kernel 之前,kexec 还会对已经读入的 purgatory 进行一些改造,存进一些在引导 内核时必需的信息,如新内核在内存中的地址、device tree blob 的地址等等。值得注意的是,kexec 用捕获内核的前 256 个字节覆盖从 purgatory 入口点开始的 256 个字节(在 ppc64 上刚好是 64 条汇编指令,覆盖完成后,kexec 会把第一个指令恢复成 purgatory 自己原来的)。这一部分有两个功能:标明 kenrel 是否是 relocatable 的和让 slave cpu 等待 primary cpu。

purgatory 和 kernel

kernel 崩溃了,如果 kexec_load() 加载了捕获内核,它会先让没有发生崩溃的 cpu(slave cpu)通过调用 kexec_wait 进入等待:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
_GLOBAL(kexec_wait)
           bl      1f
    1:      mflr    r5
           addi    r5,r5,kexec_flag-1b    
    
    99:     HMT_LOW
    #ifdef CONFIG_KEXEC             /* use no memory without kexec */
           lwz     r4,0(r5)
           cmpwi   0,r4,0
           bnea    0x60
    #endif 
           b       99b
            
    /* this can be in text because we won't change it until we are
    * running in real anyways
    */
    kexec_flag:
           .long   0

这里 kexec_wait 去检查“kexec_flag”的值(初始值是 0),如果是 0 则回到“99:”继续检查;如果不是 0 了,就跳到 0x60 处。这样发生崩溃的 cpu 可以从容地完成一些工作,例如把在 kexec 中得到的捕获内核的前 15 条指令和自己的第一条指令拷到内存的起点等等,再让 slave cpu 跳到 0x60 去等待 primary cpu 完成启动,这是在 kexec_sequence 中完成的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
... ...
    /* copy dest pages, flush whole dest image */
       mr      r3,r29
       bl      .kexec_copy_flush       /* (image) */
       /* turn off mmu */
       bl      real_mode
       /* copy  0x100 bytes starting at start to 0 */
       li      r3,0
       mr      r4,r30          /* start, aka phys mem offset */
       li      r5,0x100
       li      r6,0
       bl      .copy_and_flush /* (dest, src, copy limit, start offset) */
1:      /* assume normal blr return */
       /* release other cpus to the new kernel secondary start at 0x60 */
       mflr    r5
       li      r6,1
       stw     r6,kexec_flag-1b(5)
    ... ...

bl 会把“1:”的地址存入 LR,因此 r5 中被 mflr 存入的是这个地址,而“kexec_flag-1b(5)”就是 kexec_flag 的地址了。stw 向这个地址存入了“1”。这都是在前面的拷贝和关闭 MMU 等完成之后了才做的。在 kexec_sequence 最后,将 purgatory 的入口地址存入 CTR,然后调用 bctrl 从而调用 purgatory。purgatory 则利用 kexec 中存入的一系列信息,最终启动了捕获内核。

作为 bootloader 的 purgatory

虽然 kexec 用捕获内核开头的 256 个字节覆盖了 purgatory 入口处的 256 个字节,但保留了入口的第一条指令,即 b master。于是 purgatory 会跳转到“master”,作一些校验工作;加载一系列自己正常运行和引导内核必不可少的信息,如内核(捕获内核)在内存中的位 置、device tree blob 的位置等。这些都是在 kexec 中生成、存放好了,然后写到 purgatory 的 ELF 格式中,同时也由 kexec_load() 传递给了生产内核的,下面是引导内核前的代码片段:

1
2
3
4
5
6
7
8
9
10
11
12
13
80:
       LOADADDR(6,kernel)
       ld      4,0(6)          # load the kernel address
       LOADADDR(6,run_at_load) # the load flag
       lwz     7,0(6)          # possibly patched by kexec-elf-ppc64
       stw     7,0x5c(4)       # and patch it into the kernel
       li      5,0             # r5 will be 0 for kernel
       mtctr   4               # prepare branch too
       mr      3,16            # restore dt address
                               # skip cache flush, do we care?
       bctr                    # start kernel

此处,LOADADDR 是一个宏,它是一组 5 个的 ppc64 向寄存器加载 64 位即时数的专用指令。这里先把内核的保存位置载入寄存器 4,然后把 purgatory 中 run_at_load 处的值写入从该位置往后位移 0x5c 的地方。这个 run_at_load 是在被 kexec 用捕获内核覆盖的那部分中,kexec 在覆盖这部分时还作了一件事,就是检查捕获内核是否是可重定位的。如果是,就在这个位置上写入 1。因此,如果捕获内核是可重定位的内核,那么在 purgatory 引导它之前,距它开头 0x5c 处的值是 1。这个值告诉捕获内核它应该从它被加载的位置启动。接下来,将捕获内核的地址写入 CTR,用 bctr 来启动它。

kdump 实用小技巧

如何设定 crashkernel 参数

在 kdump 的配置中,往往困惑于 crashkernel 的设置。“crashkernel=X@Y”,X 应该多大? Y 又应该设在哪里呢?实际我们 可以完全省略“@Y”这一部分,这样,kernel 会为我们自动选择一个起始地址。而对于 X 的大小,般对 i386/x86_64 的系统, 设为 128M 即可;对于 powerpc 的系统,则要设为 256M。rhel6 引入的“auto”已经要被放弃了,代之以原来就有的如下语法:

1
2
3
4
5
6
7
8
crashkernel=<range1>:<size1>[,<range2>:<size2>,...][@offset]
             range=start-[end]
             'start' is inclusive and 'end' is exclusive.
             For example:
             crashkernel=512M-2G:64M,2G-:128M

如何判断捕获内核是否加载

可通过查看 /sys/kernel/kexec_crash_loaded 的值。“1”为已经加载,“0”为还未加载。

缩小 crashkernel

可以通过向 /sys/kernel/kexec_crash_size 中输入一个比其原值小的数来缩小甚至完全释放 crashkernel。

kdump 相关新技术

kexec 原来只是用于内核的快速启动,但很快被用来实现内存转储,成为了企业级的的重要应用。但是创新的步伐 并未就此停止。

系统休眠

使用 kexec/kdump 来实现系统休眠(hibernation)已经进行了几年了。目前的状态不得而知,但这种思路上的 创新很让人眼前一亮。目前只支持 x86 体系。相关内容参见最后的链接。

小结

kdump 是目前最有效的 linux™ 内存镜像收集机制,广泛应用于各大 linux™ 厂商的各种产品中,在 debug 内核方面起着不可替换的重要作用。本文着重于深入探索 kdump 的实现机制,希望能让读者通过了解细节从而促进对 kdump 使用的掌握。

相关主题

  • 参考 kexec-tools 的开发站点。
  • 参考 linux kernel 的开发站点。
  • 参考 developerWorks 上文章: PowerPC 汇编简介。
  • 参考 Kexec Hibernation Progress。
  • 在 developerWorks Linux 专区 寻找为 Linux 开发人员(包括 Linux 新手入门)准备的更多参考资料,查阅我们 最受欢迎的文章和教程。
  • 在 developerWorks 上查阅所有 Linux 技巧 和 Linux 教程。
  • 随时关注 developerWorks 技术活动和网络广播。

深入探索 Kdump,第 3 部分相关推荐

  1. 深入探索 Kdump,第 3 部分: Kdump 原理探秘

    Kdump 实现的基本原理 Kdump 的实现可以分为两个部分:内核和用户工具.内核提供机制,用户工具在这些机制上实现各种转储策略.内核机制对用户工具的接口是一个系统调用:kexec_load(),它 ...

  2. Linux内核调试方法总结【转】

    转自:http://my.oschina.net/fgq611/blog/113249 内核开发比用户空间开发更难的一个因素就是内核调试艰难.内核错误往往会导致系统宕机,很难保留出错时的现场.调试内核 ...

  3. Linux内核调试方法总结

    [转]Linux内核调试方法总结 目录[-] 一  调试前的准备 二  内核中的bug 三  内核调试配置选项 1  内核配置 2  调试原子操作 四  引发bug并打印信息 1  BUG()和BUG ...

  4. IBM developerWorks 技术主题 Linux 文档库

    //===================================================================== //========================== ...

  5. arm linux kdump,linux系统奔溃之vmcore:kdump 的亲密战友 crash

    crash 是目前广泛使用的 linux 内核崩溃转储文件的分析工具,掌握 crash 的使用技巧,对于分析定位内核崩溃的问题,有着非常重要的作用.本文首先介绍了 crash 的基本概念和安装方法,其 ...

  6. 探索 TVM 进行量化方法

    探索 TVM 进行量化方法 Relay框架 如上图所示,有两种不同的并行工作正在进行中 • 自动整数量化 - 采用 FP32 框架图,在 Relay 中自动转换为 Int8. • 接受预量化整数模型 ...

  7. 自主数据类型:在TVM中启用自定义数据类型探索

    自主数据类型:在TVM中启用自定义数据类型探索 介绍 在设计加速器时,一个重要的决定是如何在硬件中近似地表示实数.这个问题有一个长期的行业标准解决方案:IEEE 754浮点标准.1.然而,当试图通过构 ...

  8. 2021年大数据ELK(二十六):探索数据(Discovery)

    全网最详细的大数据ELK文章系列,强烈建议收藏加关注! 新文章都已经列出历史文章目录,帮助大家回顾前面的知识重点. 目录 探索数据(Discovery) 一.使用探索数据功能 二.导入更多的Apach ...

  9. Python数据挖掘:数据探索,数据清洗,异常值处理

    来源:天善智能韦玮老师 课堂笔记 作者:Dust 探索性数据分析Exploratory Data Analysis,EDA 数据探索的核心是︰ 1.数据质量分析(跟数据清洗密切联系) 2.数据特征分析 ...

最新文章

  1. python创建scrapy_Python爬虫教程-31-创建 Scrapy 爬虫框架项目
  2. 【青少年编程】【四级】绘制花瓣
  3. Android 自定义ProgressDialog
  4. 轻量级web富文本框——wangEditor使用手册(2)——扩展一个“缩进”功能 demo
  5. Android中Intent/Bundle的通信原理及大小限制(Parcelable原理及与Serializable的区别)
  6. 大剑无锋之一句话输出表中每一个同学的成绩最高的课程【面试推荐】
  7. 国家开放大学2021春1098中学数学教学研究题目
  8. sharedPreferences的用法
  9. git Please move or remove them before you can merge. 错误解决方案
  10. mingw64+msys2下使用cmake问题
  11. 洛谷 P2590 BZOJ 1036 [ZJOI2008]树的统计
  12. FAL风控培训「六大场景下,模型分数如何应用?」
  13. 原生js实现一个随机点餐的小效果
  14. GPT-J 自然语言处理 AI 模型
  15. Linux bz2 解压命令
  16. ESP32 AT指令集 BT SPP测试
  17. 杭电ACM-LCY算法进阶培训班-专题训练(Hash及其应用)
  18. 微信小程序实现路线规划demo
  19. 用户需求变更和确认的注意事项
  20. mac命令行更新gradle

热门文章

  1. 34. 应用监控【监控端点配置】
  2. java计算机毕业设计高速公路服务区管理系统源程序+mysql+系统+lw文档+远程调试
  3. java圆形矩形直线文字设计图_如何设计圆形文字logo?怎么让文字按圆形走?圆形文字logo...
  4. 【金猿人物展】凯捷咨询史凯:未来要充分利用数据要素和数字化生产力打造企业升维新优势...
  5. 苏州源特VPS8701B 微功率隔离电源专用芯片6-30VIN/30V/0.3A 功率管
  6. AnyDesk(远程控制软件)中文版
  7. havc是什么意思_HAVC(havc什么意思)
  8. Zero-DCE 论文阅读笔记
  9. 华中首家美高梅酒店落地武汉;洲际酒店集团与蚂蚁集团达成合作 | 美通企业日报...
  10. Flash Memory/SSD的基础知识