一加内核 panic 日志获取方法
2026-2-27
| 2026-2-27
字数 4937阅读时长 13 分钟
type
Post
status
Published
date
Feb 27, 2026
slug
oneplus-ace-5-panic-log
summary
探索如何获取一加 Ace 5 设备的内核 panic 日志
tags
思考
category
技术分享
icon
password
Property
Feb 27, 2026 04:28 AM
做安卓内核开发时,kernel panic 是家常便饭。但如果 panic 后拿不到崩溃日志,排查问题就如同盲人摸象——你知道它崩了,却不知道崩在哪里。
在一加 ACE5 上,panic 后 /sys/fs/pstore 始终是空的,无法像常规 Linux 设备那样直接获取上次崩溃的 dmesg。本文记录了我为获取 panic 日志所做的全部尝试:从 ramoops 配置调整,到内核源码级的机制调研,再到最终发现高通 minidump 方案的完整探索过程。

万物的开始

一切从一个简单的建议开始。有朋友告诉我,通过对 ramoops 驱动执行一次 unbind/bind 操作,可以让 /sys/fs/pstore 下面出现文件:
试了一下,文件确实出现了:
但打开一看,内容完全和上一次的崩溃无关——更像是 unbind 那一刻转储的当前 dmesg,而不是 panic 时保存下来的日志。这说明 ramoops 驱动本身是能工作的,但上次 panic 时写入预留内存的数据已经不在了。
问题可能出在 ramoops 的配置上——也许预留的内存区域大小不对,或者缓冲区分配有问题。于是我决定尝试修改 ramoops 的配置参数。

对 ramoops 配置的尝试

先确认一下当前 pstore 使用的后端:
返回的是 ramoops,说明 pstore 确实在用 ramoops 作为后端。那问题大概率出在 ramoops 的缓冲区配置上。ramoops 有几个关键参数:record_size(单条 oops/panic 记录大小)、console_size(控制台日志大小)、pmsg_size(用户空间 pmsg 大小)。如果 record_size 为 0,panic 日志就不会被保存。

cmdline 修改

最直接的想法是通过内核命令行参数覆盖 ramoops 配置。安卓的 boot image header 中有 cmdline 字段,可以通过 magiskboot 修改。
注意:magiskboot 需要使用 30.7 及以上版本,老版本在解析 -h 参数时有 bug。
首先尝试修改 init_boot 分区:
在 header 文件中添加 ramoops 参数:
刷入重启后,检查 /proc/cmdline——没有任何变化。看来 init_boot 的 cmdline 没有被引导程序采用。
换一个思路,改 boot 分区:
这次刷入重启后,/proc/cmdline 里确实出现了我添加的参数。但检查 ramoops 的实际参数,发现它们完全没有变化。
阅读内核源码后找到了原因:安卓内核中 ramoops 的模块参数被标记为只读(module_param 使用 0444 权限),cmdline 中的参数只是被内核解析了,但 ramoops 驱动并不从 cmdline 读取配置——它的配置完全来自设备树(Device Tree)

dtbo 修改

既然 cmdline 行不通,就得从设备树下手。
在安卓设备上,设备树通常分为两部分:DTB(基础设备树,编译进 boot image)和 DTBO(Device Tree Blob Overlay,设备树叠加层,位于独立的 dtbo 分区)。DTBO 的设计初衷是让厂商在不修改基础 DTB 的情况下,通过叠加层来定制硬件配置。ramoops 的内存区域配置通常就放在 DTBO 中。
首先从设备中提取 dtbo 分区镜像:
拉到本地:
DTBO 镜像是一个打包了多个 DTB overlay 的容器,需要专门的工具来解包。这里使用 mkdtimg:
解包 DTBO 镜像,将每个 overlay 反编译为可读的 DTS(Device Tree Source)格式,然后搜索 ramoops 相关的配置:
果然找到了。在其中一个 overlay 的 fragment@31 中,有 ramoops 预留内存区域的配置。原始配置如下:
可以看到问题所在:整个预留区域只有 0x240000(2.25MB),其中 pmsg 占了 0x200000(2MB),console 占了 0x40000(256KB),而 record-size 根本没有定义——这意味着 panic 时的 oops/panic 记录没有分配任何空间。
修改后的配置,压缩 pmsg 空间,为 console 和 record 各分配 1MB:
将修改后的 DTS 重新编译为 DTB,再打包回 DTBO 镜像:
检查新镜像的结构是否正确:
确认无误后刷入设备,重启验证参数是否生效:
参数确实变了!console_sizerecord_size 都变成了预期的值。看起来这次应该能行了。
然而,手动触发一次 panic 后重启,/sys/fs/pstore 下面依然空空如也。
ramoops 参数已经正确,驱动也正常加载,panic 也确实发生了——但日志就是不见了。具体原因尚不明确,可能的方向包括:硬件层面 DRAM 在掉电-上电周期中无法保持内容(mem-type = 0x02 表示使用的是普通 DRAM 而非 NVRAM)、bootloader 在启动早期主动清零了这片预留内存区域、或者 ramoops 驱动在该平台上存在其他未知的兼容性问题。
ramoops 这条路暂时走不通了,需要寻找其他方案。

