执行摘要

本次分析针对 SPRD UMS9230 平台上 DDR Qualify.TT 测试过程中发生的冻屏(screen freeze)问题。

根因判定: Block 层 blk_mq_tags 结构体发生 use-after-free,Scsi_Host.tag_set.tags 指针指向已释放并被 cpumask/IRQ affinity 对象重用的 slab 内存(kmalloc-128)。当 EROFS readahead 路径调用 blk_mq_get_tag 时,解引用该失效指针读取到垃圾位图数据,导致所有 tag 分配失败,形成系统级 I/O 死锁。UFS 控制器本身工作正常(79905 个 SCSI 请求全部完成)。

置信度: high-probability

关键发现:

  • tag_set.tags = 0xffffff8082570a80 指向 kmalloc-128 slab(blk_mq_tags 需要 192 字节)

  • 该地址偏移 0x80 处包含 ASCII 字符串 "effective_affinity"(cpumask/IRQ affinity 对象特征)

  • blk_mq_hw_ctx.tags = NULLsched_tags = NULL

  • UFS HBA 状态正常:outstanding_tasks = 0outstanding_reqs = 0,所有 SCSI 请求已完成

  • 40+ 个 D-state 任务阻塞在 blk_mq_get_tag(EROFS readahead 路径)

分析工件

项目

dump 模式

split ramdump(8 段内存)

dump 路径

/mnt/d/workspace/project/P25A/20260409001/ap_sys_dump

vmlinux

/mnt/d/workspace/project/P25A/20260409001/vmlinux

模块符号

/mnt/d/workspace/project/P25A/20260409001/arctic_global-target_files-odm-P25A-qualify-GL-1029-FFU-userroot-13.0-8632d78748/kernel_artifacts/arctic_global_k515-user-androidt/symbols(247 个模块)

平台

SPRD UMS9230(arm64)

内核版本

Linux 5.15.178-android13-8-00012-g4ea0fcb5d130

符号状态

ready

drgn 可用性

不可用(split dump 模式禁用)

会话模式与工具约束

  • 会话 ID: 8eac7901-21e6-4dc6-ae4f-75bab9d785ed

  • crash 参数: -m vabits_actual=39 -m phys_offset=0x80000000 -m kimage_voffset=0xffffffbf88000000 --kaslr=0x80000

  • SPRD 参数来源:dump_report.txt 手动提取(工具无法自动解析)

  • 结构体偏移限制: crash 工具对部分结构体(如 blk_mq_hw_ctxrequest_queue)存在偏移解析错误(KABI 扩展导致),需通过 rd 命令读取原始内存验证

调查过程

阶段 1:初步症状识别

可用证据: dump_report.txt 显示 panic_reason: Oops - BUG: Fatal exception,异常任务为 kworker/0:2(PID 26422),PC 在 sprd_debug_check_crash_key+0xe4(手动触发 fulldump)。

假设: 冻屏的根因不是 fulldump 触发本身,需要找到导致冻屏的底层原因。

命令: bt → 显示手动触发 fulldump 的栈帧,非冻屏根因。

阶段 2:系统级阻塞发现

假设: 冻屏可能是系统级阻塞导致。

命令: ps(输出过大,保存到文件后用 Python 解析)→ 发现 40+ 个 D-state 任务。

结果: 大量任务处于 UN(D-state)状态,确认系统级阻塞。

阶段 3:阻塞路径收敛

假设: 多个 D-state 任务可能收敛到同一阻塞点。

命令: 逐一检查 D-state 任务的栈帧 → 所有任务阻塞在 blk_mq_get_tag,路径为 EROFS readahead → blk_mq_get_tag

结果: Block 层 tag 分配成为所有 I/O 的瓶颈。这是冻屏的直接原因。

阶段 4:UFS 子系统状态检查

假设: UFS 控制器可能故障导致 I/O 无法完成。

命令: struct scsi_device 0xffffff808019f000(sda, LUN 0)

结果:

  • iorequest_cnt = 79905iodone_cnt = 79905 — SCSI 层所有请求已完成

  • queue_depth = 31device_blocked = 0

  • sdev_state = SDEV_RUNNING

假设变更: UFS 控制器正常,问题在 block 层。

阶段 5:UFS HBA 深入检查

命令: struct ufs_hba 0xffffff808591e9e8

结果:

  • ufshcd_state = UFSHCD_STATE_OPERATIONAL

  • outstanding_tasks = 0outstanding_reqs = 0

  • uic_link_state = UIC_LINK_HIBERN8_STATE

  • clk_gating.state = CLKS_OFF

  • is_irq_enabled = false

  • host_self_blocked = 1eh_noresume = 1

结论: UFS 控制器已完成所有请求并进入低功耗状态。I/O 死锁的根因不在 UFS 层。

