Android 7系统编译与刷机(三)从编译产物到高通刷机包

系列目录Android 7系统编译与刷机(一):源码环境搭建与完整编译流程 | (二):Image镜像生成机制深度解析 | (三):从编译产物到高通刷机包 | (四):QFIL刷机工具详解 | (五):特定分区镜像的单独导出 | (六):特定分区刷入实战


一、为什么要研究刷机包打包流程

编译完系统后,out/target/product/<设备名>/ 下有一堆 .img 文件。但如果直接把 system.imgboot.img 丢进 QFIL,你会发现刷不进去------要么提示"找不到烧录脚本",要么直接报错。

原因很简单:QFIL 不认裸镜像,它认的是 XML 脚本驱动的烧录包

一个完整的高通 QFIL 刷机包,目录结构是这样的:

复制代码
刷机包/
├── rawprogram0.xml        ← 核心:描述每个镜像的烧录位置和参数
├── patch0.xml             ← 分区表修补:根据实际存储大小调整分区
├── partition.xml          ← 分区表定义:各分区的名称、大小、类型
├── prog_emmc_firehose_xxx.elf  ← Firehose 协议通信程序
├── gpt_main0.bin          ← 主 GPT 分区表
├── gpt_backup0.bin        ← 备份 GPT 分区表
├── system.img             ← 系统镜像(可能被分割成多个片段)
├── boot.img               ← 启动镜像
├── userdata.img           ← 用户数据镜像
└── ...                    ← 其他镜像

这篇就来讲清楚:编译产物怎么变成这个刷机包。


二、刷机包核心文件详解

2.1 partition.xml --- 分区表定义

partition.xml 定义了设备存储的分区布局,包括每个分区的名称、大小、类型和属性。

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<partition_definition>
  <!-- 分区表基础信息 -->
  <device_info>
    <sector_size>512</sector_size>       <!-- 扇区大小,eMMC 通常为 512 -->
    <total_sectors>61071360</total_sectors>  <!-- 总扇区数 -->
    <storage_type>eMMC</storage_type>    <!-- 存储类型:eMMC 或 UFS -->
  </device_info>

  <partition>
    <name>sbl1</name>                    <!-- 分区名称 -->
    <size>1048576</size>                 <!-- 分区大小(字节) -->
    <type>DEA0BA2C-CBDD-4805-B4F9-F428251C3E98</type>  <!-- GPT 分区类型 GUID -->
    <bootable>true</bootable>            <!-- 是否可启动 -->
    <filename>sbl1.mbn</filename>        <!-- 对应的镜像文件名 -->
  </partition>

  <partition>
    <name>system</name>
    <size>2684354560</size>              <!-- 2.5GB -->
    <type>97D7B011-54DA-4835-B3C4-917AD6E73D74</type>
    <bootable>false</bootable>
    <filename>system.img</filename>
  </partition>

  <partition>
    <name>userdata</name>
    <size>0</size>                       <!-- 0 表示自动扩展到剩余空间 -->
    <type>1B81E7E6-F50D-419B-A739-2AEEF8DA3335</type>
    <bootable>false</bootable>
    <filename>userdata.img</filename>
  </partition>
</partition_definition>

关键设计userdata 分区的 size 设为 0,表示"自动扩展到存储设备剩余空间"。这是高通平台的标准做法------userdata 不需要固定大小,patch0.xml 会在刷写时根据实际存储容量自动计算。

2.2 rawprogram0.xml --- 烧录程序脚本

rawprogram0.xml 是 QFIL 的核心指令文件,描述了每个镜像文件要烧到哪个物理扇区、以什么方式烧

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<data>
  <!-- system 分区的写入指令 -->
  <program
    SECTOR_SIZE_IN_BYTES="512"
    file_sector_offset="0"              <!-- 从文件的第 0 个扇区开始读 -->
    filename="system.img"               <!-- 镜像文件名 -->
    label="system"                      <!-- 分区标签 -->
    num_partition_sectors="5242880"     <!-- 分区占用的扇区数 -->
    physical_partition_number="0"       <!-- 物理分区号(LUN) -->
    size_in_KB="2621440"               <!-- 分区大小(KB) -->
    sparse="true"                       <!-- 是否 sparse 格式 -->
    start_sector="131072"              <!-- 起始扇区号 -->
  />

  <!-- boot 分区的写入指令 -->
  <program
    SECTOR_SIZE_IN_BYTES="512"
    file_sector_offset="0"
    filename="boot.img"
    label="boot"
    num_partition_sectors="131072"
    physical_partition_number="0"
    size_in_KB="65536"
    sparse="false"
    start_sector="2048"
  />
</data>

每个 <program> 元素的关键属性:

属性 含义 典型值
start_sector 写入起始扇区 由 GPT 分区表决定
num_partition_sectors 写入扇区数 分区大小 / 扇区大小
filename 镜像文件名 system.img
sparse 是否 sparse image system/userdata 为 true,boot 为 false
file_sector_offset 文件内偏移(扇区) 通常为 0,分包时不为 0
physical_partition_number 物理分区号 eMMC 通常为 0

