资讯详情

Linux Kdump 机制详解

作者简介:中年码农做过电信、手机、安全、芯片等行业。Linux混饭吃。

简介

Kdump 当内核出现故障时,系统的所有内存信息和寄存器信息都提供了一种机制 dump 出来成文件,后续通过 gdb/crash 分析和调试等工具。用户态程序 coredump 机制相似。其主要流程如下图所示:

77250c5a7e70aefd7ceb62df09b07686.png

可以看出,它的核心原理是保留一段内存并提前加载备用内存 kernel,在主 kernel 故障时跳转到备用 kernel,。本文件的格式是 elf core 文件格式。

kdump 它主要用于捕捉纯软件故障。在嵌入式领域,还需要添加硬件故障捕获,模仿其原理,加强和改造,以构建自己 coredump 机制。

下面详细分析整个过程 kdump 详细的机制原理。

安装

之前的 kdump 安装需要手动安装 kexec-tools、kdump-tools、crash,手工配置 grub cmdline 参数 ubuntu 只需安装一个 linux-crashdump 软件包会自动帮你做:

sudoapt-getinstalllinux-crashdump

安装后,可通过 kdump-config 命令检查系统是否配置正确:

$kdump-configshow DUMP_MODE:kdump USE_KDUMP:1 KDUMP_SYSCTL:kernel.panic_on_oops=1 KDUMP_COREDIR:/var/crash//kdump存储文件目录 crashkerneladdr:0x /var/lib/kdump/vmlinuz:symboliclinkto/boot/vmlinuz-5.8.18  kdumpinitrd: /var/lib/kdump/initrd.img:symboliclinkto/var/lib/kdump/initrd.img-5.8.18  currentstate:readytokdump//显示ready状态,说明系统kdmup机制准备就绪  kexeccommand: /sbin/kexec-p--command-line="BOOT_IMAGE=/boot/vmlinuz-5.8.18 root=UUID=9ee42fe2-4e73-4703-8b6d-bb238ffdb003rofind_preseed=/preseed.cfgautonopromptpriority=criticallocale=en_USquietreset_devicessystemd.unit=kdump-tools-dump.servicenr_cpus=1irqpollnousbata_piix.prefer_ms_hyperv=0"--initrd=/var/lib/kdump/initrd.img/var/lib/kdump/vmlinuz

linux-crashdump 其本质仍由分离软件包组成:

$sudoapt-getinstalllinux-crashdump-d Readingpackagelists...Done Buildingdependencytree Readingstateinformation...Done Thefollowingadditionalpackageswillbeinstalled: crashefibootmgrgrub-commongrub-efi-arm64grub-efi-arm64-bin grub-efi-arm64-signedgrub2-commonkdump-toolskexec-toolslibfreetype6 libsnappy1v5makedumpfileos-prober Suggestedpackages: multiboot-docxorrisodesktop-base Recommendedpackages: secureboot-db ThefollowingNEWpackageswillbeinstalled: crashefibootmgrgrub-commongrub-efi-arm64grub-efi-arm64-bin grub-efi-arm64-signedgrub2-commonkdump-toolskexec-toolslibfreetype6 libsnappy1v5linux-crashdumpmakedumpfileos-prober 0upgraded,14newlyinstalled,0toremoveand67notupgraded. Needtoget6611kBofarchives.

触发 kdump

在 kdump 就绪后,我们手动触发一次 panic :

$sudobash #echoc>/proc/sysrq-trigger

在系统 kdump 完成后,重新启动。我们在那里 /var/crash 可以在目录下找到 kdump 内存转存文件:

$ls-l/var/crash/202107011353/ total65324 -rw-------1rootwhoopsie119480Jul113:53dmesg.202107011353//系统kernellog信息 -rw-------1rootwhoopsie66766582Jul113:53dump.202107011353//内存转存文件,压缩格式 $sudofile/var/crash/202107011353/dump.202107011353 /var/crash/202107011353/dump.202107011353:Kdumpcompresseddumpv6,systemLinux,nodeubuntu,release5.8.18 ,version#18SMPThuJul111:24:39CST2021,machinex86_64,domain(none)

默认生成的 dump 文件是经过 makedumpfile 压缩,或者我们修改一些原始配置 elf core 文件:

$ls-l/var/crash/202107011132/ total&nsp;1785584
-rw------- 1 root whoopsie     117052 Jul  1 11:32 dmesg.202107011132 // 系统 kernel log 信息
-r-----r-- 1 root whoopsie 1979371520 Jul  1 11:32 vmcore.202107011132 // 内存转存储文件,原始 Elf 格式
$ file /var/crash/202107011132/vmcore.202107011132 
/var/crash/202107011132/vmcore.202107011132: ELF 64-bit LSB core file, x86-64, version 1 (SYSV), SVR4-style

调试 kdump

使用 crash 工具可以很方便对 kdump 文件进行分析, crash 是对 gdb 进行了一些包装,生成了更多的调试内核的快捷命令。

$ sudo crash /usr/lib/debug/boot/vmlinux-5.8.0-43-generic /var/crash/202106170338/dump.202106170338

kdump-tools.service 流程分析

在前面我们说过可以把 kdump 默认的压缩格式改成原生 ELF Core 文件格式,本节我们就来实现这个需求。

把/proc/vmcore文件从内存拷贝到磁盘是 crash kernel 中的 kdump-tools.service 服务完成的,我们来详细分析一下其中的流程:

  1. 首先从 kdump-config 配置中可以看到,第二份 crash kernel 启动后 systemd 只需要启动一个服务 kdump-tools-dump.service:

# kdump-config show
DUMP_MODE:        kdump
USE_KDUMP:        1
KDUMP_SYSCTL:     kernel.panic_on_oops=1
KDUMP_COREDIR:    /var/crash
crashkernel addr: 0x73000000
   /var/lib/kdump/vmlinuz: symbolic link to /boot/vmlinuz-5.8.0-43-generic
kdump initrd: 
   /var/lib/kdump/initrd.img: symbolic link to /var/lib/kdump/initrd.img-5.8.0-43-generic
current state:    ready to kdump

kexec command:
  /sbin/kexec -p --command-line="BOOT_IMAGE=/boot/vmlinuz-5.8.0-43-generic root=UUID=9ee42fe2-4e73-4703-8b6d-bb238ffdb003 ro find_preseed=/preseed.cfg auto noprompt priority=critical locale=en_US quiet reset_devices systemd.unit=kdump-tools-dump.service nr_cpus=1 irqpoll nousb ata_piix.prefer_ms_hyperv=0" --initrd=/var/lib/kdump/initrd.img /var/lib/kdump/vmlinuz
  1. kdump-tools-dump.service 服务本质是调用 kdump-tools start 脚本:

# systemctl cat kdump-tools-dump.service
# /lib/systemd/system/kdump-tools-dump.service
[Unit]
Description=Kernel crash dump capture service
Wants=network-online.target dbus.socket systemd-resolved.service
After=network-online.target dbus.socket systemd-resolved.service

[Service]
Type=oneshot
StandardOutput=syslog+console
EnvironmentFile=/etc/default/kdump-tools
ExecStart=/etc/init.d/kdump-tools start
ExecStop=/etc/init.d/kdump-tools stop
RemainAfterExit=yes
  1. kdump-tools 调用了 kdump-config savecore:

# vim /etc/init.d/kdump-tools

KDUMP_SCRIPT=/usr/sbin/kdump-config

                echo -n "Starting $DESC: "
                $KDUMP_SCRIPT savecore
  1. kdump-config 调用了 makedumpfile -c -d 31 /proc/vmcore dump.xxxxxx:

MAKEDUMP_ARGS=${MAKEDUMP_ARGS:="-c -d 31"}
vmcore_file=/proc/vmcore

        makedumpfile $MAKEDUMP_ARGS $vmcore_file $KDUMP_CORETEMP

kdump-tools-dump.service 默认调用 makedumpfile 生成压缩的 dump 文件。但是我们想分析原始的 elf 格式的 vmcore 文件,怎么办?

4.1) 首先我们修改 /usr/sbin/kdump-config 文件中的 MAKEDUMP_ARGS 参数让其出错。

MAKEDUMP_ARGS=${MAKEDUMP_ARGS:="-xxxxx -c -d 31"}      // 其中 -xxxxx 是随便加的选项

4.2) 然后 kdump-config 就会调用 cp /proc/vmcore vmcore.xxxxxx 命令来生成

log_action_msg "running makedumpfile $MAKEDUMP_ARGS $vmcore_file $KDUMP_CORETEMP"
        makedumpfile $MAKEDUMP_ARGS $vmcore_file $KDUMP_CORETEMP  // 先调用 makedumpfile 生成压缩格式的 dump 文件
        ERROR=$?
        if [ $ERROR -ne 0 ] ; then          // 如果 makedumpfile 调用失败
                log_failure_msg "$NAME: makedumpfile failed, falling back to 'cp'"
                logger -t $NAME "makedumpfile failed, falling back to 'cp'"
                KDUMP_CORETEMP="$KDUMP_STAMPDIR/vmcore-incomplete"
                KDUMP_COREFILE="$KDUMP_STAMPDIR/vmcore.$KDUMP_STAMP"
                cp $vmcore_file $KDUMP_CORETEMP       // 再尝试使用 cp 拷贝原始的 vmcore elf 文件
                ERROR=$?
        fi

原理分析

kexec 实现了crash kernel 的加载。核心分为两部分:

  • kexec_file_load()/kexec_load() 负责在起始时就把备份的 kernel 和 initrd 加载好到内存。

  • __crash_kexec() 负责在故障时跳转到备份 kernel 中。

kdump 主要实现把 vmcore 文件从内存拷贝到磁盘,并进行一些瘦身。

本次并不打算对 kexec 加载内核和地址转换流程以及 kdump 的拷贝裁剪进行详细的解析,我们只关注其中的两个重要文件 /proc/kcore 和 /proc/vmcore。其中:

  • /proc/kcore 是在 normal kernel 中把 normal kernel 的内存模拟成一个 elf core 文件,可以使用gdb 对当前系统进行在线调试,因为是自己调试自己会存在一些限制。

  • /proc/vmcore 是在 crash kernel 中把 normal kernel 的内存模拟成一个 elf core 文件,因为这时 normal kernel 已经停止运行,所以可以无限制的进行调试。我们 kdump 最后得到的 dump 文件,就是把 /proc/vmcore 文件从内存简单拷贝到了磁盘,或者再加上点裁剪和压缩。

所以可以看到 /proc/kcore 和 /proc/vmcore 这两个文件是整个机制的核心,我们重点分析这两部分的实现。

elf core 文件格式

关于 ELF 文件格式,我们熟知它有三种格式 .o文件(ET_REL)、.so文件(ET_EXEC)、exe文件(ET_DYN)。但是关于它的第四种格式 core文件() 一直很神秘,也很神奇 gdb 一调试就能恢复到故障现场。

以下是 elf core 文件的大致格式:

可以看到 elf core 文件只关注运行时状态,所以它只有 segment 信息,没有 section 信息。其主要包含两种类型的 segment 信息:

  1. PT_LOAD。每个 segemnt 用来记录一段 memory 区域,还记录了这段 memory 对应的物理地址、虚拟地址和长度。

  2. PT_NOTE。这个是 elf core 中新增的 segment,记录了解析 memory 区域的关键信息。PT_NOTE segment 被分成了多个 elf_note结构,其中 NT_PRSTATUS 类型的记录了复位前 CPU 的寄存器信息,NT_TASKSTRUCT 记录了进程的 task_struct 信息,还有一个最关键0类型的自定义 VMCOREINFO 结论记录了内核的一些关键信息。

elf core 文件的大部分内容用 PT_LOAD segemnt 来记录 memeory 信息,但是怎么利用这些内存信息的钥匙记录在PT_NOTE segemnt 当中。

我们来看一个具体 vmcore 文件的例子:

  1. 首先我们查询 elf header 信息:

$ sudo readelf -e vmcore.202107011132 
ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              CORE (Core file)  // 可以看到文件类型是 ET_CORE
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x0
  Start of program headers:          64 (bytes into file)
  Start of section headers:          0 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         6
  Size of section headers:           0 (bytes)
  Number of section headers:         0
  Section header string table index: 0

There are no sections in this file.

// 可以看到包含了 PT_NOTE 和 PT_LOAD 两种类型的 segment
Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  NOTE           0x0000000000001000 0x0000000000000000 0x0000000000000000
                 0x0000000000001318 0x0000000000001318         0x0
  LOAD           0x0000000000003000 0xffffffffb7200000 0x0000000006c00000
                 0x000000000202c000 0x000000000202c000  RWE    0x0
  LOAD           0x000000000202f000 0xffff903a00001000 0x0000000000001000
                 0x000000000009d800 0x000000000009d800  RWE    0x0
  LOAD           0x00000000020cd000 0xffff903a00100000 0x0000000000100000
                 0x0000000072f00000 0x0000000072f00000  RWE    0x0
  LOAD           0x0000000074fcd000 0xffff903a7f000000 0x000000007f000000
                 0x0000000000ee0000 0x0000000000ee0000  RWE    0x0
  LOAD           0x0000000075ead000 0xffff903a7ff00000 0x000000007ff00000
                 0x0000000000100000 0x0000000000100000  RWE    0x0
  1. 可以进一步查看 PT_NOTE 存储的具体内容:

