3. OTA升级包格式解析

拿到一个高通平台的完整 OTA 包(通常是一个几 GB 的 update.zip),如果只把它当成一个压缩包,就很难理解升级过程中“为什么能只刷一部分分区”“为什么 A/B 槽位可以无缝切换”“为什么校验失败会整包回滚”。要回答这些问题,必须把包拆开,看清里面每一层文件各自承担什么职责。本节按“外层容器 → 分区镜像 → 增量载荷 → 元数据与属性”的顺序,逐层解析 OTA 包的真实结构。

一、外层容器:update.zip 的目录骨架

OTA 包本质是一个 ZIP 容器,但它的目录结构是被 Android 升级框架严格约定的。解压后典型内容如下(以 A/B 全量包为例):

update.zip
├── payload.bin              # 真正的分区数据载荷(A/B 设备核心)
├── payload_properties.txt   # payload 的哈希与大小,供校验与断点续传
├── care_map.pb              # 需要校验的块范围映射
├── metadata                 # 包级元数据(OTA 类型、时间戳、设备约束)
├── metadata.pb              # protobuf 格式的元数据(部分版本)
├── META-INF/
│   └── com/android/
│       ├── metadata         # 旧式元数据
│       ├── metadata.pb
│       └── otacert          # 签名证书
└── system.transfer.list / system.new.dat.br   # 旧式块 OTA(非 A/B)

可以看到,A/B 设备与旧式块 OTA 的包结构差异很大。A/B 包的核心是 payload.bin,而旧式包依赖 *.transfer.list 加 *.new.dat。高通平台自 Android 10 起主流方案已全面转向 A/B + 动态分区,因此本节重点放在前者。

二、payload.bin:增量载荷的容器格式

payload.bin 不是简单拼接的镜像,而是遵循 update_engine 定义的 payload 格式。它的头部是 DeltaArchiveManifest(protobuf 编码),后面紧跟各个分区的安装操作数据。manifest 中关键字段包括:

  • partitions[]:每个待更新分区的名字(如 system、vendor、boot)及其 new_partition_info(目标大小、哈希)。
  • install_operations[]:描述如何把数据写入目标分区,操作类型有 REPLACE、REPLACE_BZ、REPLACE_XZ、ZERO、SOURCE_COPY、SOURCE_BSDIFF、BROTLI_BSDIFF 等。
  • kernel_install_operations[]:针对内核分区的独立操作序列。
  • block_size:通常为 4096 字节,所有偏移和长度都以此为对齐单位。

全量包中,每个分区基本由若干 REPLACE_XZ 操作组成,即“把这段压缩数据解压后写到目标分区的指定块区间”。增量包中则大量出现 SOURCE_BSDIFF,它表示“读取旧分区对应块,与补丁数据做 bspatch 运算,得到新块”。这正是增量包体积远小于全量包的原因。

判断一个 payload 是全量还是增量,最直接的方法是看 manifest 中 install_operations 的类型分布:若几乎全是 REPLACE_*,就是全量;若出现大量 SOURCE_COPY / SOURCE_BSDIFF,就是增量。

三、super.img 与动态分区

在动态分区方案中,system、vendor、product、system_ext 等逻辑分区不再各自独立占用物理分区,而是被统一装进一个物理分区 super。这个物理分区的内容就是 super.img。它内部由 LP metadata(liblp 格式)描述逻辑分区的布局:每个逻辑分区在 super 中的起始扇区、大小、分区组(group)以及最大可扩展空间。

动态分区的价值在于:OTA 时不必为每个逻辑分区预留固定物理空间,而是由 super 统一管理。升级时 update_engine 会先根据新 manifest 计算各逻辑分区的新尺寸,必要时调整 LP metadata,再把数据写入对应偏移。因此解析 super.img 时,第一步永远是读 LP metadata,而不是直接按固定偏移找 system。

OTA 包分层结构与数据流向 ① update.zip(ZIP 容器) payload.bin payload_properties.txt metadata care_map.pb META-INF/otacert ② payload.bin 内部结构(update_engine payload 格式) DeltaArchiveManifest partitions[] install_operations[] block_size = 4096 分区数据区 REPLACE_XZ / REPLACE_BZ SOURCE_BSDIFF / SOURCE_COPY ZERO(擦除块) 校验信息 payload_properties.txt FILE_HASH / FILE_SIZE METADATA_HASH / SIZE ③ super.img(动态分区物理载体) LP metadata 分区组 group 逻辑分区偏移/大小 最大可扩展空间 slot 后缀 _a / _b system_a 逻辑分区 ext4 / erofs 只读挂载 vendor_a 逻辑分区 高通专有驱动 HAL 实现 product_a / system_ext_a 逻辑分区 共享 group 空间 按需分配 数据流:update.zip → payload.bin(manifest + 操作数据)→ 写入 super.img 中的逻辑分区