关键设计sparse="true" 告诉 Firehose 协议,这个镜像文件是 sparse 格式。Firehose 在写入时会解析 sparse header,跳过 DONTCARE chunk,只写入有效数据。如果标记错了(比如 boot.img 是 raw 格式却标了 sparse="true"),就会触发 Invalid sparse file format at header 错误。

2.3 patch0.xml --- 分区表修补

patch0.xml 的职责是在烧录完成后修补 GPT 分区表,确保分区表校验和正确,以及 userdata 等自动扩展分区被正确填充到实际存储末尾。

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<data>
  <!-- 修补主 GPT 头部 -->
  <patch
    SECTOR_SIZE_IN_BYTES="512"
    filename="gpt_main0.bin"
    physical_partition_number="0"
    size_in_KB="16"
    start_sector="0"
    byte_offset="32"
    value="CRC32"
    what="Update-CRC"
  />

  <!-- 修补 userdata 分区大小 -->
  <patch
    SECTOR_SIZE_IN_BYTES="512"
    filename=""
    physical_partition_number="0"
    start_sector="0"
    what="Update-last-partition"
  />
</data>

关键设计patch0.xml 中的 Update-last-partition 指令是动态计算 userdata 分区大小的关键。它读取实际存储的总扇区数,减去所有固定分区占用的扇区,将剩余空间全部分配给最后一个分区。


三、打包脚本:从编译产物到刷机包

3.1 打包流程总览

高通平台的标准打包流程:

复制代码
编译产物(out/target/product/xxx/)
    │
    ├── system.img ──────────────┐
    ├── boot.img ────────────────┤
    ├── userdata.img ────────────┤
    ├── vendor.img ──────────────┤
    └── ...                       │
                                  ▼
                    ┌──────────────────────┐
                    │  update_common_info.py │  ← 高通官方打包脚本
                    │  - 分割大镜像           │
                    │  - 更新 GPT 分区表      │
                    │  - 生成 rawprogram.xml  │
                    └──────────────────────┘
                                  │
                                  ▼
                    ┌──────────────────────┐
                    │  pack.sh              │  ← 自定义打包脚本
                    │  - 链接镜像文件         │
                    │  - 调用 update_common  │
                    │  - 收集所有文件打包      │
                    └──────────────────────┘
                                  │
                                  ▼
                         刷机包.zip

关键设计 :打包流程分两层------底层是高通官方的 update_common_info.py 负责镜像分割和 XML 生成;上层是自定义的 pack.sh 负责收集文件和最终打包。这种分层设计便于在不同项目间复用打包逻辑。

3.2 镜像文件分割

对于大镜像(如 system.img),高通工具会将其分割成多个小文件,避免单个文件过大导致传输失败:

bash 复制代码
# 分割后的 system.img 文件
system.img_0   # 第 0 个片段
system.img_1   # 第 1 个片段
system.img_2   # 第 2 个片段

对应的 rawprogram0.xml 中也会生成多个 <program> 元素:

xml 复制代码
<!-- system 分片 0 -->
<program file_sector_offset="0" filename="system.img_0" ... />
<!-- system 分片 1 -->
<program file_sector_offset="262144" filename="system.img_1" ... />
<!-- system 分片 2 -->
<program file_sector_offset="524288" filename="system.img_2" ... />

关键设计 :镜像分割是高通为了解决大文件传输限制而设计的。file_sector_offset 告诉 Firehose 从文件的第几个扇区开始读数据,写入到 start_sector + file_sector_offset 对应的物理位置。每个分片独立写入,互不干扰。

3.3 自定义打包脚本示例

bash 复制代码
#!/bin/bash
# pack.sh --- 将编译产物打包为 QFIL 刷机包

ROOT=$(pwd)
LNX_OUT=out/target/product/msm8974
PACK_DIR=$ROOT/flash_package

# 1. 创建打包目录
mkdir -p $PACK_DIR

# 2. 链接关键镜像文件
cp $LNX_OUT/boot.img $PACK_DIR/
cp $LNX_OUT/system.img $PACK_DIR/
cp $LNX_OUT/userdata.img $PACK_DIR/
cp $LNX_OUT/recovery.img $PACK_DIR/

# 3. 链接分区表相关文件
cp device/qcom/msm8974/gpt_main0.bin $PACK_DIR/
cp device/qcom/msm8974/gpt_backup0.bin $PACK_DIR/
cp device/qcom/msm8974/partition.xml $PACK_DIR/

# 4. 执行官方打包脚本
python device/qcom/common/update_common_info.py \
    --partition_file=$PACK_DIR/partition.xml \
    --output_dir=$PACK_DIR