$ sudo readelf -n vmcore.202107011132 

Displaying notes found at file offset 0x00001000 with length 0x00001318:
  Owner                Data size  Description
  CORE                 0x00000150 NT_PRSTATUS (prstatus structure)  // 因为系统有8个CPU,所以保存了8份 prstatus 信息
  CORE                 0x00000150 NT_PRSTATUS (prstatus structure)
  CORE                 0x00000150 NT_PRSTATUS (prstatus structure)
  CORE                 0x00000150 NT_PRSTATUS (prstatus structure)
  CORE                 0x00000150 NT_PRSTATUS (prstatus structure)
  CORE                 0x00000150 NT_PRSTATUS (prstatus structure)
  CORE                 0x00000150 NT_PRSTATUS (prstatus structure)
  CORE                 0x00000150 NT_PRSTATUS (prstatus structure)
  VMCOREINFO           0x000007dd Unknown note type: (0x00000000)   // 自定义的VMCOREINFO信息
   description data: 4f 53 52 45 4c 45 41 53 45 3d 35 2e 38 2e 31 38 2b 0a 50 41 47 45 53 49 5a 45 3d 34 30 39 36 0a 53 59 4d 42 4f 4c 28 69 6e 69 74 5f 75 74 73 5f 6e 73 29 3d 66 66 66 66 66 66 66 66 ...
  1. 可以进一步解析 VMCOREINFO 存储的信息,description data 后面是一段 16 进制的码流转换以后得到:

OSRELEASE=5.8.0-43-generic
PAGESIZE=4096
SYMBOL(init_uts_ns)=ffffffffa5014620
SYMBOL(node_online_map)=ffffffffa5276720
SYMBOL(swapper_pg_dir)=ffffffffa500a000
SYMBOL(_stext)=ffffffffa3a00000
SYMBOL(vmap_area_list)=ffffffffa50f2560
SYMBOL(mem_section)=ffff91673ffd2000
LENGTH(mem_section)=2048
SIZE(mem_section)=16
OFFSET(mem_section.section_mem_map)=0
SIZE(page)=64
SIZE(pglist_data)=171968
SIZE(zone)=1472
SIZE(free_area)=88
SIZE(list_head)=16
SIZE(nodemask_t)=128
OFFSET(page.flags)=0
OFFSET(page._refcount)=52
OFFSET(page.mapping)=24
OFFSET(page.lru)=8
OFFSET(page._mapcount)=48
OFFSET(page.private)=40
OFFSET(page.compound_dtor)=16
OFFSET(page.compound_order)=17
OFFSET(page.compound_head)=8
OFFSET(pglist_data.node_zones)=0
OFFSET(pglist_data.nr_zones)=171232
OFFSET(pglist_data.node_start_pfn)=171240
OFFSET(pglist_data.node_spanned_pages)=171256
OFFSET(pglist_data.node_id)=171264
OFFSET(zone.free_area)=192
OFFSET(zone.vm_stat)=1280
OFFSET(zone.spanned_pages)=120
OFFSET(free_area.free_list)=0
OFFSET(list_head.next)=0
OFFSET(list_head.prev)=8
OFFSET(vmap_area.va_start)=0
OFFSET(vmap_area.list)=40
LENGTH(zone.free_area)=11
SYMBOL(log_buf)=ffffffffa506a6e0
SYMBOL(log_buf_len)=ffffffffa506a6dc
SYMBOL(log_first_idx)=ffffffffa55f55d8
SYMBOL(clear_idx)=ffffffffa55f55a4
SYMBOL(log_next_idx)=ffffffffa55f55c8
SIZE(printk_log)=16
OFFSET(printk_log.ts_nsec)=0
OFFSET(printk_log.len)=8
OFFSET(printk_log.text_len)=10
OFFSET(printk_log.dict_len)=12
LENGTH(free_area.free_list)=5
NUMBER(NR_FREE_PAGES)=0
NUMBER(PG_lru)=4
NUMBER(PG_private)=13
NUMBER(PG_swapcache)=10
NUMBER(PG_swapbacked)=19
NUMBER(PG_slab)=9
NUMBER(PG_hwpoison)=23
NUMBER(PG_head_mask)=65536
NUMBER(PAGE_BUDDY_MAPCOUNT_VALUE)=-129
NUMBER(HUGETLB_PAGE_DTOR)=2
NUMBER(PAGE_OFFLINE_MAPCOUNT_VALUE)=-257
NUMBER(phys_base)=1073741824
SYMBOL(init_top_pgt)=ffffffffa500a000
NUMBER(pgtable_l5_enabled)=0
SYMBOL(node_data)=ffffffffa5271da0
LENGTH(node_data)=1024
KERNELOFFSET=22a00000
NUMBER(KERNEL_IMAGE_SIZE)=1073741824
NUMBER(sme_mask)=0
CRASHTIME=1623937823

/proc/kcore

有人在清理磁盘空间的常常会碰到 /proc/kcore 文件,因为它显示出来的体积非常的大,有时高达128T。但是实际上她没有占用任何磁盘空间,它是一个内存文件系统中的文件。它也没有占用多少内存空间,除了一些控制头部分占用少量内存,大块的空间都是模拟的,只有在用户读操作的时候才会从对应的内存空间去读取的。

上一节已经介绍了 /proc/kcore 是把当前系统的内存模拟成一个 elf core 文件,可以使用gdb 对当前系统进行在线调试。那本节我们就来看看具体的模拟过程。

准备数据

初始化就是构建 kclist_head 链表的一个过程,链表中每一个成员对应一个 PT_LOAD segment。在读操作的时候再用 elf 的 PT_LOAD segment 呈现这些成员。

