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_size 和 record_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 相关的分区(
logdump 和 rawdump)做个简单测试:数据完好无损。
logdump 和 rawdump 分区在重启后不会被清空,可以作为持久化目标。顺手确认一下存储型号:
三星 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_write、pstore、kmsg_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 相关的:
其中
minidump、qcom_va_minidump、memory_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 框架,按需收集各子系统数据 |
完整实现需要的组件
- 高通下游内核(
msm-kernel)中的drivers/soc/qcom/minidump.c
- TrustZone 固件(闭源,高通提供的
.mbn文件)
- 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 用户空间处理链:
minidumpreader读取 rawdump 分区中的 minidump 数据
olcpackupminidump将日志打包
minidumpraise2olc上报给 OLC(OnePlus Log Collection)服务
olc_get_minidump_log提取日志文件
backup_minidumplog备份到持久化路径
其中最关键的一行是那个
mount 命令——它暴露了日志的存储路径:三次 panic 的日志都在。文件名格式为
SYSTEM_LAST_KMSG@<hash>@<版本号>@<时间戳>.dat.gz。备份路径也有一份:把文件拉出来、解压、用
strings 提取可读内容——完整的 panic 日志赫然在目。完整提取步骤
总结最终可用的 panic 日志提取流程:
- 触发或等待一次 kernel panic,设备会自动重启
- 重启后确认重启原因:
- 通过
adb shell进入 root shell,将日志文件复制到 adb 可访问的路径并修改权限:
- 拉取到本地:
- 解压并提取可读的崩溃日志:
回顾
整个探索过程走了不少弯路,但也因此对 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 压缩文件中。