# 5. 打包为 zip
cd $PACK_DIR && zip -r ../flash_package.zip ./*

关键设计 :打包脚本的核心是"收集 + 生成 + 打包"三步走。先用 cp 收集所有需要的文件,再调用高通官方脚本生成 XML,最后打包成 zip。脚本中硬编码了路径,实际使用时应根据项目配置动态获取。


四、AOSP 编译产物与高通刷机包的对应关系

AOSP 编译产物 刷机包中对应文件 说明
out/.../boot.img boot.img 直接对应
out/.../system.img system.imgsystem.img_0/1/2 可能需要分割
out/.../userdata.img userdata.img 直接对应
out/.../recovery.img recovery.img 直接对应
无(需单独提供) prog_emmc_firehose_xxx.elf 高通平台专用,须从厂商获取
无(需单独提供) partition.xml 由设备树定义
无(由工具生成) rawprogram0.xml update_common_info.py 生成
无(由工具生成) patch0.xml update_common_info.py 生成

关键设计:这张表是"编译产物 → 刷机包"的映射速查。AOSP 编译产物只能直接提供镜像文件,分区表和 XML 脚本需要高通工具链生成,Firehose 文件需要从芯片厂商获取。


五、常见问题排查

问题一:分区大小不匹配

编译时 BOARD_SYSTEMIMAGE_PARTITION_SIZEpartition.xml 中的 system 分区大小不一致,导致刷写失败。

解决 :确保 BoardConfig.mk 中的分区大小和 partition.xml 中的定义一致:

makefile 复制代码
# BoardConfig.mk
BOARD_SYSTEMIMAGE_PARTITION_SIZE := 2684354560  # 2.5GB
xml 复制代码
<!-- partition.xml -->
<partition>
  <name>system</name>
  <size>2684354560</size>
  <!-- 必须一致 -->
</partition>

注意 :分区大小不一致是最常见的刷机失败原因之一。修改 BoardConfig.mk 后,必须同步更新 partition.xml,否则会导致刷写失败或分区表损坏。

问题二:sparse 格式标记错误

rawprogram0.xmlboot.imgsparse 属性误标为 true,导致 QFIL 报 Invalid sparse file format at header

解决boot.imgrecovery.img 不是 ext4 镜像,不是 sparse 格式,sparse 属性必须为 false

注意:只有 ext4 文件系统镜像(system、vendor、userdata、cache)才可能是 sparse 格式。boot 和 recovery 是自定义打包格式,永远是 raw 格式。

问题三:Firehose 文件不匹配

prog_emmc_firehose_xxx.elf 和芯片型号不匹配,导致 QFIL 无法建立 Sahara 协议通信。

解决:确认芯片型号(如 MSM8974、MSM8996),从厂商获取对应的 Firehose 文件。

警告:Firehose 文件与芯片型号强绑定,不同芯片甚至不同版本的芯片都可能不兼容。使用错误的 Firehose 文件会导致 Sahara 协议握手失败,设备无法进入刷机模式。


六、关键文件索引

文件 作用
partition.xml 分区表定义,定义各分区名称、大小、类型
rawprogram0.xml 烧录指令脚本,描述每个镜像的写入位置和方式
patch0.xml 分区表修补脚本,更新 CRC 校验和动态分区
prog_emmc_firehose_xxx.elf Firehose 协议通信程序
gpt_main0.bin / gpt_backup0.bin 主/备份 GPT 分区表
update_common_info.py 高通官方打包脚本

关键设计:这些文件构成了高通刷机包的完整结构。缺少任何一个文件都会导致刷机失败。建议将这些文件统一存放在版本控制系统中,便于追溯和复用。


七、本篇总结

本篇覆盖了从编译产物到 QFIL 刷机包的完整打包流程:

  • partition.xml 定义分区布局,userdatasize=0 表示自动扩展
  • rawprogram0.xml 是 QFIL 的核心指令文件,描述每个镜像的写入扇区、大小、sparse 标记
  • patch0.xml 负责烧录后的分区表修补和 CRC 校验
  • 打包脚本 将编译产物、分区表文件、Firehose 文件收集到一起,生成可刷写的包

下一篇我们将进入 QFIL 工具本身------从 9008 模式进入、Firehose 协议握手,到实际刷机操作的完整流程。

相关推荐
AFinalStone1 小时前
Android 7系统编译与刷机(四)QFIL刷机工具详解
android·系统编译
plainGeekDev2 小时前
JUnit 4 → JUnit 5 + Kotlin
android·java·kotlin
plainGeekDev2 小时前
Mockito → MockK
android·java·kotlin
2 小时前
使用ffmpeg将mp4视频转为m3u8格式
android·ffmpeg·音视频
00后程序员张2 小时前
使用Instruments工具深入分析iOS应用性能与启动时间优化
android·macos·ios·小程序·uni-app·cocoa·iphone
zbmwa3 小时前
jetpack compose 副作用 snapshotFlow
android
爱笑鱼3 小时前
Binder 实战(九):一次调用变慢,到底卡在客户端、驱动、Binder 线程还是 Handler?
android
恋猫de小郭4 小时前
Flutter hit,一个可以灵活控制溢出点击的第三方包
android·前端·flutter
我命由我123454 小时前
Kotlin 面向对象 - 枚举排序
android·java·开发语言·java-ee·kotlin·android studio·android-studio