static int __init proc_kcore_init(void)
{
 /* (1) 创建 /proc/kcore 文件 */
 proc_root_kcore = proc_create("kcore", S_IRUSR, NULL, &kcore_proc_ops);
 if (!proc_root_kcore) {
  pr_err("couldn't create /proc/kcore\n");
  return 0; /* Always returns 0. */
 }
 /* Store text area if it's special */
 /* (2) 将内核代码段 _text 加入kclist_head链表,kclist_head链表中每一个成员对应一个 PT_LOAD segment */
 proc_kcore_text_init();
 /* Store vmalloc area */
 /* (3) 将 VMALLOC 段内存加入kclist_head链表 */
 kclist_add(&kcore_vmalloc, (void *)VMALLOC_START,
  VMALLOC_END - VMALLOC_START, KCORE_VMALLOC);
 /* (4) 将 MODULES_VADDR 模块内存加入kclist_head链表 */
 add_modules_range();
 /* Store direct-map area from physical memory map */
 /* (5) 遍历系统内存布局表,将有效内存加入kclist_head链表 */
 kcore_update_ram();
 register_hotmemory_notifier(&kcore_callback_nb);

 return 0;
}

static int kcore_update_ram(void)
{
 LIST_HEAD(list);
 LIST_HEAD(garbage);
 int nphdr;
 size_t phdrs_len, notes_len, data_offset;
 struct kcore_list *tmp, *pos;
 int ret = 0;

 down_write(&kclist_lock);
 if (!xchg(&kcore_need_update, 0))
  goto out;

 /* (5.1) 遍历系统内存布局表,将符合`IORESOURCE_SYSTEM_RAM | IORESOURCE_BUSY`内存加入list链表 */
 ret = kcore_ram_list(&list);
 if (ret) {
  /* Couldn't get the RAM list, try again next time. */
  WRITE_ONCE(kcore_need_update, 1);
  list_splice_tail(&list, &garbage);
  goto out;
 }

 /* (5.2) 删除掉原有 kclist_head 链表中的 KCORE_RAM/KCORE_VMEMMAP 区域,因为全局链表中已经覆盖了  */
 list_for_each_entry_safe(pos, tmp, &kclist_head, list) {
  if (pos->type == KCORE_RAM || pos->type == KCORE_VMEMMAP)
   list_move(&pos->list, &garbage);
 }
 /* (5.3) 将原有 kclist_head 链表 和全局链表 list 拼接到一起 */
 list_splice_tail(&list, &kclist_head);

 /* (5.4) 更新 kclist_head 链表的成员个数,一个成员代表一个 PT_LOAD segment。
   计算 PT_NOTE segment 的长度
   计算 `/proc/kcore` 文件的长度,这个长度是个虚值,最大是虚拟地址的最大范围
  */
 proc_root_kcore->size = get_kcore_size(&nphdr, &phdrs_len, &notes_len,
            &data_offset);

out:
 up_write(&kclist_lock);
 /* (5.5) 释放掉上面删除的链表成员占用的空间 */
 list_for_each_entry_safe(pos, tmp, &garbage, list) {
  list_del(&pos->list);
  kfree(pos);
 }
 return ret;
}

其中的一个关键从遍历系统内存布局表,关键代码如下:

kcore_ram_list() → walk_system_ram_range():

int walk_system_ram_range(unsigned long start_pfn, unsigned long nr_pages,
     void *arg, int (*func)(unsigned long, unsigned long, void *))
{
 resource_size_t start, end;
 unsigned long flags;
 struct resource res;
 unsigned long pfn, end_pfn;
 int ret = -EINVAL;

 start = (u64) start_pfn << PAGE_SHIFT;
 end = ((u64)(start_pfn + nr_pages) << PAGE_SHIFT) - 1;
 /* (5.1.1) 从 iomem_resource 链表中查找符合 IORESOURCE_SYSTEM_RAM | IORESOURCE_BUSY 的资源段 */
 flags = IORESOURCE_SYSTEM_RAM | IORESOURCE_BUSY;
 while (start < end &&
        !find_next_iomem_res(start, end, flags, IORES_DESC_NONE,
        false, &res)) {
  pfn = PFN_UP(res.start);
  end_pfn = PFN_DOWN(res.end + 1);
  if (end_pfn > pfn)
   ret = (*func)(pfn, end_pfn - pfn, arg);
  if (ret)
   break;
  start = res.end + 1;
 }
 return ret;
}

其实就相当于以下命令:

$ sudo cat /proc/iomem | grep "System RAM"
00001000-0009e7ff : System RAM
00100000-7fedffff : System RAM
7ff00000-7fffffff : System RAM

读取 elf core

准备好数据以后,还是在读 /proc/kcore 文件时,以 elf core 的格式呈现。

static const struct proc_ops kcore_proc_ops = {
 .proc_read = read_kcore,
 .proc_open = open_kcore,
 .proc_release = release_kcore,
 .proc_lseek = default_llseek,
};

↓

