小米平板 6S Pro Fastboot 循环:GPT 类型 GUID 修复记录

本文由GPT-6 Luna总结编写

前言

小米平板 6S Pro(sheng)之前一直卡在 Fastboot。开机时能看到小米 Logo,随后很快又掉回 Fastboot;进 Recovery 也会重启回 Fastboot。

中间试过重刷移植包,也试过刷入 OS3.0.303.0 底包里的部分启动镜像,Fastboot 命令都显示成功,但启动现象没有变化。后来又完整刷过一次官方线刷包,还是进 Fastboot。

折腾到这里,继续猜镜像已经没什么意义了。真正有用的线索来自 UEFI 启动日志:它不是报找不到系统,而是明确说 A/B 槽位的 GUID 校验失败。

先看启动日志

从 Fastboot 进入 EDL 后,读取了启动日志分区。进入 EDL 用的是:

fastboot devices
fastboot oem edl

第二条命令会让设备切换到 EDL 模式。确认设备确实进入 EDL 后,再用 Firehose 工具读取日志分区。日志中反复出现:

ValidateSlotGuids: BootableSlot _a does not have valid guids
Boot GUID 77036CD4-03D5-42BB-8ED1-37E5A88BAA34
abl_a GUID 77036CD4-03D5-42BB-8ED1-37E5A88BAA34
Err: line:1825 FindBootableSlot() status: Device Error
Err: line:2139 LoadImageAndAuth() status: Device Error
LoadImageAndAuth failed: Device Error
Launching fastboot

_b 槽也记录了相同的 ValidateSlotGuids 失败。也就是说,UEFI 在挑选启动槽位时就拒绝了 A、B 两边,之后才回到 Fastboot。

这条日志把排查方向从“系统镜像是不是没刷进去”带到了 GPT 分区项本身。

在一个 EDL 会话里读主备 GPT

读取日志后,在同一个 Firehose 会话里继续读取 UFS LUN4 的 GPT。主 GPT 从 LBA 0 开始读 6 个 4096 字节扇区;备 GPT 则读取 LBA 521209 到 521215。这样不必每读一段都重新连接设备:

LUN 4, LBA 0,      6 sectors   -> primary GPT
LUN 4, LBA 521209, 7 sectors -> backup GPT array and header

这次读取是通过自定义 Python 包装程序在一个 Firehose 会话里完成的。当前没有保留足以复现它的完整调用参数,所以这里记录实际读取的 LUN、LBA 和扇区数,不拼凑一个看起来完整但未经核实的 EDL 命令。

这台机器报告的 UFS 型号是 256 GB。注意,读回的 GPT 文件完整不代表分区表内容一定正确,所以还要分别校验主表、备表的表头 CRC、分区数组 CRC,并把两份数组逐字节比较。

本次结果:

  • 主 GPT 表头 CRC 有效,分区数组 CRC 有效。
  • 备 GPT 表头 CRC 有效,分区数组 CRC 有效。
  • 主备分区数组逐字节一致。
  • 分区起止位置与 OS3.0.303.0 模板的主要布局相同。

所以问题不是“主 GPT 坏了、备 GPT 好了”这种简单情况。两份表自身的 CRC 都正确,但其中一些分区项的类型 GUID 与启动程序要求的不一致。

找到错位的 26 个类型 GUID

将设备当前 GPT 与 OS3.0.303.0 包里的 GPT 模板对照后,发现 13 组 A/B 分区、共 26 个 Type GUID 不匹配:

aop_a / aop_b
bk41_a / bk41_b
tz_a / tz_b
hyp_a / hyp_b
modem_a / modem_b
abl_a / abl_b
keymaster_a / keymaster_b
boot_a / boot_b
devcfg_a / devcfg_b
qupfw_a / qupfw_b
vbmeta_a / vbmeta_b
dtbo_a / dtbo_b
rticmpdata_a / rticmpdata_b

其中 boot_a、abl_a 等启动相关分区项被标成了通用 GUID:

77036CD4-03D5-42BB-8ED1-37E5A88BAA34

而 UEFI 日志正好显示它拿到的 Boot GUID 和 abl_a GUID 都是这个通用值,随后报 BootableSlot _a does not have valid guids。B 槽同理。

