The kexec-based Crash Dumping Solution (翻译 by chatgpt)

发布时间 2023-12-07 23:35:45作者: 摩斯电码

原文:https://www.kernel.org/doc/html/latest/admin-guide/kdump/kdump.html

这份文档包括概述、设置、安装和分析信息。

概述

Kdump 使用 kexec 快速引导到一个转储捕获内核,每当需要对系统内核的内存进行转储(例如系统发生崩溃)时。系统内核的内存镜像在重启过程中得以保留,并且可以被转储捕获内核访问。

您可以使用常见命令,比如 cp、scp 或 makedumpfile,将内存镜像复制到本地磁盘上的转储文件,或者跨网络复制到远程系统。

Kdump 和 kexec 目前支持 x86、x86_64、ppc64、s390x、arm 和 arm64 架构。

当系统内核引导时,它会为转储捕获内核保留一小部分内存。这确保了系统内核的持续直接内存访问(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

  1. 以 root 用户登录。
  2. 从以下 URL 下载 kexec-tools 用户空间包:

http://kernel.org/pub/linux/utils/kernel/kexec/kexec-tools.tar.gz

这是最新版本的符号链接。

最新的 kexec-tools git 树可在以下位置找到:

还有一个可用的 gitweb 接口,位于 http://www.kernel.org/git/?p=utils/kernel/kexec/kexec-tools.git

有关 kexec-tools 的更多信息,请访问 http://horms.net/projects/kexec/

  1. 使用 tar 命令解压 tarball,如下所示:
    tar xvpzf kexec-tools.tar.gz
  1. 进入 kexec-tools 目录,如下所示:
    cd kexec-tools-VERSION
  1. 配置包,如下所示:
    ./configure
  1. 编译包,如下所示:
    make
  1. 安装包,如下所示:
    make install

构建系统和转储捕获内核

有两种可能的方法可以使用 Kdump。

  1. 为捕获内核核心转储构建一个单独的自定义转储捕获内核。

  2. 或者使用系统内核二进制本身作为转储捕获内核,无需构建单独的转储捕获内核。这仅适用于支持可重定位内核的架构。截至目前,i386、x86_64、ppc64、arm 和 arm64 架构支持可重定位内核。

从系统和转储捕获内核启用 kdump 支持所需的配置设置如下。

系统内核配置选项

  1. 在“处理器类型和特性”中启用“kexec 系统调用”或“基于文件的 kexec 系统调用”:

    CONFIG_KEXEC=y 或 CONFIG_KEXEC_FILE=y

    两者都会选择 KEXEC_CORE:

    CONFIG_KEXEC_CORE=y

    随后,KEXEC_CORE 会选择 CRASH_CORE:

    CONFIG_CRASH_CORE=y

  2. 在“文件系统” -> “伪文件系统”中启用“sysfs 文件系统支持”。这通常是默认启用的:

    CONFIG_SYSFS=y

    请注意,“伪文件系统”中可能不会出现“sysfs 文件系统支持”,如果在“一般设置”中未启用“配置标准内核特性(专家用户)”。在这种情况下,请检查 .config 文件本身,确保 sysfs 被打开,如下所示:

    grep 'CONFIG_SYSFS' .config

  3. 在“内核调试”中启用“使用调试信息编译内核”:

    CONFIG_DEBUG_INFO=Y

    这将导致内核使用调试符号进行构建。转储分析工具需要一个带有调试符号的 vmlinux 才能读取和分析转储文件。

转储捕获内核配置选项(与架构无关)

  1. 在“处理器类型和特性”下启用“内核崩溃转储”支持:

    CONFIG_CRASH_DUMP=y

  2. 在“文件系统” -> “伪文件系统”下启用“/proc/vmcore 支持”:

    CONFIG_PROC_VMCORE=y

    (当选择 CONFIG_CRASH_DUMP 时,默认设置 CONFIG_PROC_VMCORE。)

转储捕获内核配置选项(与架构相关,i386 和 x86_64)

  1. 在 i386 上,启用“高内存支持”:

    CONFIG_HIGHMEM64G=y

    或:

    CONFIG_HIGHMEM4G

  2. 使用 CONFIG_SMP=y 时,通常需要在加载转储捕获内核时在内核命令行中指定 nr_cpus=1,因为一个 CPU 对于 kdump 内核来说足够进行 vmcore 转储。

    但是,您也可以指定 nr_cpus=X 以在 kdump 内核中启用多个处理器。在这种情况下,需要使用 "disable_cpu_apicid=" 告诉 kdump 内核哪个 CPU 是第一个内核的 BSP。请参阅 admin-guide/kernel-parameters.txt 了解更多详情。

    使用 CONFIG_SMP=n 时,上述内容与之无关。

  3. 建议默认构建可重定位内核。如果尚未启用,请在“处理器类型和特性”下启用“构建可重定位内核”支持:

    CONFIG_RELOCATABLE=y

  4. 使用适当的值设置“内核加载的物理地址”(在“处理器类型和特性”下)。只有在启用“内核崩溃转储”时才会出现此选项。适当的值取决于内核是否可重定位。

    如果您使用可重定位内核,请使用 CONFIG_PHYSICAL_START=0x100000。这将为物理地址 1MB 编译内核,但鉴于内核是可重定位的事实,它可以从任何物理地址运行,因此 kexec 引导加载程序将其加载到为转储捕获内核保留的内存区域中。

    否则,它应该是用于第二个内核保留内存区域的起始位置,使用引导参数 "crashkernel=Y@X"。这里 X 是用于转储捕获内核保留内存区域的起始位置。通常 X 是 16MB(0x1000000)。因此,您可以设置 CONFIG_PHYSICAL_START=0x1000000

  5. 制作并安装内核及其模块。不要将此内核添加到引导加载程序配置文件中。

转储捕获内核配置选项(与架构相关,ppc64)

  1. 在“内核”选项下启用“构建 kdump 崩溃内核”支持:

    CONFIG_CRASH_DUMP=y

  2. 启用“构建可重定位内核”支持:

    CONFIG_RELOCATABLE=y

    制作并安装内核及其模块。

转储捕获内核配置选项(与架构相关,arm)

  • 要使用可重定位内核,请在“引导”选项下启用“AUTO_ZRELADDR”支持:

    AUTO_ZRELADDR=y

转储捕获内核配置选项(与架构相关,arm64)

  • 请注意,即使配置了,非 VHE 系统上的转储捕获内核的 kvm 也不会被启用。这是因为 CPU 在发生紧急情况时不会重置为 EL2。

crashkernel 语法

  1. crashkernel=size@offset

    这里的 'size' 指定了为转储捕获内核保留多少内存,'offset' 指定了这个保留内存的起始位置。例如,“crashkernel=64M@16M” 告诉系统内核保留 64 MB 内存,起始物理地址为 0x01000000(16MB)用于转储捕获内核。

    crashkernel 区域可以由系统内核在运行时自动放置。这是通过指定基地址为 0,或者完全省略来完成的:

    crashkernel=256M@0
    

    或:

    crashkernel=256M
    

    如果指定了起始地址,请注意内核的起始地址将对齐到一个值(这取决于架构),因此如果起始地址不是,则对齐点下方的任何空间将被浪费。

  2. range1:size1[,range2:size2,...][@offset]

    虽然“crashkernel=size[@offset]”语法对于大多数配置来说已经足够了,但有时根据系统 RAM 的值来保留内存是很方便的 -- 这主要是为了避免在从机器中移除一些内存后导致系统无法引导。

    语法是:

    crashkernel=<range1>:<size1>[,<range2>:<size2>,...][@offset]
    range=start-[end]
    

    例如:

    crashkernel=512M-2G:64M,2G-:128M
    

    这意味着:
    1. 如果 RAM 小于 512M,则不保留任何内存(这是“救援”情况)
    2. 如果 RAM 大小在 512M 和 2G 之间(不包括 2G),则保留 64M
    3. 如果 RAM 大于 2G,则保留 128M

  3. crashkernel=size,high 和 crashkernel=size,low

    如果希望使用 4G 以上的内存,可以使用 crashkernel=size,high 来实现。使用它,物理内存可以从顶部分配,因此如果系统安装了超过 4G 的 RAM,它可以位于 4G 以上。否则,如果可用,内存区域将在 4G 以下分配。

    当传递 crashkernel=X,high 时,内核可以分配 4G 以上的物理内存区域,在这种情况下需要 4G 以下的低内存。有三种方法可以获得低内存:

    1. 如果未指定 crashkernel=Y,low,则内核将自动分配至少 256M 低内存。
    2. 让用户指定低内存大小。
    3. 指定值 0 将禁用低内存分配:
          crashkernel=0,low
      

引导到系统内核

  1. 根据需要更新引导加载程序(如 grub、yaboot 或 lilo)配置文件。

  2. 使用引导参数“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)。