阶段 6:Block 层 Tag 管理结构检查(关键发现)

假设: blk_mq_tags 结构体可能损坏。

命令: struct Scsi_Host 0xffffff808591e000 → 获取 tag_set.tags = 0xffffff8082570a80

命令: kmem 0xffffff8082570a80

结果:

CACHE             OBJSIZE  ALLOCATED     TOTAL  SLABS  SSIZE  NAME
ffffff8080002300      128     297284    298144   9317     4k  kmalloc-128
  SLAB              MEMORY            NODE  TOTAL  ALLOCATED  FREE
  fffffffe02095c00  ffffff8082570000     0     32         32     0
  [ffffff8082570a80]

关键发现: tag_set.tags 指向 kmalloc-128 slab 中的一个 128 字节对象,但 struct blk_mq_tags 大小为 192 字节。该指针不可能指向有效的 blk_mq_tags 结构体。

阶段 7:内存内容检查(确认 use-after-free)

命令: rd -x 0xffffff8082570a80 30

结果:

ffffff8082570a80:  ffffff8084d85600 0000000000000000  ← 偏移 0x00
...
ffffff8082570b00:  7669746365666665 696e696666615f65  ← 偏移 0x80: "effective_"
ffffff8082570b10:  0000000000007974                    ← 偏移 0x90: "affinity\0"

关键发现: 偏移 0x80 处包含 ASCII 字符串 "effective_affinity"(0x7669746365666665 = "effectiv", 0x696e696666615f65 = "e_affini", 0x0000000000007974 = "ty\0")。这是 cpumask 或 IRQ affinity 描述符的特征数据,不是 blk_mq_tags 结构体内容。

结论: tag_set.tags 是一个失效指针(stale pointer)。原始 blk_mq_tags 对象已被释放,该 slab 槽位被 cpumask/IRQ affinity 对象重用。这是典型的 use-after-free 模式。

阶段 8:blk_mq_hw_ctx 验证

命令: struct blk_mq_hw_ctx 0xffffff80886a9080 + rd -x 原始内存验证

结果: 由于 KABI 扩展导致结构体偏移不匹配,crash 工具报告 tags = 0x0。通过原始内存分析确认:

  • 偏移 0x130 处:0xffffff8080ff5600(接近 request_queue 地址 0xffffff8080ff55f0)

  • 偏移 0x150 处:0x0000000000000000(tags 指针为 NULL)

结论: blk_mq_hw_ctx 的 tags 指针为 NULL,进一步确认 tag 管理子系统已完全失效。

直接证据

  1. Scsi_Host.tag_set.tags = 0xffffff8082570a80 — 指向 kmalloc-128 slab(blk_mq_tags 需要 192 字节)

  2. 地址 0xffffff8082570a80 偏移 0x80 处包含 ASCII "effective_affinity" — cpumask/IRQ affinity 对象特征

  3. kmem 确认该对象当前在 kmalloc-128 中已分配(非空闲)— 内存已被不同子系统重用

  4. blk_mq_hw_ctx.tags = NULLsched_tags = NULL — 硬件上下文无 tag 指针

  5. UFS HBA outstanding_tasks = 0outstanding_reqs = 0 — 无待处理 UFS 请求

  6. sda iorequest_cnt = 79905iodone_cnt = 79905 — SCSI 层所有请求已完成

  7. 40+ 个 D-state 任务阻塞在 blk_mq_get_tag(EROFS readahead 路径)— 系统级 I/O 死锁

  8. 1% 空闲内存,2608% committed — 严重内存压力

  9. Scsi_Host.host_self_blocked = 1eh_noresume = 1 — SCSI 主机自阻塞

  10. UFS 链路状态 UIC_LINK_HIBERN8_STATE,时钟门控 CLKS_OFF — UFS 控制器已休眠

推理与根因评估

根因机制

tag_set.tags 指针是一个失效指针,指向已释放的 blk_mq_tags slab 对象。该 slab 槽位已被 cpumask/IRQ affinity 对象重用(由 "effective_affinity" ASCII 字符串证明)。当 EROFS readahead 路径调用 blk_mq_get_tag 时,解引用该失效指针读取到垃圾位图数据(大部分为零,末尾为 affinity 字符串),导致所有 tag 分配失败或返回无效索引。这形成了系统级 I/O 死锁。

依赖链

EROFS readahead → blk_mq_get_tag → 解引用 tag_set.tags (0xffffff8082570a80)
  → 读取到 cpumask/IRQ affinity 数据(非有效 tag 位图)
  → tag 分配失败 → I/O 请求无法提交 → 系统级 I/O 死锁 → 冻屏

UFS 控制器状态