static ssize_t
read_kcore(struct file *file, char __user *buffer, size_t buflen, loff_t *fpos)
{
 char *buf = file->private_data;
 size_t phdrs_offset, notes_offset, data_offset;
 size_t phdrs_len, notes_len;
 struct kcore_list *m;
 size_t tsz;
 int nphdr;
 unsigned long start;
 size_t orig_buflen = buflen;
 int ret = 0;

 down_read(&kclist_lock);

 /* (1) 获取到PT_LOAD segment个数、PT_NOTE segment 的长度等信息,开始动态构造 elf core 文件了 */
 get_kcore_size(&nphdr, &phdrs_len, &notes_len, &data_offset);
 phdrs_offset = sizeof(struct elfhdr);
 notes_offset = phdrs_offset + phdrs_len;

 /* ELF file header. */
 /* (2) 构造 ELF 文件头,并拷贝给给用户态读内存 */
 if (buflen && *fpos < sizeof(struct elfhdr)) {
  struct elfhdr ehdr = {
   .e_ident = {
    [EI_MAG0] = ELFMAG0,
    [EI_MAG1] = ELFMAG1,
    [EI_MAG2] = ELFMAG2,
    [EI_MAG3] = ELFMAG3,
    [EI_CLASS] = ELF_CLASS,
    [EI_DATA] = ELF_DATA,
    [EI_VERSION] = EV_CURRENT,
    [EI_OSABI] = ELF_OSABI,
   },
   .e_type = ET_CORE,
   .e_machine = ELF_ARCH,
   .e_version = EV_CURRENT,
   .e_phoff = sizeof(struct elfhdr),
   .e_flags = ELF_CORE_EFLAGS,
   .e_ehsize = sizeof(struct elfhdr),
   .e_phentsize = sizeof(struct elf_phdr),
   .e_phnum = nphdr,
  };

  tsz = min_t(size_t, buflen, sizeof(struct elfhdr) - *fpos);
  if (copy_to_user(buffer, (char *)&ehdr + *fpos, tsz)) {
   ret = -EFAULT;
   goto out;
  }

  buffer += tsz;
  buflen -= tsz;
  *fpos += tsz;
 }

 /* ELF program headers. */
 /* (3) 构造 ELF program 头,并拷贝给给用户态读内存 */
 if (buflen && *fpos < phdrs_offset + phdrs_len) {
  struct elf_phdr *phdrs, *phdr;

  phdrs = kzalloc(phdrs_len, GFP_KERNEL);
  if (!phdrs) {
   ret = -ENOMEM;
   goto out;
  }

  /* (3.1) PT_NOTE segment 不需要物理地址和虚拟地址 */
  phdrs[0].p_type = PT_NOTE;
  phdrs[0].p_offset = notes_offset;
  phdrs[0].p_filesz = notes_len;

  phdr = &phdrs[1];
  /* (3.2) 逐个计算 PT_LOAD segment 的物理地址、虚拟地址和长度 */
  list_for_each_entry(m, &kclist_head, list) {
   phdr->p_type = PT_LOAD;
   phdr->p_flags = PF_R | PF_W | PF_X;
   phdr->p_offset = kc_vaddr_to_offset(m->addr) + data_offset;
   if (m->type == KCORE_REMAP)
    phdr->p_vaddr = (size_t)m->vaddr;
   else
    phdr->p_vaddr = (size_t)m->addr;
   if (m->type == KCORE_RAM || m->type == KCORE_REMAP)
    phdr->p_paddr = __pa(m->addr);
   else if (m->type == KCORE_TEXT)
    phdr->p_paddr = __pa_symbol(m->addr);
   else
    phdr->p_paddr = (elf_addr_t)-1;
   phdr->p_filesz = phdr->p_memsz = m->size;
   phdr->p_align = PAGE_SIZE;
   phdr++;
  }

  tsz = min_t(size_t, buflen, phdrs_offset + phdrs_len - *fpos);
  if (copy_to_user(buffer, (char *)phdrs + *fpos - phdrs_offset,
     tsz)) {
   kfree(phdrs);
   ret = -EFAULT;
   goto out;
  }
  kfree(phdrs);

  buffer += tsz;
  buflen -= tsz;
  *fpos += tsz;
 }

 /* ELF note segment. */
 /* (4) 构造 PT_NOTE segment,并拷贝给给用户态读内存 */
 if (buflen && *fpos < notes_offset + notes_len) {
  struct elf_prstatus prstatus = {};
  struct elf_prpsinfo prpsinfo = {
   .pr_sname = 'R',
   .pr_fname = "vmlinux",
  };
  char *notes;
  size_t i = 0;

  strlcpy(prpsinfo.pr_psargs, saved_command_line,
   sizeof(prpsinfo.pr_psargs));

  notes = kzalloc(notes_len, GFP_KERNEL);
  if (!notes) {
   ret = -ENOMEM;
   goto out;
  }

  /* (4.1) 添加 NT_PRSTATUS */
  append_kcore_note(notes, &i, CORE_STR, NT_PRSTATUS, &prstatus,
      sizeof(prstatus));
  /* (4.2) 添加 NT_PRPSINFO */
  append_kcore_note(notes, &i, CORE_STR, NT_PRPSINFO, &prpsinfo,
      sizeof(prpsinfo));
  /* (4.3) 添加 NT_TASKSTRUCT */
  append_kcore_note(notes, &i, CORE_STR, NT_TASKSTRUCT, current,
      arch_task_struct_size);
  /*
   * vmcoreinfo_size is mostly constant after init time, but it
   * can be changed by crash_save_vmcoreinfo(). Racing here with a
   * panic on another CPU before the machine goes down is insanely
   * unlikely, but it's better to not leave potential buffer
   * overflows lying around, regardless.
   * Vmcoreinfo_size在初始化后基本保持不变,但可以通过crash_save_vmcoreinfo()修改。在机器宕机之前,在另一个CPU上出现恐慌是不太可能的,但无论如何,最好不要让潜在的缓冲区溢出到处存在。
   */
  /* (4.4) 添加 VMCOREINFO */
  append_kcore_note(notes, &i, VMCOREINFO_NOTE_NAME, 0,
      vmcoreinfo_data,
      min(vmcoreinfo_size, notes_len - i));

  tsz = min_t(size_t, buflen, notes_offset + notes_len - *fpos);
  if (copy_to_user(buffer, notes + *fpos - notes_offset, tsz)) {
   kfree(notes);
   ret = -EFAULT;
   goto out;
  }
  kfree(notes);

  buffer += tsz;
  buflen -= tsz;
  *fpos += tsz;
 }

 /*
  * Check to see if our file offset matches with any of
  * the addresses in the elf_phdr on our list.
  */
 start = kc_offset_to_vaddr(*fpos - data_offset);
 if ((tsz = (PAGE_SIZE - (start & ~PAGE_MASK))) > buflen)
  tsz = buflen;

 m = NULL;
 /* (5) 构造 PT_LOAD segment,并拷贝给给用户态读内存 */
 while (buflen) {
  /*
   * If this is the first iteration or the address is not within
   * the previous entry, search for a matching entry.
   */
  if (!m || start < m->addr || start >= m->addr + m->size) {
   list_for_each_entry(m, &kclist_head, list) {
    if (start >= m->addr &&
        start < m->addr + m->size)
     break;
   }
  }

  if (&m->list == &kclist_head) {
   if (clear_user(buffer, tsz)) {
    ret = -EFAULT;
    goto out;
   }
   m = NULL; /* skip the list anchor */
  } else if (!pfn_is_ram(__pa(start) >> PAGE_SHIFT)) {
   if (clear_user(buffer, tsz)) {
    ret = -EFAULT;
    goto out;
   }
  } else if (m->type == KCORE_VMALLOC) {
   vread(buf, (char *)start, tsz);
   /* we have to zero-fill user buffer even if no read */
   if (copy_to_user(buffer, buf, tsz)) {
    ret = -EFAULT;
    goto out;
   }
  } else if (m->type == KCORE_USER) {
   /* User page is handled prior to normal kernel page: */
   if (copy_to_user(buffer, (char *)start, tsz)) {
    ret = -EFAULT;
    goto out;
   }
  } else {
   if (kern_addr_valid(start)) {
    /*
     * Using bounce buffer to bypass the
     * hardened user copy kernel text checks.
     */
    if (copy_from_kernel_nofault(buf, (void *)start,
      tsz)) {
     if (clear_user(buffer, tsz)) {
      ret = -EFAULT;
      goto out;
     }
    } else {
     if (copy_to_user(buffer, buf, tsz)) {
      ret = -EFAULT;
      goto out;
     }
    }
   } else {
    if (clear_user(buffer, tsz)) {
     ret = -EFAULT;
     goto out;
    }
   }
  }
  buflen -= tsz;
  *fpos += tsz;
  buffer += tsz;
  start += tsz;
  tsz = (buflen > PAGE_SIZE ? PAGE_SIZE : buflen);
 }

out:
 up_read(&kclist_lock);
 if (ret)
  return ret;
 return orig_buflen - buflen;
}

