Kdump 文档 - 基于 kexec 的崩溃转储解决方案¶
本文档包括概述、设置、安装和分析信息。
概述¶
Kdump 使用 kexec 在需要获取系统内核内存转储时(例如,当系统发生恐慌时)快速启动到转储捕获内核。系统内核的内存映像在重新启动后保留,并且转储捕获内核可以访问它。
您可以使用 cp、scp 或 makedumpfile 等常用命令将内存映像复制到本地磁盘上的转储文件,或通过网络复制到远程系统。
目前在 x86、x86_64、ppc64、s390x、arm 和 arm64 架构上支持 Kdump 和 kexec。
当系统内核启动时,它会为转储捕获内核保留一小部分内存。这确保了来自系统内核的持续直接内存访问 (DMA) 不会损坏转储捕获内核。kexec -p 命令将转储捕获内核加载到此保留内存中。
在 x86 机器上,无论内核加载在哪里,都需要物理内存的前 640 KB 用于启动。为了简化处理,整个低 1M 都被保留,以避免任何后续的内核或设备驱动程序将数据写入此区域。这样,低 1M 可以被 kdump 内核重用为系统 RAM,而无需额外的处理。
在 PPC64 机器上,无论内核加载在哪里,都需要物理内存的前 32KB 用于启动,并且为了支持 64K 页面大小,kexec 会备份前 64KB 的内存。
对于 s390x,当触发 kdump 时,crashkernel 区域将与区域 [0, crashkernel 区域大小] 交换,然后 kdump 内核在 [0, crashkernel 区域大小] 中运行。因此,s390x 不需要可重定位内核。
有关系统内核核心映像的所有必要信息都以 ELF 格式编码,并在崩溃前存储在保留的内存区域中。ELF 标头起始的物理地址通过 elfcorehdr= 引导参数传递给转储捕获内核。可选地,当使用 elfcorehdr=[size[KMG]@]offset[KMG] 语法时,也可以传递 ELF 标头的大小。
使用转储捕获内核,您可以通过 /proc/vmcore 访问内存映像。这将转储导出为 ELF 格式的文件,您可以使用 cp 或 scp 等文件复制命令将其写出。您还可以使用 makedumpfile 实用程序来分析和写出带有选项的过滤内容,例如,使用“-d 31”将只写出内核数据。此外,您可以使用 GNU 调试器 (GDB) 和 Crash 工具等分析工具来调试转储文件。此方法确保转储页面正确排序。
设置和安装¶
安装 kexec-tools¶
以 root 用户身份登录。
从以下 URL 下载 kexec-tools 用户空间软件包
https://linuxkernel.org.cn/pub/linux/utils/kernel/kexec/kexec-tools.tar.gz
这是指向最新版本的符号链接。
最新的 kexec-tools git 树可在以下网址获得
git://git.kernel.org/pub/scm/utils/kernel/kexec/kexec-tools.git
https://linuxkernel.org.cn/pub/scm/utils/kernel/kexec/kexec-tools.git
还有一个 gitweb 界面可用,网址为 https://linuxkernel.org.cn/git/?p=utils/kernel/kexec/kexec-tools.git
有关 kexec-tools 的更多信息,请访问 http://horms.net/projects/kexec/
使用 tar 命令解压 tarball,如下所示
tar xvpzf kexec-tools.tar.gz
更改到 kexec-tools 目录,如下所示
cd kexec-tools-VERSION
配置软件包,如下所示
./configure
编译软件包,如下所示
make
安装软件包,如下所示
make install
构建系统和转储捕获内核¶
使用 Kdump 有两种可能的方法。
构建一个单独的自定义转储捕获内核来捕获内核核心转储。
或者使用系统内核二进制文件本身作为转储捕获内核,并且无需构建单独的转储捕获内核。这仅适用于支持可重定位内核的架构。截至今天,i386、x86_64、ppc64、arm 和 arm64 架构支持可重定位内核。
从不需要构建第二个内核来捕获转储的角度来看,构建可重定位内核是有利的。但与此同时,您可能希望构建适合您需求的自定义转储捕获内核。
以下是用于启用 kdump 支持的系统和转储捕获内核所需的配置设置。
系统内核配置选项¶
在“处理器类型和功能”中启用“kexec 系统调用”或“基于文件的 kexec 系统调用”。
CONFIG_KEXEC=y or CONFIG_KEXEC_FILE=y
它们都会选择 KEXEC_CORE
CONFIG_KEXEC_CORE=y
在“文件系统”->“伪文件系统”中启用“sysfs 文件系统支持”。这通常默认启用
CONFIG_SYSFS=y
请注意,如果“通用设置”中未启用“配置标准内核功能(专家用户)”,则“sysfs 文件系统支持”可能不会出现在“伪文件系统”菜单中。在这种情况下,请检查 .config 文件本身以确保 sysfs 已打开,如下所示
grep 'CONFIG_SYSFS' .config
在“内核黑客”中启用“使用调试信息编译内核”。
CONFIG_DEBUG_INFO=Y
这会导致内核使用调试符号构建。转储分析工具需要带有调试符号的 vmlinux 才能读取和分析转储文件。
转储捕获内核配置选项(与架构无关)¶
在“处理器类型和功能”下启用“内核崩溃转储”支持
CONFIG_CRASH_DUMP=y
- 这将选择 VMCORE_INFO 和 CRASH_RESERVE:
CONFIG_VMCORE_INFO=y CONFIG_CRASH_RESERVE=y
在“文件系统”->“伪文件系统”下启用“/proc/vmcore 支持”
CONFIG_PROC_VMCORE=y
(选择 CONFIG_CRASH_DUMP 时,默认情况下会设置 CONFIG_PROC_VMCORE。)
转储捕获内核配置选项(与架构相关,i386 和 x86_64)¶
在 i386 上,在“处理器类型和功能”下启用高内存支持
CONFIG_HIGHMEM64G=y
或
CONFIG_HIGHMEM4G
使用 CONFIG_SMP=y,通常在加载转储捕获内核时需要在内核命令行上指定 nr_cpus=1,因为一个 CPU 就足以让 kdump 内核在大多数系统上转储 vmcore。
但是,您也可以指定 nr_cpus=X 以在 kdump 内核中启用多个处理器。
使用 CONFIG_SMP=n,以上内容不相关。
建议默认构建可重定位内核。如果尚未启用,请在“处理器类型和功能”下启用“构建可重定位内核”支持
CONFIG_RELOCATABLE=y
使用“内核加载的物理地址”(在“处理器类型和功能”下)的合适值。仅当启用“内核崩溃转储”时,才会出现此选项。合适的值取决于内核是否可重定位。
如果您使用的是可重定位内核,请使用 CONFIG_PHYSICAL_START=0x100000。这将为物理地址 1MB 编译内核,但考虑到内核是可重定位的,它可以从任何物理地址运行,因此 kexec 引导加载程序会将其加载到为转储捕获内核保留的内存区域中。
否则,它应该是使用启动参数 “crashkernel=Y@X” 为第二个内核预留的内存区域的起始位置。这里 X 是为转储捕获内核预留的内存区域的起始位置。通常 X 是 16MB (0x1000000)。因此,您可以设置 CONFIG_PHYSICAL_START=0x1000000
编译并安装内核及其模块。不要将此内核添加到启动加载程序配置文件中。
转储捕获内核配置选项(架构相关,ppc64)¶
在“内核”选项下启用“构建 kdump 崩溃内核”支持
CONFIG_CRASH_DUMP=y
启用“构建可重定位内核”支持
CONFIG_RELOCATABLE=y
编译并安装内核及其模块。
转储捕获内核配置选项(架构相关,arm)¶
要使用可重定位内核,请在“启动”选项下启用“AUTO_ZRELADDR”支持
AUTO_ZRELADDR=y
转储捕获内核配置选项(架构相关,arm64)¶
请注意,即使配置了转储捕获内核的 kvm,在非 VHE 系统上也不会启用它。这是因为 CPU 不会在发生 panic 时重置为 EL2。
crashkernel 语法¶
-
这里,“大小” 指定为转储捕获内核预留多少内存,“偏移量” 指定此预留内存的起始位置。例如,“crashkernel=64M@16M” 告诉系统内核为转储捕获内核预留从物理地址 0x01000000 (16MB) 开始的 64 MB 内存。
crashkernel 区域可以由系统内核在运行时自动放置。这可以通过将基地址指定为 0 或完全省略它来实现
crashkernel=256M@0
或
crashkernel=256M
如果指定了起始地址,请注意内核的起始地址将与一个值(取决于架构)对齐,因此如果起始地址不对齐,则对齐点以下的任何空间都将被浪费。
range1:size1[,range2:size2,...][@offset]
虽然 “crashkernel=size[@offset]” 语法足以满足大多数配置,但有时让预留内存依赖于系统 RAM 的值会很方便 -- 这主要是为分发版预先设置内核命令行,以避免在从机器中移除一些内存后出现无法启动的系统。
语法是
crashkernel=<range1>:<size1>[,<range2>:<size2>,...][@offset] range=start-[end]
例如
crashkernel=512M-2G:64M,2G-:128M
这将意味着
如果 RAM 小于 512M,则不预留任何内容(这是 “rescue” 情况)
如果 RAM 大小在 512M 和 2G 之间(不包括 2G),则预留 64M
如果 RAM 大小大于 2G,则预留 128M
crashkernel=size,high 和 crashkernel=size,low
如果首选 4G 以上的内存,可以使用 crashkernel=size,high 来实现。使用它,允许从顶部分配物理内存,因此如果系统安装了超过 4G 的 RAM,则可能高于 4G。否则,如果可用,内存区域将在 4G 以下分配。
当传递 crashkernel=X,high 时,内核可以在 4G 以上分配物理内存区域,在这种情况下需要 4G 以下的低内存。有三种方法可以获取低内存
如果没有指定 crashkernel=Y,low,内核将自动在 4G 以下分配至少 256M 的内存。
让用户指定低内存大小。
指定值 0 将禁用低内存分配
crashkernel=0,low
启动到系统内核¶
根据需要更新启动加载程序(例如 grub、yaboot 或 lilo)配置文件。
使用启动参数 “crashkernel=Y@X” 启动系统内核。
在 x86 和 x86_64 上,使用 “crashkernel=Y[@X]”。大多数情况下,起始地址 “X” 不是必需的,内核将搜索合适的区域。除非需要显式的起始地址。
在 ppc64 上,使用 “crashkernel=128M@32M”。
在 s390x 上,通常使用 “crashkernel=xxM”。xx 的值取决于 kdump 系统的内存消耗。一般来说,这不取决于生产系统的内存大小。
在 arm 上,不再需要使用 “crashkernel=Y@X”;如果未给定 X,内核将自动在 RAM 的前 512MB 内找到崩溃内核映像。
在 arm64 上,使用 “crashkernel=Y[@X]”。请注意,内核的起始地址 X(如果显式指定)必须与 2MiB (0x200000) 对齐。
加载转储捕获内核¶
启动到系统内核后,需要加载转储捕获内核。
根据架构和映像类型(可重定位或不可重定位),可以选择加载转储捕获内核的未压缩 vmlinux 或压缩 bzImage/vmlinuz。以下是摘要。
对于 i386 和 x86_64
如果内核是可重定位的,则使用 bzImage/vmlinuz。
如果内核不可重定位,则使用 vmlinux。
对于 ppc64
使用 vmlinux
对于 s390x
使用 image 或 bzImage
对于 arm
使用 zImage
对于 arm64
使用 vmlinux 或 Image
如果您使用的是未压缩的 vmlinux 映像,则使用以下命令加载转储捕获内核
kexec -p <dump-capture-kernel-vmlinux-image> \
--initrd=<initrd-for-dump-capture-kernel> --args-linux \
--append="root=<root-dev> <arch-specific-options>"
如果您使用的是压缩的 bzImage/vmlinuz,则使用以下命令加载转储捕获内核
kexec -p <dump-capture-kernel-bzImage> \
--initrd=<initrd-for-dump-capture-kernel> \
--append="root=<root-dev> <arch-specific-options>"
如果您使用的是压缩的 zImage,则使用以下命令加载转储捕获内核
kexec --type zImage -p <dump-capture-kernel-bzImage> \
--initrd=<initrd-for-dump-capture-kernel> \
--dtb=<dtb-for-dump-capture-kernel> \
--append="root=<root-dev> <arch-specific-options>"
如果您使用的是未压缩的 Image,则使用以下命令加载转储捕获内核
kexec -p <dump-capture-kernel-Image> \
--initrd=<initrd-for-dump-capture-kernel> \
--append="root=<root-dev> <arch-specific-options>"
以下是加载转储捕获内核时要使用的特定于架构的命令行选项。
对于 i386 和 x86_64
“1 irqpoll nr_cpus=1 reset_devices”
对于 ppc64
“1 maxcpus=1 noirqdistrib reset_devices”
对于 s390x
“1 nr_cpus=1 cgroup_disable=memory”
对于 arm
“1 maxcpus=1 reset_devices”
对于 arm64
“1 nr_cpus=1 reset_devices”
有关加载转储捕获内核的注意事项
默认情况下,ELF 标头以 ELF64 格式存储,以支持内存超过 4GB 的系统。在 i386 上,kexec 会自动检查物理 RAM 大小是否超过 4 GB 限制,如果未超过,则使用 ELF32。因此,在非 PAE 系统上,始终使用 ELF32。
可以使用 --elf32-core-headers 选项来强制生成 ELF32 标头。这是必要的,因为 GDB 目前无法在 32 位系统上打开具有 ELF64 标头的 vmcore 文件。
“irqpoll” 启动参数减少了由于转储捕获内核中的共享中断而导致的驱动程序初始化失败。
您必须以与 mount 命令输出中的根设备名称相对应的格式指定 <root-dev>。
启动参数 “1” 将转储捕获内核引导到单用户模式,而无需网络。如果要使用网络,请使用 “3”。
我们通常不必启动 SMP 内核来捕获转储。因此,通常最好构建 UP 转储捕获内核或在加载转储捕获内核时指定 maxcpus=1 选项。请注意,虽然 maxcpus 始终有效,但如果当前 ARCH(例如 x86)支持,您最好将其替换为 nr_cpus 以节省内存。
如果您打算将其与多线程程序一起使用,例如 makedumpfile 的并行转储功能,则应在转储捕获内核中启用多 CPU 支持。否则,多线程程序可能会出现严重的性能下降。要启用多 CPU 支持,您应该启动一个 SMP 转储捕获内核,并在加载时指定 maxcpus/nr_cpus 选项。
对于 s390x,有两种 kdump 模式:如果使用 elfcorehdr= 内核参数指定了 ELF 标头,则 kdump 内核会像在所有其他架构上一样使用它。如果未指定 elfcorehdr= 内核参数,则 s390x kdump 内核会动态创建标头。第二种模式的优点是对于 CPU 和内存热插拔,不必使用 kexec_load() 重新加载 kdump。
对于具有许多连接设备的 s390x 系统,应该为 kdump 内核使用 “cio_ignore” 内核参数,以防止为与 kdump 无关的设备分配内核内存。这同样适用于使用 SCSI/FCP 设备的系统。在这种情况下,在使 FCP 设备联机之前,应将 “allow_lun_scan” zfcp 模块参数设置为零。
内核 Panic¶
如前所述成功加载转储捕获内核后,如果触发系统崩溃,系统将重新启动到转储捕获内核。触发点位于 panic()
、die()、die_nmi() 和 sysrq 处理程序 (ALT-SysRq-c) 中。
以下情况将执行崩溃触发点
如果检测到硬锁定并且配置了 “NMI 监视程序”,系统将启动到转储捕获内核 ( die_nmi() )。
如果调用 die(),并且恰好是 pid 为 0 或 1 的线程,或者在中断上下文中调用 die(),或者调用 die() 并且设置了 panic_on_oops,系统将启动到转储捕获内核。
在 powerpc 系统上,当生成软复位时,所有 cpu 都会调用 die(),系统将启动到转储捕获内核。
出于测试目的,您可以使用 “ALT-SysRq-c”、“echo c > /proc/sysrq-trigger” 或编写模块来强制 panic 来触发崩溃。
写出转储文件¶
在启动转储捕获内核后,使用以下命令写出转储文件
cp /proc/vmcore <dump-file>
或者使用 scp 在网络上的主机之间写出转储文件,例如
scp /proc/vmcore remote_username@remote_ip:<dump-file>
您还可以使用 makedumpfile 实用程序写出具有指定选项的转储文件以过滤掉不需要的内容,例如
makedumpfile -l --message-level 1 -d 31 /proc/vmcore <dump-file>
分析¶
在分析转储映像之前,您应该重新启动到一个稳定的内核。
您可以使用 GDB 对 /proc/vmcore 中复制出来的转储文件进行有限的分析。使用使用 -g 构建的调试 vmlinux 并运行以下命令
gdb vmlinux <dump-file>
处理器 0 上任务的堆栈跟踪、寄存器显示和内存显示工作正常。
注意:GDB 无法分析 x86 的 ELF64 格式生成的 core 文件。在内存最大为 4GB 的系统上,您可以使用转储内核上的 --elf32-core-headers 内核选项生成 ELF32 格式的标头。
您还可以使用 Crash 实用程序来分析 Kdump 格式的转储文件。Crash 可在以下 URL 中找到
- Crash 文档可以在以下网址找到
在 WARN() 上触发 Kdump¶
内核参数 panic_on_warn 会在所有 WARN() 路径中调用 panic()
。这将导致在 panic()
调用时发生 kdump。如果用户想要在运行时指定此项,可以将 /proc/sys/kernel/panic_on_warn 设置为 1 以实现相同的行为。
在 add_taint() 上触发 Kdump¶
内核参数 panic_on_taint 允许在 add_taint()
函数内部有条件地调用 panic()
,前提是此位掩码中设置的值与 add_taint()
设置的位标志相匹配。 这将导致在 add_taint()
-> panic()
调用时发生 kdump。
联系方式¶
GDB 宏¶
# # This file contains a few gdb macros (user defined commands) to extract # useful information from kernel crashdump (kdump) like stack traces of # all the processes or a particular process and trapinfo. # # These macros can be used by copying this file in .gdbinit (put in home # directory or current directory) or by invoking gdb command with # --command=<command-file-name> option # # Credits: # Alexander Nyberg <[email protected]> # V Srivatsa <[email protected]> # Maneesh Soni <[email protected]> # define bttnobp set $tasks_off=((size_t)&((struct task_struct *)0)->tasks) set $pid_off=((size_t)&((struct task_struct *)0)->thread_group.next) set $init_t=&init_task set $next_t=(((char *)($init_t->tasks).next) - $tasks_off) set var $stacksize = sizeof(union thread_union) while ($next_t != $init_t) set $next_t=(struct task_struct *)$next_t printf "\npid %d; comm %s:\n", $next_t.pid, $next_t.comm printf "===================\n" set var $stackp = $next_t.thread.sp set var $stack_top = ($stackp & ~($stacksize - 1)) + $stacksize while ($stackp < $stack_top) if (*($stackp) > _stext && *($stackp) < _sinittext) info symbol *($stackp) end set $stackp += 4 end set $next_th=(((char *)$next_t->thread_group.next) - $pid_off) while ($next_th != $next_t) set $next_th=(struct task_struct *)$next_th printf "\npid %d; comm %s:\n", $next_t.pid, $next_t.comm printf "===================\n" set var $stackp = $next_t.thread.sp set var $stack_top = ($stackp & ~($stacksize - 1)) + stacksize while ($stackp < $stack_top) if (*($stackp) > _stext && *($stackp) < _sinittext) info symbol *($stackp) end set $stackp += 4 end set $next_th=(((char *)$next_th->thread_group.next) - $pid_off) end set $next_t=(char *)($next_t->tasks.next) - $tasks_off end end document bttnobp dump all thread stack traces on a kernel compiled with !CONFIG_FRAME_POINTER end define btthreadstack set var $pid_task = $arg0 printf "\npid %d; comm %s:\n", $pid_task.pid, $pid_task.comm printf "task struct: " print $pid_task printf "===================\n" set var $stackp = $pid_task.thread.sp set var $stacksize = sizeof(union thread_union) set var $stack_top = ($stackp & ~($stacksize - 1)) + $stacksize set var $stack_bot = ($stackp & ~($stacksize - 1)) set $stackp = *((unsigned long *) $stackp) while (($stackp < $stack_top) && ($stackp > $stack_bot)) set var $addr = *(((unsigned long *) $stackp) + 1) info symbol $addr set $stackp = *((unsigned long *) $stackp) end end document btthreadstack dump a thread stack using the given task structure pointer end define btt set $tasks_off=((size_t)&((struct task_struct *)0)->tasks) set $pid_off=((size_t)&((struct task_struct *)0)->thread_group.next) set $init_t=&init_task set $next_t=(((char *)($init_t->tasks).next) - $tasks_off) while ($next_t != $init_t) set $next_t=(struct task_struct *)$next_t btthreadstack $next_t set $next_th=(((char *)$next_t->thread_group.next) - $pid_off) while ($next_th != $next_t) set $next_th=(struct task_struct *)$next_th btthreadstack $next_th set $next_th=(((char *)$next_th->thread_group.next) - $pid_off) end set $next_t=(char *)($next_t->tasks.next) - $tasks_off end end document btt dump all thread stack traces on a kernel compiled with CONFIG_FRAME_POINTER end define btpid set var $pid = $arg0 set $tasks_off=((size_t)&((struct task_struct *)0)->tasks) set $pid_off=((size_t)&((struct task_struct *)0)->thread_group.next) set $init_t=&init_task set $next_t=(((char *)($init_t->tasks).next) - $tasks_off) set var $pid_task = 0 while ($next_t != $init_t) set $next_t=(struct task_struct *)$next_t if ($next_t.pid == $pid) set $pid_task = $next_t end set $next_th=(((char *)$next_t->thread_group.next) - $pid_off) while ($next_th != $next_t) set $next_th=(struct task_struct *)$next_th if ($next_th.pid == $pid) set $pid_task = $next_th end set $next_th=(((char *)$next_th->thread_group.next) - $pid_off) end set $next_t=(char *)($next_t->tasks.next) - $tasks_off end btthreadstack $pid_task end document btpid backtrace of pid end define trapinfo set var $pid = $arg0 set $tasks_off=((size_t)&((struct task_struct *)0)->tasks) set $pid_off=((size_t)&((struct task_struct *)0)->thread_group.next) set $init_t=&init_task set $next_t=(((char *)($init_t->tasks).next) - $tasks_off) set var $pid_task = 0 while ($next_t != $init_t) set $next_t=(struct task_struct *)$next_t if ($next_t.pid == $pid) set $pid_task = $next_t end set $next_th=(((char *)$next_t->thread_group.next) - $pid_off) while ($next_th != $next_t) set $next_th=(struct task_struct *)$next_th if ($next_th.pid == $pid) set $pid_task = $next_th end set $next_th=(((char *)$next_th->thread_group.next) - $pid_off) end set $next_t=(char *)($next_t->tasks.next) - $tasks_off end printf "Trapno %ld, cr2 0x%lx, error_code %ld\n", $pid_task.thread.trap_no, \ $pid_task.thread.cr2, $pid_task.thread.error_code end document trapinfo Run info threads and lookup pid of thread #1 'trapinfo <pid>' will tell you by which trap & possibly address the kernel panicked. end define dump_record set var $desc = $arg0 set var $info = $arg1 if ($argc > 2) set var $prev_flags = $arg2 else set var $prev_flags = 0 end set var $prefix = 1 set var $newline = 1 set var $begin = $desc->text_blk_lpos.begin % (1U << prb->text_data_ring.size_bits) set var $next = $desc->text_blk_lpos.next % (1U << prb->text_data_ring.size_bits) # handle data-less record if ($begin & 1) set var $text_len = 0 set var $log = "" else # handle wrapping data block if ($begin > $next) set var $begin = 0 end # skip over descriptor id set var $begin = $begin + sizeof(long) # handle truncated message if ($next - $begin < $info->text_len) set var $text_len = $next - $begin else set var $text_len = $info->text_len end set var $log = &prb->text_data_ring.data[$begin] end # prev & LOG_CONT && !(info->flags & LOG_PREIX) if (($prev_flags & 8) && !($info->flags & 4)) set var $prefix = 0 end # info->flags & LOG_CONT if ($info->flags & 8) # (prev & LOG_CONT && !(prev & LOG_NEWLINE)) if (($prev_flags & 8) && !($prev_flags & 2)) set var $prefix = 0 end # (!(info->flags & LOG_NEWLINE)) if (!($info->flags & 2)) set var $newline = 0 end end if ($prefix) printf "[%5lu.%06lu] ", $info->ts_nsec / 1000000000, $info->ts_nsec % 1000000000 end if ($text_len) eval "printf \"%%%d.%ds\", $log", $text_len, $text_len end if ($newline) printf "\n" end # handle dictionary data set var $dict = &$info->dev_info.subsystem[0] set var $dict_len = sizeof($info->dev_info.subsystem) if ($dict[0] != '\0') printf " SUBSYSTEM=" set var $idx = 0 while ($idx < $dict_len) set var $c = $dict[$idx] if ($c == '\0') loop_break else if ($c < ' ' || $c >= 127 || $c == '\\') printf "\\x%02x", $c else printf "%c", $c end end set var $idx = $idx + 1 end printf "\n" end set var $dict = &$info->dev_info.device[0] set var $dict_len = sizeof($info->dev_info.device) if ($dict[0] != '\0') printf " DEVICE=" set var $idx = 0 while ($idx < $dict_len) set var $c = $dict[$idx] if ($c == '\0') loop_break else if ($c < ' ' || $c >= 127 || $c == '\\') printf "\\x%02x", $c else printf "%c", $c end end set var $idx = $idx + 1 end printf "\n" end end document dump_record Dump a single record. The first parameter is the descriptor, the second parameter is the info, the third parameter is optional and specifies the previous record's flags, used for properly formatting continued lines. end define dmesg # definitions from kernel/printk/printk_ringbuffer.h set var $desc_committed = 1 set var $desc_finalized = 2 set var $desc_sv_bits = sizeof(long) * 8 set var $desc_flags_shift = $desc_sv_bits - 2 set var $desc_flags_mask = 3 << $desc_flags_shift set var $id_mask = ~$desc_flags_mask set var $desc_count = 1U << prb->desc_ring.count_bits set var $prev_flags = 0 set var $id = prb->desc_ring.tail_id.counter set var $end_id = prb->desc_ring.head_id.counter while (1) set var $desc = &prb->desc_ring.descs[$id % $desc_count] set var $info = &prb->desc_ring.infos[$id % $desc_count] # skip non-committed record set var $state = 3 & ($desc->state_var.counter >> $desc_flags_shift) if ($state == $desc_committed || $state == $desc_finalized) dump_record $desc $info $prev_flags set var $prev_flags = $info->flags end if ($id == $end_id) loop_break end set var $id = ($id + 1) & $id_mask end end document dmesg print the kernel ring buffer end