UFS 硬件本身工作正常:

  • 所有 79905 个 SCSI 请求已完成(iorequest_cnt == iodone_cnt)

  • HBA 状态为 OPERATIONAL,无待处理任务

  • 链路已进入 HIBERN8 低功耗状态(无活跃 I/O)

  • 时钟已门控(CLKS_OFF)

I/O 死锁完全发生在 block 层 tag 管理层面,与 UFS 硬件无关。

DDR 测试上下文

DDR Qualify.TT 压力测试导致严重内存压力(1% 空闲,2608% committed),可能加速了 slab compaction 和对象重用,使得失效指针更可能被无关数据覆盖。但 "effective_affinity" ASCII 模式过于结构化,不可能是随机位翻转的结果,指向软件层面的正常 slab 重用

竞争假设

DDR 内存损坏假设: 弱于 use-after-free 假设。"effective_affinity" 是结构化的 ASCII 字符串,对应 cpumask/IRQ affinity 对象,这表明内存是被正常分配和写入的,而非随机损坏。

UFS 控制器故障假设: 已被排除。UFS HBA 状态正常,所有 SCSI 请求已完成。

置信度、限制与报告天花板

置信度标签

high-probability

置信度限制

  1. 无法证明精确的释放路径: 无法从 post-mortem 快照中恢复 blk_mq_tags 被释放时的调用栈

  2. drgn 不可用: split dump 模式禁用了 drgn,无法通过 drgn 验证结构体字段偏移

  3. crash 工具偏移错误: KABI 扩展导致部分结构体(blk_mq_hw_ctx、request_queue)字段偏移解析不正确,需通过原始内存读取验证

  4. 无法区分竞争类型: 无法确定是 blk_mq_tags teardown 竞争(队列移除 vs. 进行中 I/O)还是更复杂的 slab 管理损坏

最有用的下一步

  1. 启用 SLUB debugging(CONFIG_SLUB_DEBUG)以在 slab 释放时记录调用栈

  2. 在 DDR 压力测试环境中复现,使用 kmem -s 监控 blk_mq_tags slab 分配/释放

  3. 检查上游 5.15.x 系列是否有 blk_mq_tags 生命周期管理的已知修复补丁

推荐行动与缓解措施

缓解措施

时间范围

负责方

范围

证据关联

预期效果

风险/权衡

验证计划

回滚路径

启用 CONFIG_SLUB_DEBUGslub_debug=FZPU

短期

BSP 团队

全平台

blk_mq_tags slab 重用

在 slab 释放/分配时记录调用栈,定位精确的 UAF 路径

增加内存开销和启动时间

DDR 压力测试复现

关闭 CONFIG_SLUB_DEBUG

检查上游 5.15.x blk-mq 补丁

短期

BSP 团队

block 层

tag_set.tags 失效指针

修复已知的 blk_mq_tags 生命周期竞争

需要验证补丁兼容性

对比补丁前后 DDR 测试结果

回退补丁

添加 blk_mq_tags 指针有效性检查

中期

BSP 团队

block 层

tag_set.tags 指向 kmalloc-128

在 tag 分配前验证指针有效性,避免读取垃圾数据

增加少量运行时开销

压力测试验证

移除检查

DDR 压力测试中监控 slab 碎片化

中期

测试团队

DDR 测试

严重内存压力加速 slab 重用

及早发现内存压力导致的异常行为

增加测试复杂度

集成到 DDR 测试流程

移除监控

关键命令附录

命令

目的

关键结果

ps → Python 解析 D-state

识别系统级阻塞

40+ 个 UN 状态任务

struct scsi_device 0xffffff808019f000

检查 sda I/O 计数

iorequest_cnt=79905, iodone_cnt=79905

struct ufs_hba 0xffffff808591e9e8

检查 UFS HBA 状态

OPERATIONAL, outstanding_reqs=0, HIBERN8

struct Scsi_Host 0xffffff808591e000

获取 tag_set.tags 指针

tag_set.tags=0xffffff8082570a80

kmem 0xffffff8082570a80

检查 slab 分配状态

kmalloc-128, 128 字节对象(blk_mq_tags 需要 192 字节)

rd -x 0xffffff8082570a80 30

检查内存内容

偏移 0x80 处 "effective_affinity" ASCII

struct blk_mq_hw_ctx 0xffffff80886a9080 + rd

检查 hw_ctx tags 指针

tags=NULL, sched_tags=NULL

struct request_queue 0xffffff8080ff55f0

检查 sda request_queue

queue_depth=31, nr_requests=62


Report generated at: 2026-05-14
Session ID: 8eac7901-21e6-4dc6-ae4f-75bab9d785ed
Analyst: bsp-stability-ai by iliuqi
Author homepage: https://www.iliuqi.com