/proc/vmcore

/proc/vmcore 是在 crash kernel 中把 normal kernel 的内存模拟成一个 elf core 文件。

它的文件格式构造是和上一节的 /proc/kcore 是类似的,不同的是它的数据准备工作是分成两部分完成的:

  • normal kernel 负责事先把 elf header 准备好。

  • crash kernel 负责把传递过来的 elf header 封装成 /proc/vmcore 文件,并且保存到磁盘。

下面我们就来详细分析具体的过程。

准备 elf header (运行在 normal kernel)

在系统发生故障时状态是很不稳定的,时间也是很紧急的,所以我们在 normal kernel 中就尽可能早的把 /proc/vomcore 文件的 elf header 数据准备好。虽然 normal kernel 不会呈现 /proc/vmcore,只会在 crash kernel 中呈现。

在 kexec_tools 使用 kexec_file_load() 系统调用加载 crash kernel 时,就顺带把 /proc/vmcore 的 elf header 需要的大部分数据准备好了:

kexec_file_load() → kimage_file_alloc_init() → kimage_file_prepare_segments() → arch_kexec_kernel_image_load() → image->fops->load() → kexec_bzImage64_ops.load() → bzImage64_load() → crash_load_segments() → prepare_elf_headers() → crash_prepare_elf64_headers():

static int prepare_elf_headers(struct kimage *image, void **addr,
     unsigned long *sz)
{
 struct crash_mem *cmem;
 int ret;

 /* (1) 遍历系统内存布局表统计有效内存区域的个数,根据个数分配 cmem 空间 */
 cmem = fill_up_crash_elf_data();
 if (!cmem)
  return -ENOMEM;

 /* (2) 再次遍历系统内存布局表统计有效内存区域,记录到 cmem 空间 */
 ret = walk_system_ram_res(0, -1, cmem, prepare_elf64_ram_headers_callback);
 if (ret)
  goto out;

 /* Exclude unwanted mem ranges */
 /* (3) 排除掉一些不会使用的内存区域 */
 ret = elf_header_exclude_ranges(cmem);
 if (ret)
  goto out;

 /* By default prepare 64bit headers */
 /* (4) 开始构造 elf header */
 ret =  crash_prepare_elf64_headers(cmem, IS_ENABLED(CONFIG_X86_64), addr, sz);

out:
 vfree(cmem);
 return ret;
}

int crash_prepare_elf64_headers(struct crash_mem *mem, int kernel_map,
     void **addr, unsigned long *sz)
{
 Elf64_Ehdr *ehdr;
 Elf64_Phdr *phdr;
 unsigned long nr_cpus = num_possible_cpus(), nr_phdr, elf_sz;
 unsigned char *buf;
 unsigned int cpu, i;
 unsigned long long notes_addr;
 unsigned long mstart, mend;

 /* extra phdr for vmcoreinfo elf note */
 nr_phdr = nr_cpus + 1;
 nr_phdr += mem->nr_ranges;

 /*
  * kexec-tools creates an extra PT_LOAD phdr for kernel text mapping
  * area (for example, ffffffff80000000 - ffffffffa0000000 on x86_64).
  * I think this is required by tools like gdb. So same physical
  * memory will be mapped in two elf headers. One will contain kernel
  * text virtual addresses and other will have __va(physical) addresses.
  */

 nr_phdr++;
 elf_sz = sizeof(Elf64_Ehdr) + nr_phdr * sizeof(Elf64_Phdr);
 elf_sz = ALIGN(elf_sz, ELF_CORE_HEADER_ALIGN);

 buf = vzalloc(elf_sz);
 if (!buf)
  return -ENOMEM;

 /* (4.1) 构造 ELF 文件头 */
 ehdr = (Elf64_Ehdr *)buf;
 phdr = (Elf64_Phdr *)(ehdr + 1);
 memcpy(ehdr->e_ident, ELFMAG, SELFMAG);
 ehdr->e_ident[EI_CLASS] = ELFCLASS64;
 ehdr->e_ident[EI_DATA] = ELFDATA2LSB;
 ehdr->e_ident[EI_VERSION] = EV_CURRENT;
 ehdr->e_ident[EI_OSABI] = ELF_OSABI;
 memset(ehdr->e_ident + EI_PAD, 0, EI_NIDENT - EI_PAD);
 ehdr->e_type = ET_CORE;
 ehdr->e_machine = ELF_ARCH;
 ehdr->e_version = EV_CURRENT;
 ehdr->e_phoff = sizeof(Elf64_Ehdr);
 ehdr->e_ehsize = sizeof(Elf64_Ehdr);
 ehdr->e_phentsize = sizeof(Elf64_Phdr);

 /* Prepare one phdr of type PT_NOTE for each present cpu */
 /* (4.2) 构造 ELF program 头,
   每个 cpu 独立构造一个 PT_LOAD segment
   segment 的数据存放在 per_cpu_ptr(crash_notes, cpu) 变量当中
   注意 crash_notes 中目前还没有数据,当前只是记录了物理地址。只有在 crash 发生以后,才会实际往里面存储数据
  */
 for_each_present_cpu(cpu) {
  phdr->p_type = PT_NOTE;
  notes_addr = per_cpu_ptr_to_phys(per_cpu_ptr(crash_notes, cpu));
  phdr->p_offset = phdr->p_paddr = notes_addr;
  phdr->p_filesz = phdr->p_memsz = sizeof(note_buf_t);
  (ehdr->e_phnum)++;
  phdr++;
 }

 /* Prepare one PT_NOTE header for vmcoreinfo */
 /* (4.3) 构造 ELF program 头,VMCOREINFO 独立构造一个 PT_LOAD segment
   注意当前只是记录了 vmcoreinfo_note 的物理地址,实际数据也是分几部分更新的
  */
 phdr->p_type = PT_NOTE;
 phdr->p_offset = phdr->p_paddr = paddr_vmcoreinfo_note();
 phdr->p_filesz = phdr->p_memsz = VMCOREINFO_NOTE_SIZE;
 (ehdr->e_phnum)++;
 phdr++;

 /* Prepare PT_LOAD type program header for kernel text region */
 /* (4.4) 构造 ELF program 头,内核代码段对应的 PT_LOAD segment */
 if (kernel_map) {
  phdr->p_type = PT_LOAD;
  phdr->p_flags = PF_R|PF_W|PF_X;
  phdr->p_vaddr = (unsigned long) _text;
  phdr->p_filesz = phdr->p_memsz = _end - _text;
  phdr->p_offset = phdr->p_paddr = __pa_symbol(_text);
  ehdr->e_phnum++;
  phdr++;
 }

 /* Go through all the ranges in mem->ranges[] and prepare phdr */
 /* (4.5) 遍历 cmem,把系统中的有效内存创建成 PT_LOAD segment */
 for (i = 0; i < mem->nr_ranges; i++) {
  mstart = mem->ranges[i].start;
  mend = mem->ranges[i].end;

  phdr->p_type = PT_LOAD;
  phdr->p_flags = PF_R|PF_W|PF_X;
  phdr->p_offset  = mstart;

  phdr->p_paddr = mstart;
  phdr->p_vaddr = (unsigned long) __va(mstart);
  phdr->p_filesz = phdr->p_memsz = mend - mstart + 1;
  phdr->p_align = 0;
  ehdr->e_phnum++;
  phdr++;
  pr_debug("Crash PT_LOAD elf header. phdr=%p vaddr=0x%llx, paddr=0x%llx, sz=0x%llx e_phnum=%d p_offset=0x%llx\n",
   phdr, phdr->p_vaddr, phdr->p_paddr, phdr->p_filesz,
   ehdr->e_phnum, phdr->p_offset);
 }

 *addr = buf;
 *sz = elf_sz;
 return 0;
}

