1     设计

1.1    技术

使用kdump(kexec)引导第二个内核启动,在第二个内核中捕捉panic信息,同时将系统内核的内存镜像存储到/proc/vmcore文件中,由于此文件与内存大小一致,故不能直接使用此文件。使用kexec 的一个工具vmcore-dmesg,可以将/proc/vmcore转换为dmesg的信息,然后存储到文件中即可。

1.2    步骤

1.2.1  制作小内核

制作一个小内核,即crash kernel,专门捕捉panic信息,必须足够小,如此可以少占用内存。

根据内核文件kdump.txt,编译小内核时,编译参数选中HIGHMEM、CRASH_DUMP、PROC_VMCORE、RELOCABLE,详细参考内核文档。

小内核使用自己的config文件编译,目前小内核加上文件系统总共2.1M(已经包含了网络和部分网卡驱动)。

1.2.2  修改大内核

大内核,即系统内核,增加KEXEC编译选项,注意openwrt中不仅仅选中内核参数,还需要选中openwrt的“Global build settings”的KEXEC编译参数,之前为此搞了近一个星期。

1.2.3  修改grub

引导大内核时,需要指定参数crashkernel,由于小内核足够小,目前仅仅预留32M。

小内核启动后,只能使用crashkernel指定的内存区域,大内核的内存内容不会被修改。

1.2.4  启动脚本

大内核启动脚本,启动时调用命令kexec -p PATH_TO_CRASH_KERNEL,如此就装载了panic时的小内核,目前不需要传递任何参数。

小内核启动脚本,启动时调用命令vmcore-dmesg /proc/vmcore  输出panic日志到文件,然后系统重启。

大内核启动参数中有BOOT_IMAGE,故系统启动时使用这个标志区分大小内核。


 

2     尝试

在实现此功能的过程中,作了很多尝试,之前不知道有vmcore-dmesg这个工具,如此大的vmcore文件在系统中无法使用,故刚开始否定了kdump的方案。

2.1   ramoops

内核已经支持,但在使用时发现重启后无法获得panic信息。

后来模仿ramoops,自己设计了一个内核模块,预留一块内存,使用kmsg_dump_register获得panic时的字符串信息,然后将这些信息写入预留的内存区域,同样在系统启动后这块区域的内容消失。

2.2   pstore

内核已经有这方面的考虑,pstore,即persistent store,顾名思义,即可以保存的存储空间,试验时发现系统重启后无法保存写入的消息。

 

后来仔细查看内核文档,发现pstore机制需要硬件支持,x86上有APEI,在其他平台上也有相应的硬件支持,但我们目前的设备上不支持。

2.3   makedumpfile

此程序可以对vmcore作压缩动作,而且可以直接输出dmesg字符串信息,但在编译时发现依赖的库太多,故作罢。


 

3     原理

此章节简单描述kexec、kdump、vmcore-dmesg的原理。

3.1   kexec

这个东西网上的资料很多,目前有两种工作方式。

1.     kexec-l

需要使用kexec –e命令手动启动小内核, 这种方式非常适合内核切换调试。

2.     kexec-p

在系统panic时,自动进入小内核

这种方式必须设定crashkernel,小内核只会使用这个指定的内存空间,因此不会污染大内核的内存数据。

3.2   ELF

大内核的内存数据存储为elf文件格式,其elf文件头被当作内核启动参数传递到小内核。

此elf格式为CORE,可以看作是可执行文件(Execution View),有若干个programheader,由于我们只关注dmesg的信息,因此只需要关注PT_NOTE类型的program header。

图1:ELF格式

3.2.1  PT_NOTE

PT_NOTE类型的program header,是由一个固定的头部加多个NOTE结点组成的,每个NOTE结点结构如下(Elf32_word为4字节长度):

typedef structelf32_note {

  Elf32_Word n_namesz; /* Name size */

  Elf32_Word n_descsz; /* Content size */

  Elf32_Word n_type;      /* Content type */

} Elf32_Nhdr;

NOTE结构后面直接是字符串形式的name和desc,注意4字节对齐。

对于vmcore信息来说,name固定为VMCOREINFO,desc为内核写入的字符串。

 

3.3   log

内核的log信息使用一个类似“字符串环”结构,使用4个全局变量:

log_buf,起始指针

log_end,下一个写入内容的指针

log_buf_len,字符串环总长,默认为128K

logged_chars,未读取内容的长度

 

3.4   dmesg

这就是我们最关注的panic对应的dmesg内容了。

3.4.1  生成

3.4.1.1 系统初始化

crash_save_vmcoreinfo_init函数初始化时调用,它会初始化vmcoreinfo_data内容,此内容就是ELF文件中的PT_NOTE结构的desc信息,这些内容在初始化时就可以得到。

注意“字符串环”,系统初始化时那4个全局变量的地址也被存储到ELF文件中去了,因此后续panic时对这个全局变量指向内容的修改,均可以通过此“字符串环”获得。

3.4.1.2 系统panic

crash_kexec函数会被执行,其中调用crash_save_vmcoreinfo函数,将CRASHTIME字符串内容数据写入“字符串环”,然后加载小内核并启动小内核。

3.4.2  获取

小内核启动后,会在vmcore.c中将ELF内存映射到/proc/vmcore文件,供应用层访问。此过程比较简单,主要是根据内核启动参数是否带有“elfcorehdr=”,如果有这个参数表明是小内核启动,会将这个地址作为ELF头部进行深度解析。

3.4.3  分析

vmcore-dmesg.c这个文件,使用vmcore这个文件作为输入,输出是dmesg的信息。分析ELF文件,获取PT_NOTE的program header,再关注NOTE结点中的“VMCOREINFO”的name字段,获取其中的desc字符串信息输出到终端即可。

Logo

更多推荐