Ramoops oops/panic 日志记录器

Sergiu Iordache <sergiu@chromium.org>

更新:2021 年 2 月 10 日

简介

Ramoops 是一个 oops/panic 日志记录器,它在系统崩溃前将日志写入 RAM。它的工作原理是将 oops 和 panic 记录在循环缓冲区中。Ramoops 需要一个具备持久化 RAM 的系统,以便该区域的内容在重启后能够保留下来。

Ramoops 概念

Ramoops 使用预定义的内存区域来存储转储。内存区域的起始地址、大小和类型是通过以下变量来设置的

  • mem_address 用于指定起始地址

  • mem_size 用于指定大小。内存大小会被向下舍入为 2 的幂次方。

  • mem_type 用于指定内存类型(默认值为 pgprot_writecombine)。

  • mem_name 用于指定由 reserve_mem 命令行参数定义的内存区域。

通常应使用 mem_type=0 的默认值,因为这会将 pstore 映射设置为 pgprot_writecombine。设置 mem_type=1 会尝试使用 pgprot_noncached,这仅在某些平台上有效。这是因为 pstore 依赖于原子操作。至少在 ARM 上,pgprot_noncached 会导致内存被映射为强序(strongly ordered),而对强序内存的原子操作是与实现相关的,在许多 ARM(例如 omap)上无法工作。设置 mem_type=2 会尝试将内存区域视为普通内存,从而在其上启用完整缓存。这可以提高性能。

内存区域被划分为 record_size 大小的块(同样向下舍入为 2 的幂次方),每个 kmesg 转储都会写入一个大小为 record_size 的信息块。

可以通过 max_reason 值来限制存储哪些种类的 kmsg 转储,如 include/linux/kmsg_dump.h 的 enum kmsg_dump_reason 中所定义的。例如,要同时存储 Oops 和 Panic,应将 max_reason 设置为 2 (KMSG_DUMP_OOPS);要仅存储 Panic,max_reason 应设置为 1 (KMSG_DUMP_PANIC)。将其设置为 0 (KMSG_DUMP_UNDEF) 意味着原因过滤将由启动参数 printk.always_kmsg_dump 控制:如果未设置,它将是 KMSG_DUMP_OOPS,否则为 KMSG_DUMP_MAX。

该模块使用一个计数器来记录多个转储,但该计数器会在重启时重置(即重启后的新转储会覆盖旧转储)。

Ramoops 还支持持久化内存区域的软件 ECC 保护。当使用硬件重置来恢复机器(例如触发了看门狗)时,这可能会非常有用。在这种情况下,RAM 可能会有一定程度的损坏,但通常是可以恢复的。

设置参数

可以通过几种不同的方式来设置 ramoops 参数

A. 使用模块参数(其名称与前面描述的变量相同)。为了快速调试,您还可以在引导期间保留部分内存,然后将保留的内存用于 ramoops。例如,假设一台机器拥有 > 128 MB 的内存,以下内核命令行将指示内核仅使用前 128 MB 的内存,并将受 ECC 保护的 ramoops 区域放置在 128 MB 的边界处

mem=128M ramoops.mem_address=0x8000000 ramoops.ecc=1

B. 使用设备树(Device Tree)绑定,如 Documentation/devicetree/bindings/reserved-memory/ramoops.yaml 中所述。例如

reserved-memory {
        #address-cells = <2>;
        #size-cells = <2>;
        ranges;

        ramoops@8f000000 {
                compatible = "ramoops";
                reg = <0 0x8f000000 0 0x100000>;
                record-size = <0x4000>;
                console-size = <0x4000>;
        };
};

C. 使用平台设备并设置平台数据。然后可以通过该平台数据来设置参数。一个这样做的示例如下

#include <linux/pstore_ram.h>
[...]

static struct ramoops_platform_data ramoops_data = {
      .mem_size               = <...>,
      .mem_address            = <...>,
      .mem_type               = <...>,
      .record_size            = <...>,
      .max_reason             = <...>,
      .ecc                    = <...>,
};

static struct platform_device ramoops_dev = {
      .name = "ramoops",
      .dev = {
              .platform_data = &ramoops_data,
      },
};

[... inside a function ...]
int ret;

ret = platform_device_register(&ramoops_dev);
if (ret) {
      printk(KERN_ERR "unable to register platform device\n");
      return ret;
}
  1. 使用通过 reserve_mem 命令行参数保留的内存区域。地址和大小将由 reserve_mem 参数定义。请注意,reserve_mem 可能并不总是在相同的位置分配内存,因此不能完全对其产生依赖。需要进行测试,并且它可能并非在每台机器或每个内核上都能工作。请将其视为一种“尽力而为”的方法。reserve_mem 选项接受大小、对齐方式和名称作为参数。该名称用于将内存映射到一个可由 ramoops 检索的标签。

    reserve_mem=2M:4096:oops ramoops.mem_name=oops

您可以指定 RAM 内存或外设设备的内存。但是,当指定 RAM 时,请务必在架构代码的极早期通过调用 memblock_reserve() 来保留内存,例如

#include <linux/memblock.h>

memblock_reserve(ramoops_data.mem_address, ramoops_data.mem_size);

转储格式

数据转储以一个头部开始,当前定义为 ====,后跟时间戳和换行符。随后转储会继续输出实际数据。

读取数据

可以从 pstore 文件系统中读取转储数据。这些文件的格式为 dmesg-ramoops-N,其中 N 是内存中的记录编号。要从 RAM 中删除存储的记录,只需删除(unlink)相应的 pstore 文件即可。

持久化函数跟踪

持久化函数跟踪对于调试软件或硬件相关的挂起(hang)可能很有用。函数调用链日志存储在 ftrace-ramoops 文件中。以下是使用示例

# mount -t debugfs debugfs /sys/kernel/debug/
# echo 1 > /sys/kernel/debug/pstore/record_ftrace
# reboot -f
[...]
# mount -t pstore pstore /mnt/
# tail /mnt/ftrace-ramoops
0 ffffffff8101ea64  ffffffff8101bcda  native_apic_mem_read <- disconnect_bsp_APIC+0x6a/0xc0
0 ffffffff8101ea44  ffffffff8101bcf6  native_apic_mem_write <- disconnect_bsp_APIC+0x86/0xc0
0 ffffffff81020084  ffffffff8101a4b5  hpet_disable <- native_machine_shutdown+0x75/0x90
0 ffffffff81005f94  ffffffff8101a4bb  iommu_shutdown_noop <- native_machine_shutdown+0x7b/0x90
0 ffffffff8101a6a1  ffffffff8101a437  native_machine_emergency_restart <- native_machine_restart+0x37/0x40
0 ffffffff811f9876  ffffffff8101a73a  acpi_reboot <- native_machine_emergency_restart+0xaa/0x1e0
0 ffffffff8101a514  ffffffff8101a772  mach_reboot_fixups <- native_machine_emergency_restart+0xe2/0x1e0
0 ffffffff811d9c54  ffffffff8101a7a0  __const_udelay <- native_machine_emergency_restart+0x110/0x1e0
0 ffffffff811d9c34  ffffffff811d9c80  __delay <- __const_udelay+0x30/0x40
0 ffffffff811d9d14  ffffffff811d9c3f  delay_tsc <- __delay+0xf/0x20