自研?

既然 ramoops 靠不住,那不如自己动手——写一个内核模块,在 panic 时把日志直接写入 UFS 的某个分区。

以 mtdoops 为蓝本

内核中有一个现成的参考实现:mtdoops。它是一个独立的 kmsg dumper,在 panic 时将 printk ring buffer 中的日志写入 MTD 设备。我最初的想法很简单:参考 mtdoops 的源码架构,写一份类似的模块,把目标设备从 MTD 换成 /dev/block/by-name/logdump 分区。
不过在动手之前,有一个前提需要验证:logdump 分区的数据在重启后是否真的能保留?如果 bootloader 会在启动时清空这个分区,那不管怎么写都是白费。

分区持久化测试

挑两个看起来和 dump 相关的分区(logdumprawdump)做个简单测试:
数据完好无损。logdumprawdump 分区在重启后不会被清空,可以作为持久化目标。
顺手确认一下存储型号:
三星 UFS 4.0 闪存,存储介质可靠。前提条件满足了,接下来就是搞定 panic 时的写入逻辑。

从 MTD 到 block:撞上了墙

然而,当我真正深入 mtdoops 的源码时,才意识到事情没那么简单。
mtdoops 之所以能在 panic 时成功写入,是因为它走的是 MTD 子系统的专用接口 mtd_panic_write()。MTD(Memory Technology Device)面向的是 NOR/NAND Flash 这类原始闪存,驱动可以直接以轮询方式操作硬件寄存器完成擦写,不需要中断、不需要调度器,天然适合 panic 上下文。
但一加 ACE5 的存储是 UFS,它挂载在 SCSI/block 子系统下,和 MTD 完全是两个世界。我不能简单地把 mtdoops 的 mtd_panic_write() 替换成 kernel_write() 写 block 设备——panic 发生时中断已禁用、调度器已停止,整个 block I/O 路径根本不可用
换句话说,mtdoops 的架构只能借鉴思路(注册 kmsg dumper → panic 时从 ring buffer 读日志 → 写入持久化存储),但写入这一步在 UFS 上完全行不通。

转向 pstore/blk

既然自己从头写不现实,那内核有没有现成的框架来解决”panic 时写 block 设备”这个问题?
答案是 pstore/blk——pstore 框架的块设备后端。它的设计目标正是把 panic 日志写入通用块设备分区。但深入源码后发现,它的可用性取决于一个关键条件:存储驱动是否实现了 panic_write 回调
pstore/blk 的 best_effort 模式虽然可以挂载到任意块设备上,但看一下它的写函数实现:
这个函数只注册为普通 write,而不是 panic_write。panic 时 pstore/zone 框架的选择逻辑是:
所以 best_effort 模式在 panic 时只会标记”脏”,期望下次正常启动时回写——但 panic 后系统已经重启,RAM 中的脏标记自然也丢了。
要让 pstore/blk 真正在 panic 时落盘,需要存储驱动主动调用 register_pstore_device(),并提供一个能在中断禁用上下文中以轮询方式直接操作硬件的 panic_write 回调。

UFS 驱动有 panic_write 吗?

这成了关键问题。我让 Claude 帮忙阅读 AOSP android14-6.1 通用内核中 drivers/ufs/ 的全部源码,搜索任何与 panic_writepstorekmsg_dump 相关的实现。
结果:什么都没有。 AOSP 主线的 UFS 驱动完全没有实现 panic 路径下的持久化写入能力。
Claude 提了一个思路:AOSP 通用内核的 UFS 驱动比较精简,但高通的下游 vendor 内核(msm-kernel)中的 ufs-qcom.c 可能有类似的同步写入机制。毕竟高通对自家平台的 UFS 控制器最了解,很可能在 vendor 分支里加了 panic 写入的支持。
于是我让 Claude 转而分析一加 ACE5 的官方内核源码(android_kernel_oneplus_sm8650)中的 UFS 驱动。结果依然令人失望:
关键词
搜索结果
panic_write
未找到
pstore_zone / register_pstore_device
未找到
kmsg_dump
未找到
vendor 内核的 UFS 驱动里唯一和 panic 相关的代码是 ufs-qcom.c 中注册的 panic notifier:
这只是把 UFS 寄存器状态打印到 kernel log 用于调试,和把日志写入 UFS 存储是完全不同的两件事
到这里,纯内核层面的方案基本全部走入了死胡同。问题的本质很清楚:panic 后 block 层不可用,而 UFS 驱动也没有实现绕过 block 层的轮询写机制。谁能在 panic 时直接操作 UFS 硬件?

深入 minidump

纯内核方案走不通,但一加自己肯定有办法拿到 panic 日志。查看设备上加载的内核模块,过滤 dump 相关的:
其中 minidumpqcom_va_minidumpmemory_dump_v2 是高通平台特有的崩溃转储模块。让 Claude 对一加 ACE5 内核源码中这些模块的实现进行调研,得到以下分析结果。

机制概述

minidump 是高通平台私有的跨层协作方案,涉及 TrustZone(EL3)、ABL(Android Bootloader)、内核驱动三层,不是纯内核机制。