只有在发生 panic 以后,才会往 crash_notes 中保存实际的 CPU 寄存器数据。其更新过程如下:

__crash_kexec() → machine_crash_shutdown() → crash_save_cpu():
ipi_cpu_crash_stop() → crash_save_cpu():

void crash_save_cpu(struct pt_regs *regs, int cpu)
{
 struct elf_prstatus prstatus;
 u32 *buf;

 if ((cpu < 0) || (cpu >= nr_cpu_ids))
  return;

 /* Using ELF notes here is opportunistic.
  * I need a well defined structure format
  * for the data I pass, and I need tags
  * on the data to indicate what information I have
  * squirrelled away.  ELF notes happen to provide
  * all of that, so there is no need to invent something new.
  */
 buf = (u32 *)per_cpu_ptr(crash_notes, cpu);
 if (!buf)
  return;
 /* (1) 清零 */
 memset(&prstatus, 0, sizeof(prstatus));
 /* (2) 保存 pid */
 prstatus.pr_pid = current->pid;
 /* (3) 保存 寄存器 */
 elf_core_copy_kernel_regs(&prstatus.pr_reg, regs);
 /* (4) 以 elf_note 格式存储到 crash_notes 中 */
 buf = append_elf_note(buf, KEXEC_CORE_NOTE_NAME, NT_PRSTATUS,
         &prstatus, sizeof(prstatus));
 /* (5) 追加一个全零的 elf_note 当作结尾 */
 final_note(buf);
}

vmcoreinfo_note 分成两部分来更新:

  • 2.1 第一部分在系统初始化的时候准备好了大部分的数据:

static int __init crash_save_vmcoreinfo_init(void)
{
 /* (1.1) 分配 vmcoreinfo_data 空间 */
 vmcoreinfo_data = (unsigned char *)get_zeroed_page(GFP_KERNEL);
 if (!vmcoreinfo_data) {
  pr_warn("Memory allocation for vmcoreinfo_data failed\n");
  return -ENOMEM;
 }

 /* (1.2) 分配 vmcoreinfo_note 空间 */
 vmcoreinfo_note = alloc_pages_exact(VMCOREINFO_NOTE_SIZE,
      GFP_KERNEL | __GFP_ZERO);
 if (!vmcoreinfo_note) {
  free_page((unsigned long)vmcoreinfo_data);
  vmcoreinfo_data = NULL;
  pr_warn("Memory allocation for vmcoreinfo_note failed\n");
  return -ENOMEM;
 }

 /* (2.1) 把系统的各种关键信息使用 VMCOREINFO_xxx 一系列宏,以字符串的形式保持到 vmcoreinfo_data  */
 VMCOREINFO_OSRELEASE(init_uts_ns.name.release);
 VMCOREINFO_PAGESIZE(PAGE_SIZE);

 VMCOREINFO_SYMBOL(init_uts_ns);
 VMCOREINFO_SYMBOL(node_online_map);
#ifdef CONFIG_MMU
 VMCOREINFO_SYMBOL_ARRAY(swapper_pg_dir);
#endif
 VMCOREINFO_SYMBOL(_stext);
 VMCOREINFO_SYMBOL(vmap_area_list);

#ifndef CONFIG_NEED_MULTIPLE_NODES
 VMCOREINFO_SYMBOL(mem_map);
 VMCOREINFO_SYMBOL(contig_page_data);
#endif
#ifdef CONFIG_SPARSEMEM
 VMCOREINFO_SYMBOL_ARRAY(mem_section);
 VMCOREINFO_LENGTH(mem_section, NR_SECTION_ROOTS);
 VMCOREINFO_STRUCT_SIZE(mem_section);
 VMCOREINFO_OFFSET(mem_section, section_mem_map);
#endif
 VMCOREINFO_STRUCT_SIZE(page);
 VMCOREINFO_STRUCT_SIZE(pglist_data);
 VMCOREINFO_STRUCT_SIZE(zone);
 VMCOREINFO_STRUCT_SIZE(free_area);
 VMCOREINFO_STRUCT_SIZE(list_head);
 VMCOREINFO_SIZE(nodemask_t);
 VMCOREINFO_OFFSET(page, flags);
 VMCOREINFO_OFFSET(page, _refcount);
 VMCOREINFO_OFFSET(page, mapping);
 VMCOREINFO_OFFSET(page, lru);
 VMCOREINFO_OFFSET(page, _mapcount);
 VMCOREINFO_OFFSET(page, private);
 VMCOREINFO_OFFSET(page, compound_dtor);
 VMCOREINFO_OFFSET(page, compound_order);
 VMCOREINFO_OFFSET(page, compound_head);
 VMCOREINFO_OFFSET(pglist_data, node_zones);
 VMCOREINFO_OFFSET(pglist_data, nr_zones);
#ifdef CONFIG_FLAT_NODE_MEM_MAP
 VMCOREINFO_OFFSET(pglist_data, node_mem_map);
#endif
 VMCOREINFO_OFFSET(pglist_data, node_start_pfn);
 VMCOREINFO_OFFSET(pglist_data, node_spanned_pages);
 VMCOREINFO_OFFSET(pglist_data, node_id);
 VMCOREINFO_OFFSET(zone, free_area);
 VMCOREINFO_OFFSET(zone, vm_stat);
 VMCOREINFO_OFFSET(zone, spanned_pages);
 VMCOREINFO_OFFSET(free_area, free_list);
 VMCOREINFO_OFFSET(list_head, next);
 VMCOREINFO_OFFSET(list_head, prev);
 VMCOREINFO_OFFSET(vmap_area, va_start);
 VMCOREINFO_OFFSET(vmap_area, list);
 VMCOREINFO_LENGTH(zone.free_area, MAX_ORDER);
 log_buf_vmcoreinfo_setup();
 VMCOREINFO_LENGTH(free_area.free_list, MIGRATE_TYPES);
 VMCOREINFO_NUMBER(NR_FREE_PAGES);
 VMCOREINFO_NUMBER(PG_lru);
 VMCOREINFO_NUMBER(PG_private);
 VMCOREINFO_NUMBER(PG_swapcache);
 VMCOREINFO_NUMBER(PG_swapbacked);
 VMCOREINFO_NUMBER(PG_slab);
#ifdef CONFIG_MEMORY_FAILURE
 VMCOREINFO_NUMBER(PG_hwpoison);
#endif
 VMCOREINFO_NUMBER(PG_head_mask);
#define PAGE_BUDDY_MAPCOUNT_VALUE (~PG_buddy)
 VMCOREINFO_NUMBER(PAGE_BUDDY_MAPCOUNT_VALUE);
#ifdef CONFIG_HUGETLB_PAGE
 VMCOREINFO_NUMBER(HUGETLB_PAGE_DTOR);
#define PAGE_OFFLINE_MAPCOUNT_VALUE (~PG_offline)
 VMCOREINFO_NUMBER(PAGE_OFFLINE_MAPCOUNT_VALUE);
#endif

 /* (2.2) 补充一些架构相关的 vmcoreinfo */
 arch_crash_save_vmcoreinfo();

 /* (3) 把 vmcoreinfo_data 中保存的数据以 elf_note 的形式保存到 vmcoreinfo_note 中 */
 update_vmcoreinfo_note();

 return 0;
}
  • 2.2 第二部分在 panic 发生后追加了数据:

__crash_kexec() → crash_save_vmcoreinfo():

void crash_save_vmcoreinfo(void)
{
 if (!vmcoreinfo_note)
  return;

 /* Use the safe copy to generate vmcoreinfo note if have */
 if (vmcoreinfo_data_safecopy)
  vmcoreinfo_data = vmcoreinfo_data_safecopy;

 /* (1) 补充 "CRASHTIME=xxx" 信息 */
 vmcoreinfo_append_str("CRASHTIME=%lld\n", ktime_get_real_seconds());
 update_vmcoreinfo_note();
}

vmcoreinfo 对应 readelf -n xxx 读出的数据:

$ readelf -n vmcore.202106170650 

Displaying notes found at file offset 0x00001000 with length 0x00000ac8:
  Owner                Data size  Description
  CORE                 0x00000150 NT_PRSTATUS (prstatus structure)
  CORE                 0x00000150 NT_PRSTATUS (prstatus structure)
  VMCOREINFO           0x000007e6 Unknown note type: (0x00000000)
   description data: 4f 53 52 45 4c 45 41 53 45 3d 35 2e 38 2e 30

// description data 对应 ascii:
OSRELEASE=5.8.0-43-generic
PAGESIZE=4096
SYMBOL(init_uts_ns)=ffffffffa5014620
SYMBOL(node_online_map)=ffffffffa5276720
SYMBOL(swapper_pg_dir)=ffffffffa500a000
SYMBOL(_stext)=ffffffffa3a00000
SYMBOL(vmap_area_list)=ffffffffa50f2560
SYMBOL(mem_section)=ffff91673ffd2000
LENGTH(mem_section)=2048
SIZE(mem_section)=16
OFFSET(mem_section.section_mem_map)=0
SIZE(page)=64
SIZE(pglist_data)=171968
SIZE(zone)=1472
SIZE(free_area)=88
...
CRASHTIME=1623937823

准备 cmdline (运行在 normal kernel)

准备好的 elf header 数据怎么传递给 crash kernel 呢?是通过 cmdline 来进行传递的:

kexec_file_load() → kimage_file_alloc_init() → kimage_file_prepare_segments() → arch_kexec_kernel_image_load() → image->fops->load() → kexec_bzImage64_ops.load() → bzImage64_load() → setup_cmdline():

static int setup_cmdline(struct kimage *image, struct boot_params *params,
    unsigned long bootparams_load_addr,
    unsigned long cmdline_offset, char *cmdline,
    unsigned long cmdline_len)
{
 char *cmdline_ptr = ((char *)params) + cmdline_offset;
 unsigned long cmdline_ptr_phys, len = 0;
 uint32_t cmdline_low_32, cmdline_ext_32;

    /* (1) 在 crask kernel 的 cmdline 中追加参数:"elfcorehdr=0x%lx " */
 if (image->type == KEXEC_TYPE_CRASH) {
  len = sprintf(cmdline_ptr,
   "elfcorehdr=0x%lx ", image->arch.elf_load_addr);
 }
 memcpy(cmdline_ptr + len, cmdline, cmdline_len);
 cmdline_len += len;

 cmdline_ptr[cmdline_len - 1] = '\0';

 pr_debug("Final command line is: %s\n", cmdline_ptr);
 cmdline_ptr_phys = bootparams_load_addr + cmdline_offset;
 cmdline_low_32 = cmdline_ptr_phys & 0xffffffffUL;
 cmdline_ext_32 = cmdline_ptr_phys >> 32;

 params->hdr.cmd_line_ptr = cmdline_low_32;
 if (cmdl

标签: 24vdc500a继电器

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台