这里有个容易误判的地方:GPT 的 CRC 只检查数据和 CRC 是否匹配,并不理解某个分区应该使用哪种 Type GUID。因此,CRC 全绿仍然可能是语义错误的 GPT。

修复方式

没有把官方 GPT 文件整份覆盖到设备上,而是以设备当前 GPT 为底稿,只把上面 26 个分区项的 Type GUID 恢复成 OS3.0.303.0 模板对应的值,再重算 GPT CRC。

写回顺序是先备表,再主表:

内容LUN起始 LBA扇区数
备 GPT 分区数组45212093
备 GPT 表头45212151
主 GPT 分区数组423
主 GPT 表头411

写完后仍在同一个 Firehose 会话里把主、备 GPT 重新读回,重新计算 CRC,并确认两份分区数组一致。

这次只修改了 Type GUID 和相应 CRC,保留了:

  • 分区起止位置与大小
  • 分区属性和槽位状态
  • 每个分区自己的唯一 GUID
  • 设备磁盘 GUID
  • xbl_sc_logs 等设备特有的尾部布局

之前线刷日志里出现过 metadata 擦除和 userdata 写入;本次 GPT 修复没有写 userdata。但此前的刷机操作是否影响过用户数据,不能只凭这次修复判断。

修复后怎么验证

读回确认主备 GPT CRC 都有效后,设备并没有立刻进入 Android,而是先从 Recovery 又回到了 Recovery。随后从 Recovery 进入 Fastboot,检查当前槽和槽位状态:

fastboot getvar current-slot
fastboot getvar slot-successful:a
fastboot getvar slot-successful:b
fastboot getvar slot-unbootable:a
fastboot getvar slot-unbootable:b

当时读到当前槽是 a,A、B 都没有被标记为 unbootable。随后执行 Fastboot 的继续启动命令:

fastboot continue

平板随后离开 Fastboot 并进入 Android 系统。设备屏幕显示系统已启动;USB 调试当时没有开启,因此没有用 ADB 读取 sys.boot_completed。

到这里可以确认:这次 GPT Type GUID 修复解除了 UEFI 对 A/B 槽的 GUID 校验阻断,Android 已经能够启动。Recovery 的一次普通重启仍回到 Recovery,Linux 启动路径也没有在本次修复后验证。

这次踩到的坑

Fastboot 显示 OKAY 不代表启动链完整

此前多次刷写都显示成功,但设备依旧回到 Fastboot。Fastboot 的 OKAY 只能说明对应写入命令完成了,不能证明 UEFI 后续会接受 GPT 元数据并成功引导。

GPT CRC 正确不代表分区定义正确

主备 GPT 完全一致且 CRC 正确,UEFI 仍然因为分区 Type GUID 不符拒绝 A/B 槽。需要同时看表结构、字段内容和启动日志。

不要盲目覆盖官方 GPT

官方 GPT 适合做布局和 Type GUID 的参考,不应该不加区分地整份覆盖。设备 GPT 里有唯一 GUID、槽位状态和设备自己的尾部布局;直接套模板可能把这些设备状态一起覆盖。本次只恢复了确认错误的 Type GUID。

qbootctl -s _a 不能直接认定是根因

本地检查的 qbootctl 源码只接受 a、b 或 0、1 这种槽位参数,字面量 _a 会被参数解析拒绝。不过,这不能证明设备上当时运行的二进制与检查的源码完全相同,也不足以单独证明故障由那条命令造成。最终定位依据是设备当前 GPT 与 UEFI 报错内容相互吻合。

总结

这次 Fastboot 循环的直接阻断点,是 GPT 中多组 A/B 分区 Type GUID 与 UEFI 槽位校验预期不一致。修复这些字段并重算 CRC 后,设备从 Fastboot 继续启动进入了 Android。

完整过程可以概括成:先读 UEFI 日志定位失败阶段,再在同一 EDL 会话中读回主备 GPT,对照设备对应版本的模板,只修确认错误的字段,最后读回验证并启动测试。

这篇记录只对应这台 sheng 和 OS3.0.303.0 分区模板。分区 LBA、GUID 和修复数据都具有设备与版本相关性,不能直接复制到其他平板上使用。