
说起 Android OTA 更新包很多人第一反应是解压出来不就行了真动手才发现update.zip里那个几个 GB 的payload.bin用常规解压工具根本打不开改成.img后缀也没用。payload-dumper-go 就是专门对付这个文件的工具一个用 Go 写的payload.bin提取器把里面按块压缩的镜像还原成可以直接挂载、可以直接丢进镜像厨房的boot.img、system.img、vendor.img、super.img。它解决的核心痛点是速度和并发早期那套 Python 脚本单线程跑一个完整包要十几二十分钟换成 payload-dumper-go 在多核机器上通常一两分钟就能收工。这篇内容适合三类人看想从完整 OTA 包里挖系统镜像做定制的、做 Android 平台分析需要拆包看分区的、以及单纯被payload.bin卡住过一次、想彻底搞清楚这个格式到底怎么回事的。下面我按格式原理—工具选型—环境准备—完整实操—问题排查这条线把我这几年反复拆包积累下来的东西摊开讲。1. 先搞清楚 payload.bin 到底是什么很多人卡住不是因为不会用工具而是压根不知道自己在处理什么东西报错一出就懵。花几分钟把格式摸清楚后面所有报错你都能自己对上号。1.1 A/B 分区之后OTA 包换了内核早期 Android 用的是 A 分区单一系统OTA 更新就是把新文件覆盖到现有系统上所以更新包里塞的是一堆零碎文件和脚本updater-script里写满了mount、package_extract_file这种指令。那种包用普通解压工具能打开里面是一堆目录和文件改起来也直观。A/B 无缝更新机制上线后逻辑完全变了。设备上有两套完整的系统分区槽位OTA 的过程是把完整的新镜像写到当前没在用的那个槽位写完之后切换启动槽重启就进新系统如果新系统起不来还能回滚到旧槽。这个机制要求更新包必须能整块整块地还原镜像而不是零散地覆盖文件。于是payload.bin登场了。它本质是一个容器里面装着若干个分区镜像的压缩数据块外加一份描述这些块分别属于哪个分区、要放到哪个块偏移上的清单。更新引擎读取清单把数据块解压后按偏移写进目标分区写完整个分区的哈希一对对上了就切换。这套设计带来两个直接后果你一定要记住第一payload.bin不是标准压缩包任何解压软件都打不开第二包里的数据是按块组织的块大小固定提取工具必须严格按清单里的偏移去还原不能自己乱猜。1.2 二十个字节的文件头payload.bin的开头是一段极简的二进制头结构固定到可以用二十个字节概括偏移长度含义04 字节魔数ASCII 为CrAU48 字节格式版本号大端 uint64常见值 2128 字节manifest 的长度大端 uint6420N 字节manifest 本体protobuf 序列化20N4 字节metadata 签名的长度大端 uint3224NM 字节metadata 签名protobuf24NM剩余所有分区的数据块按顺序紧密排列这个头最大的价值在于排查问题时能快速判断文件性质。用xxd payload.bin | head -2看一眼开头是43 72 41 55也就是CrAU就说明文件没问题如果看到的是50 4B 03 04ZIP 的PK魔数那说明你还拿着update.zip没解压如果全是00或者乱码那多半是下载截断了。版本号这一项也值得留意。我见过不少unsupported major version的报错根子在版本号上。版本 2 是目前的主流工具链对它的支持最完整。如果哪天冒出版本 3八成是格式有扩展先确认工具版本够不够新别急着怀疑文件损坏。1.3 manifest 决定了怎么把数据拼回去manifest 是 protobuf 编码的DeltaArchiveManifest里面信息量很大但真正影响提取的有三块。第一块是block_size通常是 4096。所有偏移量都以块为单位计数换算成字节要乘 4096。这一点在你自己写脚本解析时特别容易错我在第一次手写解析器时就把块偏移当字节偏移用了结果解出来的镜像整个错位排查了大半天。第二块是partitions列表。每个元素描述一个分区包含分区名、新分区的总大小、新分区的 SHA-256、以及一串operations。分区名就是system、vendor、boot、vendor_boot、super、product、odm这些工具的-l参数打印的就是这个列表。第三块是operations这是整个格式里最关键的部分。每个 operation 描述从数据区读哪一段、解压后写到目标分区的哪个块偏移。操作类型决定了数据怎么处理类型编码处理方式全量包常见度REPLACE0原始数据不压缩直接拷贝少见REPLACE_BZ1bzip2 压缩少见MOVE2旧数据搬移增量专用增量包BSDIFF3二进制差分增量专用增量包SOURCE_COPY4从旧镜像拷贝块增量专用增量包SOURCE_BSDIFF5基于旧数据的差分增量专用增量包ZERO6目标区段全填 0常见DISCARD7标记丢弃等价于填 0少见REPLACE_XZ8xz 压缩全量包主流PUFFDIFF9针对 deflate 数据的差分增量包BROTLI_BSDIFF10Brotli bsdiff增量包ZUCCHINI11专项差分算法增量包看懂这张表你就明白了一件事全量包和增量包是完全不同的两种东西。全量包full OTA里只有REPLACE_XZ、REPLACE_BZ、ZERO这几种自包含的操作光凭payload.bin一个文件就能完整还原所有镜像。增量包incremental OTA里则大量出现SOURCE_COPY、BSDIFF这类操作它们要求你提供完全相同版本的旧镜像作为输入缺一块都解不出来。这也是网上很多解包失败帖子的真正原因——不是工具不行是拿了个增量包在硬解。2. 工具选型为什么是 payload-dumper-go能解payload.bin的工具不止一个从最早的 Python 脚本到现在的 Go 实现还有直接调 AOSP 里update_engine编译出来的delta_generator。我前后用过四五种最后长期留在机器上的就是 payload-dumper-go原因不是它功能最全而是它在速度、依赖、容错这三件事上平衡得最好。2.1 并发模型带来的速度差先说最直观的速度。payload.bin里的operations之间是相互独立的每个 operation 的数据在文件里的偏移是固定的写入目标分区的块区间也是固定的理论上完全可以并行。Python 那套脚本大多数是老式单线程实现读一块、解一块、写一块串行到底。payload-dumper-go 把每个分区交给一个 goroutine 处理分区内部再按 operation 分发到 worker 池默认的 worker 数量在旧版本是 4新版本会跟随 CPU 核心数走也可以用-c手动指定。几个 worker 同时打开自己的文件句柄用ReadAt定位读互不干扰。我做过一次对比同一台机器8 核、NVMe 固态同一个 6.4 GB 的全量包方案耗时备注Python 单线程脚本约 14 分钟纯串行xz 解压吃满一个核payload-dumper-go-c 4约 2 分 40 秒CPU 占用约 400%payload-dumper-go-c 8约 1 分 50 秒CPU 占用约 700%payload-dumper-go-c 16约 1 分 45 秒边际收益明显下降数字不一定精确但趋势很清楚从 4 提到 8 收益巨大从 8 提到 16 基本没感觉。原因在于REPLACE_XZ的解压是纯 CPU 密集操作8 个核跑满之后瓶颈就从 CPU 转到了磁盘写入和内存带宽上。所以并发数不要盲目往上堆跑到核心数附近再加就没意义了。还有一个容易被忽略的点payload-dumper-go 是单个静态编译的二进制不依赖 Go 环境、不依赖 Python 运行时、不依赖一堆 pip 包。这意味着你在一台干净的机器上丢一个可执行文件就能跑这对经常换机器的场景太重要了。2.2 增量包支持的边界很多人以为 payload-dumper-go 只能解全量包这是老印象了。现在的版本已经支持-diff和-old参数能够处理增量包前提是你得准备好旧镜像目录。具体做法是先用同版本的完整包解出一套镜像放进目录目录里文件名为分区名.img然后把增量包和这个目录一起喂给工具。工具在处理SOURCE_COPY时会去旧镜像里按块读取在处理BSDIFF时会先读旧数据再做差分还原。但这里有个硬边界必须说清楚旧镜像的版本必须和增量包要求的源版本完全一致。差一个补丁级别都不行因为差分是基于字节级别的精确比对源数据差一个字节还原出来的结果就全错而且不一定报错可能解出来一堆能写进去但跑不起来的垃圾镜像。我踩过一次增量包要求的源版本和我手上的旧包差了半个月工具一声不响跑完了镜像 Timeline 一看全是脏数据。所以我的习惯是解增量包之前先把旧镜像的构建号build.prop里的ro.build.version.incremental和增量包元数据里声明的源版本对一遍对上了再动手。2.3 几种常见方案对照顺手把几种方案摆在一起你可以按自己的场景挑方案优点缺点适合场景payload-dumper-go单文件、并发快、支持增量参数不算多出错信息偏简洁日常拆包首选Python payload_dumper源码直白方便改逻辑单线程慢依赖 Python 环境学习格式、写定制解析AOSP 的 delta_generator权威功能最全编译门槛高依赖一堆库深度定制、做包各类图形化工具点点鼠标就行封装太厚出问题看不到根因一次性使用不推荐长期我的建议很简单先用 payload-dumper-go 把活干完遇到它解不动的情况再去翻源码。它的源码本身写得挺清楚格式解析部分不长配合update_engine的 proto 定义读比啃文档快得多。3. 环境准备与原料获取工具再好没有正确的原料也白搭。这一节讲怎么把工具装好、怎么把payload.bin从正确的位置拿下来、以及动手前必须做的那道校验。3.1 三种安装方式按省事程度排序。第一种直接下预编译二进制。项目发布页有各平台的压缩包Linux、macOS、Windows 都有对应版本。下载后解压把可执行文件丢进PATH里的任意目录chmod x一下就能用。这是绝大多数人的选择我也推荐这个因为省去了折腾工具链的时间。# Linux / macOS 示例 chmod x payload-dumper-go sudo mv payload-dumper-go /usr/local/bin/ payload-dumper-go -h第二种用go install。前提是你本机有 Go 环境1.20 以上比较稳。这种方式的好处是拿到的是最新代码而且升级只要重跑一次命令。go install github.com/ssut/payload-dumper-golatest装完之后二进制会落在$GOPATH/bin下记得把这个目录加进PATH不然会提示命令找不到。第三种从源码编译。适合想改代码或者跑在不常见架构上的情况比如 ARM 服务器、某些嵌入式板子。克隆仓库go build一条命令就出二进制交叉编译也很方便git clone https://github.com/ssut/payload-dumper-go.git cd payload-dumper-go go build -o payload-dumper-go # 交叉编译到 ARM64 GOOSlinux GOARCHarm64 go build -o payload-dumper-go-arm64装完之后跑一次payload-dumper-go -h确认能正常输出帮助信息不同版本参数会有细微出入先看帮助再照抄网上的命令这是我踩过的一个坑——网上很多教程是两年前写的参数名已经对不上了。3.2 从哪拿到 payload.bin三个常见来源按可靠程度排。来源一官方完整包。最稳妥。厂商通常会放出完整 OTA 包有时叫完整更新包恢复包下载下来就是一个update.zip。优点是不需要设备、不依赖权限文件完整性有官方哈希兜底。缺点是体积大动辄几个 GB。来源二设备本地缓存。系统在下载完 OTA 但还没应用时会把包暂存在/data/ota_package/目录下经常能直接看到payload.bin或者update.zip。这个路径需要较高权限才能访问普通用户拿不到。适合已经做过权限处理、只想快速拿当前版本包的场景。注意这个目录会被系统定期清理下载完尽快取走。来源三抓取更新过程中的中间文件。一些分析场景下可以在更新过程中拦截或导出中间产物。这条路径依赖环境配置稳定性最差非必要不走。拿到update.zip之后解压出payload.bin就行mkdir ota cd ota unzip ../update.zip payload.bin payload_properties.txt如果update.zip里还有care_map.pb、metadata、META-INF/com/android/metadata这些文件顺手一起解出来后面会用到。payload_properties.txt一定要留着校验全靠它。3.3 动手前的完整性校验这一步很多人跳过然后被各种莫名其妙的错误折磨。payload.bin动不动几个 GB下载中断、传输损坏、磁盘写满截断都有可能出现而损坏的文件在解压时往往不会立刻报错而是在某个随机位置崩掉排查成本极高。payload_properties.txt里通常有四个字段FILE_HASHbase64 编码的 SHA-256 FILE_SIZE字节数 METADATA_HASHbase64 编码的 SHA-256 METADATA_SIZE字节数FILE_HASH是整个payload.bin的 SHA-256注意是base64 编码的不是常见的十六进制。很多人拿sha256sum的输出直接去比怎么都对不上问题就出在编码上。用一小段 Python 把编码问题解决掉import hashlib, base64, sys, os path sys.argv[1] size os.path.getsize(path) h hashlib.sha256() with open(path, rb) as f: for chunk in iter(lambda: f.read(4 20), b): h.update(chunk) print(FILE_SIZE , size) print(FILE_HASH , base64.b64encode(h.digest()).decode())跑完拿输出的两行去和payload_properties.txt对全对上再往下走。这一步花两分钟能省掉后面可能的两小时。注意METADATA_HASH校验的是 manifest 那一段数据的哈希不是整个文件。如果你只想快速判断文件有没有下完校验FILE_SIZE加FILE_HASH就够了。METADATA_HASH在分析 manifest 内容时更有意义。4. 完整实操流程前面都是铺垫这一节是真正动手的部分。我按我平时的工作顺序来写你可以直接照着走。4.1 先看清单别急着解拿到payload.bin第一件事不是解压是列出里面有什么。payload-dumper-go -l payload.bin输出大概长这样boot dtbo init_boot odm product system system_ext vendor vendor_boot vendor_dlkm super这一步的价值在于两件事。一是确认文件本身能被正确解析如果这一条命令就报magic mismatch那就不用往下浪费时间了。二是让你知道包里的分区构成不同厂商、不同版本的分区集合差别很大有些包里有独立的system.img有些只有整合的super.img提前看清楚能避免后面白忙。还有个实用技巧把-l的输出存一份解完之后对照检查是不是所有分区都出来了。payload-dumper-go -l payload.bin partitions.txt wc -l partitions.txt4.2 全量提取与并发调优确认没问题就开始解payload-dumper-go -o ./out -c 8 payload.bin-o指定输出目录不指定的话工具会自动建一个带时间戳的目录比如extracted_20240612_143022。我习惯自己指定方便后续脚本引用。-c是并发数这是唯一一个真正需要调的参数。调多少合适取决于你的存储介质不完全取决于 CPU存储介质建议并发数原因NVMe 固态8 到 16随机读延迟极低不容易成为瓶颈SATA 固态6 到 8顺序读带宽充足并发太高反而抢带宽机械硬盘2 到 4磁头频繁跳转并发越高越慢移动硬盘 / 网络盘2延迟高并发意义不大判断是否调对了有个简单办法跑起来之后开一个终端看top或htop。如果 CPU 占用远没跑满、但磁盘 IO 打满了说明是 IO 瓶颈加并发没用该换盘了。如果 CPU 跑满了但耗时还是很长说明并发数已经吃满核心再加也没收益。关于内存也得心里有数。工具在处理一个 operation 时会把该 operation 的数据整段读进内存再解压所以峰值内存大致是并发数 × 单个 operation 的数据长度。大部分情况下单个 operation 数据在几 MB 到几十 MB 之间8 并发对应几百 MB 内存问题不大。但有些包的分区操作切得很碎有些又切得很粗遇到特别粗的16 并发可能吃掉好几个 GB。机器内存不到 8 GB 的话并发别开太高。提取过程中会看到分区的进度滚动正常结束后每个分区会打一行完成信息。如果某个分区中途报错但整体命令返回 0一定要回头检查这种情况通常意味着部分分区没解出来。4.3 只解想要的那几个分区大多数场景下你根本不需要全解。想看boot.img结构解一个boot就够了想改vendor里的配置解vendor和system也就够了。全解一个包动辄十几 GB纯属浪费。用-p指定分区名多个用逗号分隔# 只解启动相关分区 payload-dumper-go -p boot,init_boot,vendor_boot -o ./boot_out payload.bin # 只解系统相关分区 payload-dumper-go -p system,system_ext,vendor,product -o ./sys_out payload.bin分区名必须和-l输出完全一致大小写敏感。写错名字工具会直接告诉你找不到不会静默跳过这点挺好的。按需提取还有个隐性好处-p模式下工具只需要扫描 manifest 定位相关分区不需要为其他分区准备写入缓冲磁盘占用和耗时都能明显下降。我做过对比从 6.4 GB 的包里只解boot、init_boot、vendor_boot三个小分区耗时不到 5 秒输出总共不到 200 MB全解同一份文件要 2 分钟左右输出 14 GB。4.4 增量包怎么解增量包的处理要复杂一些核心是准备好正确版本的旧镜像。第一步确认旧镜像版本。把旧包解出来的system.img挂上去或者用debugfs读build.propdebugfs -R cat /system/build.prop system.img 2/dev/null | grep ro.build.version.incremental拿到构建号和增量包元数据里声明的源版本比对。元数据在META-INF/com/android/metadata里是个文本文件里面有pre-build和post-build两个字段pre-build就是源版本。第二步把旧镜像按分区名整理进一个目录文件名必须是分区名.imgold_images/ ├── boot.img ├── system.img ├── vendor.img └── ...第三步带上-diff和-old跑payload-dumper-go -diff -old ./old_images -o ./inc_out payload.bin工具在遇到SOURCE_COPY时会去old_images里读对应块遇到BSDIFF时先读旧数据再做差分还原。这里有几个坑我踩过旧镜像必须是解压后的 raw 镜像不能是 sparse 格式。如果你的旧镜像本身是稀疏的先simg2img转一遍否则读块会全错。目录里不能缺分区。增量包可能引用任何一个旧分区缺一个就整体失败工具报错时往往只提一个分区名容易误以为只缺那一个。保险的做法是把旧包全解一遍。分区大小必须匹配。同一版本不同地区、不同运营商的镜像大小可能不同哪怕构建号一样。这种情况不常见但要留意。4.5 输出镜像的二次加工工具跑完out/目录里是一堆.img。这些镜像未必能直接挂载取决于它们本身的格式。常见的几种后续处理如下。情况一镜像本身是 sparse 格式。特征是文件体积小于分区实际大小file命令识别不出来mount会报wrong fs type。用simg2img转 rawsimg2img system.img system_raw.img转换后会得到一个和分区容量等大的镜像几十 GB 的也有注意磁盘空间。情况二拿到了 super.img。现在绝大多数设备用动态分区super.img是个容器里面装着system、vendor、product这些逻辑分区但前面已经说过-l有时候不会单独列出它们。要拆开得用lpunpack# 先转 raw如果本身是 sparse simg2img super.img super_raw.img # 再拆逻辑分区 lpunpack --slot0 super_raw.img ./super_parts/ ls super_parts/ # 会得到 system_a.img、vendor_a.img、product_a.img 等拆出来的文件名带槽位后缀_a和_b是 A/B 两套。你解包得到的通常全是_a那套的数据因为payload.bin只装当前要写的那一套。情况三boot.img 需要解头。boot.img不是裸内核前面有 boot header可能是 v0 到 v4 多种版本。要拿里面的kernel、ramdisk用unpack_bootimgunpack_bootimg --boot_img boot.img --out ./boot_out ls boot_out/ # kernel、ramdisk、second、dtb 等init_boot和vendor_boot结构不同vendor_boot里的 ramdisk 是分段的解出来会有vendor_ramdisk、ramdisk等多个文件改的时候别搞混。情况四直接看文件系统内容。ext4 镜像不用挂载也能读debugfs就够# 列目录 debugfs -R ls -l /system/app system.img # 导出单个文件 debugfs -R dump /system/build.prop ./build.prop system.img如果镜像是 EROFS 格式新版系统里越来越常见debugfs不认得用fsck.erofs或erofsfuse挂载erofsfuse system.img /mnt/system ls /mnt/system第五步校验解出来的东西对不对。这一步别省。最简单的办法是看file输出file out/*.img正常的 ext4 会显示Linux rev 1.0 ext4 filesystem datasparse 会显示Android sparse imageboot 会显示Android bootimg。如果某个本该是 ext4 的文件显示data那基本就是解坏了回头查前面哪一步出的问题。5. 常见问题与排查实录这一节是我这几年攒下来的错误现场按出现频率排。看之前先记住一条原则报错信息永远是线索不是结论别看到一条就去搜先想想它在说什么。5.1 报错速查表现象大概率原因处理方式magic mismatch或not a payload file传入了update.zip本身先unzip出payload.binunsupported metadata version工具版本旧或格式版本高于支持范围升级 payload-dumper-go 到最新metadata signature verification failed文件截断或损坏按 3.3 节校验FILE_HASH解到某分区报unsupported op type这是增量包工具在找旧数据加-diff -old并确认旧镜像版本open old/system.img: no such file旧镜像目录结构不对文件名必须是分区名.img放在同层解完的分区缺斤少两-p参数写错或分区在该包中不存在用-l对照分区名no space left on device输出盘空间不足见 5.2 节容量估算进程被系统杀掉OOM并发太高单次读块太大降-c或加大内存/交换空间解出来能写进去但开不了机增量包用错了源版本严格核对pre-build输出目录里只有空文件磁盘配额限制或权限问题换目录、查挂载点配额Windows 下报路径过长输出路径层级太深换短路径比如D:\out这张表能覆盖八成的现场。剩下两成通常是环境问题比如权限、文件系统不支持大文件、杀毒软件锁文件这类换成干净环境复现一下基本就能定位。5.2 容量估算别在 90% 时才发现盘满payload.bin解压后的体积膨胀很明显因为里面是压缩数据。经验比例大致是1 : 2 到 1 : 2.5。一个 6.4 GB 的包全解出来差不多 14 到 16 GB。预估有个相对准的办法把-l的结果看一眼全量包的分区总容量基本等于payload.bin大小的两倍多。更保守的做法是按2.5 倍预留。# 看 payload.bin 大小 ls -lh payload.bin # 看输出盘剩余空间以 GB 计 df -BG /path/to/out除了解压输出还有两个吃空间的地方容易被忽略。一是simg2img转换后的 raw 镜像super.img转出来可能是二十多 GB。二是lpunpack拆出来的逻辑分区加起来又是一个super.img的大小。如果你打算走完解包—转 raw—拆分区这条完整链路预留空间按payload.bin的5 倍算比较稳妥。我吃过一次亏在只剩 30 GB 的盘上解一个 12 GB 的包解到system的时候盘满工具中断留下的是一堆半截镜像和一堆权限异常的临时文件清理起来比重新跑一遍还麻烦。现在我习惯在跑之前先df看一眼写在脚本第一步。5.3 几个反复踩到的坑坑一把 sparse 镜像当 raw 用。这是最高频的一类。payload.bin里存的分区镜像格式取决于构建配置有些是 raw ext4有些是 sparse。sparse 镜像的特点是开头有个魔数3A FF 26 ED用xxd | head -1一眼能看出来。判断方法很简单xxd -l 4 system.img输出3aff 26ed就是 sparse需要simg2img输出0000 0000之类的是 raw直接用。搞错了之后的表现是mount报wrong fs type或者debugfs报Bad magic number in super-block。坑二以为super.img就是全部。现在很多设备的-l输出里根本没有system、vendor只有一个super。这不代表包不完整而是这些逻辑分区被打进了super.img里。拿着super.img直接debugfs当然打不开因为它不是文件系统是个分区容器。得先lpunpack拆。坑三解包中途 CtrlC 后目录残留。中断之后输出目录里会留下不完整的镜像文件下次跑的时候如果目标文件名已存在工具的行为取决于版本有些会覆盖有些会报错。我的习惯是每次跑之前先清理输出目录或者用一个全新的目录名OUTout_$(date %Y%m%d_%H%M%S) mkdir -p $OUT payload-dumper-go -o $OUT -c 8 payload.bin坑四只看工具返回码不看输出内容。有些情况下工具会解出部分分区然后跳过其余最终返回 0。比如分区被-p过滤掉了这是正常的或者个别分区遇到了它不知道怎么处理的操作类型。所以跑完之后一定对照-l的结果核一遍缺了哪个心里要有数。坑五Windows 上的路径与文件锁。Windows 下有两个高频问题一是路径太长输出目录如果在很深的层级下加上镜像文件名容易超限二是安全软件会扫描新生成的大文件导致写入极慢甚至超时。解决办法很朴素输出到盘根目录下的短路径跑之前把输出目录加进安全软件的排除列表。坑六以为工具能修包。payload-dumper-go 是提取工具不是修复工具。FILE_HASH对不上就是文件坏了重下比什么都快。我见过有人试图用截断的文件硬解最后拿到一堆看起来像镜像实际上全是垃圾的东西浪费的时间远超重新下载。最后提一个我觉得挺有用的小技巧把常用命令包成一个小脚本输入包路径就自动完成校验哈希—列分区—按需解压—转 raw—拆 super这一整套。我自己那个脚本跑了三年改过十几版最大的价值不是省事而是保证每次流程一致不会因为某次手快漏掉校验步骤最后在别的环节浪费时间排查。我个人在实际操作中的体会是payload.bin这个东西的复杂度其实不在工具而在你有没有耐心把格式搞清楚。工具参数就那么几个看完帮助就能用真正决定你能不能顺利拿到可用镜像的是对分区结构、镜像格式、增量包机制这些底层细节的理解。把这些搞明白之后你会发现同类工具都长得差不多换个工具也就十分钟的事。