整体架构

内核侧:正常运行期间构建 minidump table

内核驱动在正常运行期间(非 panic 时)就通过 API 注册要 dump 的内存区域:
典型注册内容包括 KSTACK(内核栈)、KLOGBUF(printk ring buffer)、KPGTBL(内核页表)、各驱动关键结构体等。其中 KLOGBUF 即 printk ring buffer——panic 时所有的 pr_emerg()dump_stack() 输出都在这里。

TrustZone 能直接写 UFS 的原因

TrustZone 运行在 EL3,有对所有硬件的直接访问权,自带精简 UFS 驱动,使用寄存器轮询等待命令完成,完全不依赖 Linux 内核的 UFS 驱动栈和中断机制。这恰好绕开了前面所有纯内核方案的致命瓶颈。

关键源文件

文件
功能
drivers/soc/qcom/msm_minidump.c
核心注册接口,md_ops dispatch
drivers/soc/qcom/minidump_smem.c
SMEM 后端实现,管理 SMEM ID 602 中的全局 TOC
drivers/soc/qcom/minidump_log.c
HLOS 侧 region 注册(kernel sections、IRQ stack、panic dump 等)
drivers/soc/qcom/qcom_va_minidump.c
VA minidump 框架,按需收集各子系统数据

完整实现需要的组件

  1. 高通下游内核msm-kernel)中的 drivers/soc/qcom/minidump.c
  1. TrustZone 固件(闭源,高通提供的 .mbn 文件)
  1. ABL 中的 dump 处理代码(高通 UEFI ABL,部分开源)
三者缺一不可,纯内核层面无法单独实现。

与纯内核方案的对比

对比项
ramoops(内核方案)
minidump(高通方案)
谁来写存储
panic 核(内存保留)
TrustZone(EL3 固件)
依赖中断
否(TZ 自带轮询 UFS)
存储介质
预留 RAM
UFS rawdump 分区
数据量
受 ramoops 分区大小限制
可配置,通常更大
可选择性
全量 kmsg
精细化(按模块注册的关键内存)
开源程度
完全开源(内核主线)
高通私有(TZ 固件 + 下游内核驱动)
至此可以确定:一加实际使用的是高通 minidump 机制,由 TrustZone 在 panic 后直接将内存内容写入 UFS,而非依赖 ramoops。ramoops 在该设备上无法生效的具体原因仍未定论,但这已不影响日志的获取。

曙光与结论

理论到此为止,接下来只需要找到日志的具体存储位置。思路很简单:手动触发一次 panic,重启后从 dmesg 中搜索 minidump 相关的服务日志。
首先确认上次重启确实是因为 panic:
kernel crash Minidump——说明 minidump 机制确实被触发了。然后搜索 dmesg 中 minidump 相关的日志:
从日志中可以还原出一加的 minidump 用户空间处理链:
  1. minidumpreader 读取 rawdump 分区中的 minidump 数据
  1. olcpackupminidump 将日志打包
  1. minidumpraise2olc 上报给 OLC(OnePlus Log Collection)服务
  1. olc_get_minidump_log 提取日志文件
  1. backup_minidumplog 备份到持久化路径
其中最关键的一行是那个 mount 命令——它暴露了日志的存储路径:
三次 panic 的日志都在。文件名格式为 SYSTEM_LAST_KMSG@<hash>@<版本号>@<时间戳>.dat.gz。备份路径也有一份:
把文件拉出来、解压、用 strings 提取可读内容——完整的 panic 日志赫然在目。

完整提取步骤

总结最终可用的 panic 日志提取流程:
  1. 触发或等待一次 kernel panic,设备会自动重启
  1. 重启后确认重启原因:
  1. 通过 adb shell 进入 root shell,将日志文件复制到 adb 可访问的路径并修改权限:
  1. 拉取到本地:
  1. 解压并提取可读的崩溃日志:

回顾

整个探索过程走了不少弯路,但也因此对 Linux 内核的 panic 日志转储体系有了系统性的理解:
  • ramoops:最通用的方案,将日志写入预留内存,但依赖这片内存跨重启不被清除。在一加 ACE5 上实测修改 dtbo 配置后仍无法生效,具体原因未明
  • mtdoops:架构简洁的 MTD 闪存 dumper,但它的写入接口绑定在 MTD 子系统上,无法用于 UFS 存储
  • pstore/blk:pstore 的块设备后端,设计上支持通用块设备,但 panic 时 block 层不可用,需要存储驱动实现 panic_write 回调——而 AOSP 主线和一加 vendor 内核的 UFS 驱动均未实现
  • minidump:高通私有方案,panic 后由 TrustZone 在 EL3 接管控制权,用自带的精简 UFS 驱动直接写入存储——这才是一加实际使用的机制
对于一加 ACE5(以及其他使用高通平台的一加设备),panic 日志最终存储在 /mnt/vendor/oplusreserve/media/log/minidump/ 目录下,以 SYSTEM_LAST_KMSG 为前缀的 gzip 压缩文件中。
  • 思考
  • 博客重建深入分析 Seccomp BPF
    Loading...