将转储捕获内核加载

在引导到系统内核后,需要加载转储捕获内核。

根据体系结构和镜像类型(可重定位或不可重定位),可以选择加载未压缩的 dump-capture 内核的 vmlinux 或压缩的 bzImage/vmlinuz。以下是摘要。

对于 i386 和 x86_64:

  • 如果内核是可重定位的,请使用 bzImage/vmlinuz。
  • 如果内核不可重定位,请使用 vmlinux。

对于 ppc64:

  • 请使用 vmlinux。

对于 s390x:

  • 请使用 image 或 bzImage。

对于 arm:

  • 请使用 zImage。

对于 arm64:

  • 请使用 vmlinux 或 Image。

如果您使用未压缩的 vmlinux 镜像,则使用以下命令加载 dump-capture 内核:

kexec -p <dump-capture-kernel-vmlinux-image> \
--initrd=<initrd-for-dump-capture-kernel> --args-linux \
--append="root=<root-dev> <arch-specific-options>"

如果您使用压缩的 bzImage/vmlinuz,则使用以下命令加载 dump-capture 内核:

kexec -p <dump-capture-kernel-bzImage> \
--initrd=<initrd-for-dump-capture-kernel> \
--append="root=<root-dev> <arch-specific-options>"

如果您使用压缩的 zImage,则使用以下命令加载 dump-capture 内核:

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,则使用以下命令加载 dump-capture 内核:

kexec -p <dump-capture-kernel-Image> \
--initrd=<initrd-for-dump-capture-kernel> \
--append="root=<root-dev> <arch-specific-options>"

以下是加载 dump-capture 内核时要使用的特定于体系结构的命令行选项。

对于 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 大小是否超过 4GB 限制,如果没有,则使用 ELF32。因此,在非 PAE 系统上,始终使用 ELF32。
  • 可以使用 --elf32-core-headers 选项强制生成 ELF32 头。这是必要的,因为当前 GDB 无法在 32 位系统上打开具有 ELF64 头的 vmcore 文件。
  • "irqpoll" 引导参数减少了转储捕获内核中由于共享中断而导致的驱动程序初始化失败。
  • 您必须以与 mount 命令输出中的根设备名称相对应的格式指定 <root-dev>
  • 引导参数 "1" 将转储捕获内核引导到无网络的单用户模式。如果需要网络,请使用 "3"。
  • 通常我们不必启动 SMP 内核来捕获转储。因此,通常要么构建一个 UP 转储捕获内核,要么在加载转储捕获内核时指定 maxcpus=1 选项。请注意,尽管 maxcpus 总是有效,但最好将其替换为 nr_cpus,以节省内存(如果当前 ARCH 支持,例如 x86)。
  • 如果打算在其中使用多线程程序,例如 makedumpfile 的并行转储功能,则应在转储捕获内核中启用多 CPU 支持。否则,多线程程序可能会出现严重的性能下降。要启用多 CPU 支持,应启动 SMP 转储捕获内核,并在加载时指定 maxcpus/nr_cpus、disable_cpu_apicid=[X] 选项。
  • 对于 s390x,有两种 kdump 模式:如果使用 elfcorehdr= 内核参数指定了 ELF 头,则它将像在所有其他体系结构上一样由 kdump 内核使用。如果未指定 elfcorehdr= 内核参数,则 s390x kdump 内核会动态创建头部。第二种模式的优点是对于 CPU 和内存热插拔,kdump 不必使用 kexec_load() 重新加载。
  • 对于具有许多附加设备的 s390x 系统,应该在 kdump 内核中使用 "cio_ignore" 内核参数,以防止为与 kdump 无关的设备分配内核内存。对于使用 SCSI/FCP 设备的系统也是如此。在这种情况下,应在设置 FCP 设备在线之前将 "allow_lun_scan" zfcp 模块参数设置为零。

内核恐慌

成功加载转储捕获内核后,如果触发系统崩溃,系统将重新启动到转储捕获内核。触发点位于 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" 或编写一个模块来强制触发崩溃。

写出转储文件

转储捕获内核引导后,使用以下命令写出转储文件:

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 构建的 debug vmlinux,并运行以下命令:

gdb vmlinux <dump-file>

处理器 0 上的任务的堆栈跟踪、寄存器显示和内存显示都可以正常工作。

注意:GDB 无法分析在 x86 的 ELF64 格式中生成的核心文件。在最大内存为 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。

联系

  • kexec@lists.infradead.org

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 <alexn@telia.com>
# V Srivatsa <vatsa@in.ibm.com>
# Maneesh Soni <maneesh@in.ibm.com>
#

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