四、metadata 与 manifest:约束与描述

包根目录的 metadata 是纯文本键值对,描述“这个包能不能装到这台设备上”。常见字段包括:

  • ota-type=AB 或 BLOCK:升级类型。
  • post-build / post-build-incremental:目标版本指纹,用于与当前系统比对。
  • pre-device:允许安装的设备代号,不匹配则拒绝。
  • post-timestamp:构建时间戳,用于防降级。
  • ota-property-files:列出包内需要校验的文件及其偏移,供流式升级直接定位。

而 manifest 在 A/B 语境下通常指 payload 内部的 DeltaArchiveManifest,它描述“每个分区怎么改”;metadata 描述“这个包能不能装”。两者一个管约束、一个管操作,缺一不可。旧式包中还存在 META-INF/com/android/metadata,作用类似,但字段更少。

五、property 文件:构建属性与兼容性

升级完成后,系统会读取新分区的 build.prop、vendor/build.prop、odm/etc/build.prop 等属性文件。它们决定了 ro.build.fingerprint、ro.vendor.build.fingerprint、ro.product.device 等关键值。OTA 校验时,pre-device 与当前 ro.product.device 比对,post-build 与目标 ro.build.fingerprint 比对,任何一项不符都会中止升级。因此 property 文件虽然不直接参与数据写入,却是升级准入判断的依据。

六、一次解析实操:从包到分区布局

下面给出一个可复现的解析流程,用于确认包结构与分区尺寸。示例命令基于通用工具,参数为合理假设值。

# 1. 查看包内文件清单
unzip -l update.zip | grep -E "payload|metadata|care_map"

# 2. 提取 payload 属性(哈希与大小)
unzip -p update.zip payload_properties.txt
# 输出示例:
# FILE_HASH=AbCd...==
# FILE_SIZE=2147483648
# METADATA_HASH=EfGh...==
# METADATA_SIZE=123456

# 3. 解析 payload manifest,查看分区与操作类型
payload-dumper-go -l payload.bin
# 输出示例(节选):
# Partition system_a: size=3221225472, ops=REPLACE_XZ x 48
# Partition vendor_a: size=1073741824, ops=SOURCE_BSDIFF x 12
# Partition boot_a:   size=100663296,  ops=REPLACE_XZ x 2

# 4. 查看 super 内逻辑分区布局
lpdump super.img
# 输出示例(节选):
# Group qti_dynamic_partitions_a: maximum_size=6442450944
#   system_a: offset=1048576, size=3221225472
#   vendor_a: offset=3222274048, size=1073741824

从输出可以读出三个关键判断:第一,system_a 的操作全是 REPLACE_XZ,说明它是全量替换;第二,vendor_a 出现 SOURCE_BSDIFF,说明该分区走增量补丁;第三,lpdump 给出的 offset 与 size 必须与 manifest 中 new_partition_info 一致,否则写入会越界或覆盖相邻分区。实际工程中,若 lpdump 显示 group 的 maximum_size 小于各逻辑分区之和,升级会在 metadata 校验阶段直接失败。

解析 payload.bin 时不要用普通解压工具直接展开,它并非 ZIP 或 tar。必须使用 payload-dumper-go、update_engine 的 payload_generator 或 AOSP 提供的 delta_generator 等工具,否则只能得到二进制乱码。

七、结构对照速查

文件/对象格式核心作用解析工具
update.zipZIP外层容器,承载全部升级资源unzip
payload.binupdate_engine payload分区数据与操作序列payload-dumper-go
DeltaArchiveManifestprotobuf描述分区如何被修改delta_generator
super.imgLP metadata + 逻辑分区动态分区物理载体lpdump
metadata键值文本设备约束与升级准入cat / grep
build.prop键值文本版本指纹与设备属性grep

把这几层关系串起来:update.zip 是外壳,metadata 决定“能不能装”,payload.bin 的 manifest 决定“怎么装”,super.img 的 LP metadata 决定“装到哪里”,而 build.prop 等 property 文件决定“装完是什么版本”。理解这条链路,后续分析升级失败日志、制作增量包或裁剪分区时,才能准确定位到具体文件与字段。