ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

MTK Android P差分包去除preloader分区:原理、排查与实操

MTK Android P差分包去除preloader分区:原理、排查与实操 在实际的MTK Android P项目里差分包里带不带preloader很多时候不是文档里写死的。我有好几次拿到编译产出的增量OTA包在实测机上刷完后lk阶段直接报preloader版本校验失败机器停在开机logo前怎么都进不了系统。最后定位到的根源并不是lk或者刷机流程本身而是差分包里被硬塞进了preloader分区而这块分区跟板子的DDR配置、安全启动密钥强绑定版本和板型稍有出入就是变砖的下场。这篇文章就把如何从差分包中去掉preloader分区这件事的原理、排查链路和可落地操作拆开讲清楚做ROM发布、OTA整包集成的同学可以直接照着排查。1. 先搞清楚 preloader 在差分包里的存在逻辑1.1 preloader 分区与其他分区有什么不一样MTK平台的启动链大致是这样BootROM - preloader - lk - kernel - init。preloader是SoC上电后由固化在芯片里的BootROM加载的第一段代码负责最基础的时钟、DDR初始化然后把lk加载进内存。它被烧在独立分区里分区名一般就叫preloader镜像文件名通常形如preloader_mt6765.bin、preloader_mt6771.bin。这个分区特殊在三点上跟硬件强绑定。preloader里包含DDR训练参数、时序配置、存储控制器初始化代码。同一个项目如果换过DDR颗粒、改过PCB走线preloader的bin也会跟着变。换句话说preloader并不是一个通用软件模块它是半硬件性质的东西。牵扯到安全启动链路。preloader是安全启动信任链的起点它要对lk做签名校验。很多人对preloader的签名机制理解有偏差以为只要能刷进去就能开机实际上MTK平台在preloader和lk之间还有一套版本回滚保护机制旧版本的preloader在某些项目上会被lk或者rpmb直接拒掉。产线是独立烧录的。量产阶段preloader一般由产线工具单独烧录并不依赖系统OTA。很多项目里preloader的版本和Android系统版本完全不是一个节奏。所以系统OTA差分包理论上根本不应该去动preloader。但是在实际构建链路里因为打包脚本是扫描所有镜像分区的通用逻辑preloader很容易被当成普通分区一并收进去产生一个不明不白的更新项。1.2 差分包打包链路里preloader 是从哪个环节混进来的Android P的差分包生成核心入口是build/tools/releasetools/ota_from_target_files.py。MTK平台会在这套AOSP原生逻辑之上做二次封装常见路径在vendor/mediatek/proprietary/scripts/releasetools/里面一般有MTK自己的ota生成脚本或patch比如mtk_ota_package.py这类模块。整个流程大致是编译产出target-files包也就是*-target_files-*.zip。ota脚本解析这个target-files包里的IMAGES/目录和META/目录。对比旧target-files和新target-files对每个有差异的分区执行bsdiff/imgdiff生成差分数据。如果分区在新包里存在、旧包里也存在但内容有变化就会在升级脚本里加入对应的package_extract_file、write_raw_image或类似命令。问题就出在第三步。MTK把preloader、lk、tee这类非Android常规分区同样放在了IMAGES/目录下。通用脚本不区分这些分区的硬件属性只要发现新旧包不一致就会把它当作一个普通分区来差分。而preloader恰恰每次版本迭代都可能变一有变化就会进入差分包。另外一个容易混入的入口是META/ab_partitions.txt。A/B设备上update_engine是通过这个清单决定处理哪些分区的。MTK在生成target-files包时如果项目启用了A/B升级preloader会按惯例被写进这个清单导致后面生成增量payload时被自动包含。所以去掉preloader要处理的就是两个层面的东西一个是target-files里的实际镜像文件另一个是META清单里的分区登记信息。2. 动手前先验明正身怎么确认差分包里有 preloader2.1 打开 OTA 包直接检查在动手改任何构建脚本之前第一件事是确认你手里这个差分包里到底有没有preloader。直接用解压命令看最快unzip -l incremental_ota.zip | grep -i preloader如果输出里出现类似preloader_mt6765.bin或preloader.img的文件条目那就说明包里有preloader相关的payload数据。但这只是第一步还要看升级脚本里有没有实际执行preloader写入动作。Android P的整包升级脚本在META-INF/com/google/android/updater-script差分包也一样。继续解压看unzip -p incremental_ota.zip META-INF/com/google/android/updater-script | grep -i preloader如果发现package_extract_file(preloader_xxx.bin, ...)、write_raw_image(... preloader)或者block_image_update对preloader分区做payload应用等命令那说明这个差分包刷机时确实会去更新preloader分区。对于打开了A/B升级的MTK项目检查方式不同。差分包里没有updater-script而是一个payload.bin和payload_properties.txt。可以这样确认分区列表python /path/to/android/build/tools/releasetools/delta_generator --inspect --partitions payload.bin或者用合法的OTA工具链里的payload_info.py脚本查看payload包含哪些分区。结果里出现preloader基本可以断定它被包含在了升级payload中。2.2 从 target-files 逆向确认打包输入差分包是从两个target-files包生成的所以要找根因必须看target-files包里有没有preloader相关内容和META登记信息。编译中间产物通常在out/target/product/board/obj/PACKAGING/target_files_intermediates/product-target_files-*.zip用unzip列出关键位置unzip -l product-target_files-*.zip | grep -i preloader重点看两处IMAGES/preloader_*.bin这是实际镜像。如果这个文件在preloader就有了被差分的基础。META/ab_partitions.txt这个文本文件里每一行是一个分区名。如果存在preloader这一行A/B payload生成时几乎一定会带上它。还有一个检查点是META/misc_info.txt。这个文件存储了构建时的关键参数比如ab_updatetrue/false、has_recovery、system_size等。部分MTK项目会在里面写跟preloader相关的自定义字段比如mtk_preloader_includetrue此时打包脚本就会根据这个标志做判断。可以搜索一下unzip -p product-target_files-*.zip META/misc_info.txt | grep -i preloader从这几处就能把preloader的存在路径完全摸清。我遇到过的绝大多数情况是IMAGES/preloader_*.bin存在同时META/ab_partitions.txt里也登记了preloader两者共同导致差分包带上它。3. 三个去掉 preloader 分区的可行方案3.1 方案A在 releasetools 脚本里把 preloader 过滤掉这个方案适合在源码层面彻底解决问题每次出包都不用额外动手。核心思路是修改MTK仓库里的releasetools脚本在生成差分包的目标分区列表时把preloader从清单里摘掉。以AOSP原生的ota_from_target_files.py逻辑为例它的入口会在增量模式调用WriteBlockIncrementalOTAPackage或WriteABOTAPackageMTK二次封装的脚本通常会在外层写一个自己的函数遍历分区列表并调用AOSP的差分函数。我们的切入点就在这里思路如下# 伪代码示意具体函数名以你仓库实际为准 BLOCKED_PARTITIONS {preloader, preloader_ufs} def FilterOutPreloader(partitions): return [p for p in partitions if p not in BLOCKED_PARTITIONS]在MTK脚本里找出构建分区列表的位置比如解析ab_partitions.txt或扫描IMAGES/得到分区名集合的代码对这个列表做一次过滤让preloader不要进入后续的差分逻辑。同时也要在生成升级脚本的地方做同样处理以免某些项目在写edify脚本时单独为preloader生成命令。如果不想改动MTK封装的脚本也可以直接在生成差包的命令行入口套一层wrapper。比如写一个shell脚本#!/bin/bash # 过滤掉 target-files 中的 preloader 后再调用原版 ota_from_target_files workdir$(mktemp -d) unzip -q $1 -d $workdir/new_tf # 删除 preloader 相关镜像 rm -f $workdir/new_tf/IMAGES/preloader* # 从 ab_partitions.txt 中移除 preloader 登记 sed -i /^preloader$/d $workdir/new_tf/META/ab_partitions.txt # 重新打包成干净的 target-files cd $workdir/new_tf zip -q -r $workdir/new_tf_clean.zip . # 调用原始生包命令 ota_from_target_files -i $2 $workdir/new_tf_clean.zip -k $KEY $OUTPUT_OTA这里的-i就是生成差分包时用到的旧target-files包。需要说明的是这个wrapper方案是一种应急手段好处是改动面小、不出源码问题坏处是每次出包都要记得套这层而且如果项目分支多、打包频次高很容易漏。长期维护建议还是落到releasetools脚本里。3.2 方案B改 META 信息让生成逻辑无 preloader 可用第二个思路是直接修改target-files包里的META信息。既然生成差分包的脚本是依据META/ab_partitions.txt和META/misc_info.txt里的标志做判断的那就把这些登记信息改成没有preloader。这个方法不需要改代码甚至不需要重新编译只需要在上一步的检查基础上手动处理target-files包。操作流程是解包新target-files和旧target-files如果有必要。删除IMAGES/目录下的preloader*文件。编辑META/ab_partitions.txt删掉preloader那一行。如果META/misc_info.txt里有类似has_preloadertrue、mtk_preloader_includetrue这样的标志改为false或直接删掉。重新打包target-files。用修改后的包执行正常的差分包生成命令。这个方案看起来简单但有几个地方必须注意修改target-files包之后其内部文件的hash可能被签名/完整性校验机制检测到。好在MTK和AOSP的target-files包在OTA生成阶段并不会对自身做严格hash自校验它更信任解压后的目录结构。因此这个方案在绝大多数Android P项目上是可行的。如果旧target-files里也有preloader新包里删掉了AOSP脚本在比较时发现旧有、新无可能会认为该分区被删除从而生成一个删除分区的动作。这个行为需要格外留意。好在preloader分区在分区表里是真实存在的而OTA脚本并不会真的删除物理分区一般是忽略或者报错。实际项目里要跑一遍确认升级脚本里没有异常命令。建议同时检查META/care_map.pb、META/otatools.xml之类文件是否包含preloader信息如果不包含则不影响如果包含建议一并处理防止个别脚本按这些清单做二次校验。这个方案比较适合临时救急或者你手上已经有编好的target-files不想为了改配置重新编译整个工程。但它需要严格走一遍回归测试因为手工改包比较容易产生其他脏数据。3.3 方案C在构建期就不产出 preloader 目标第三个方案是从构建源头切断让target-files包在编译阶段压根不生成IMAGES/preloader_*条目。这样后面releasetools自然找不到preloader可差分逻辑最干净。MTK Android P的构建系统里preloader镜像的产出链路大概长这样vendor/mediatek/proprietary/scripts/build_pl.mkMTK的preloader构建脚本具体名称随版本可能不同负责编译preloader。编出来的bin文件会被拷贝到out/target/product/board/相关目录然后被target-files的打包逻辑收进IMAGES/。device/mediatek/board/BoardConfig.mk或ProjectConfig.mk里通常会有是否编译preloader的开关比如BUILD_PRELOADER、MTK_PRELOADER_SUPPORT之类。要找到当前项目的开关可以这样搜索grep -rn PRELOADER device/mediatek/board/BoardConfig.mk grep -rn preloader vendor/mediatek/proprietary/scripts/build_pl.mk如果确认preloader只是被PRODUCT_PACKAGES引用进来可以直接在device mk文件里把对应条目注释掉或置空。还有一种情况是target-files的IMAGES/生成逻辑在build/core/Makefile里它会对一些固定的镜像名做收集比如$(foreach p, $(TARGET_PRELOADER_IMAGE), \ $(eval $(call add-radio-image,$(p),$(p)))这里的TARGET_PRELOADER_IMAGE如果被定义成了某个值preloader就会作为radio image被收进target-files。在BoardConfig.mk里把TARGET_PRELOADER_IMAGE清空或者删除add-radio-image调用也能达到目的。需要强调的是这个方案影响的不只是差分包还包括整包OTA和target-files本身。如果你们产线工具依赖target-files里的preloader做独立烧录那么强行从构建期移除preloader会导致产线脚本拿不到镜像。所以方案C适合系统版本和preloader版本完全解耦、产线独立管理preloader的项目使用前要和产线同事确认好。4. 去掉 preloader 之后的回归验证与隐藏坑4.1 如何验证升级后 preloader 未被篡改把preloader从差分包里去掉之后不能只看差分包里没有preloader文件就认为事完了关键是刷机后设备行为要对。我自己的回归验证路径一般分四步第一步确认差分包升级脚本。非A/B设备看updater-script里没有preloader相关命令A/B设备用delta_generator或者payload_info确认payload分区列表里没有preloader。第二步在一台可以接受风险的原型机上刷入差分包。刷完后不要急着进系统先在lk阶段观察串口日志。如果preloader没有被OTA动过lk启动时不会报preloader校验异常。重点抓取下面这几类日志关键字preloader version CHECK_PL pl verify fail第三步进入系统后检查当前preloader版本和刷机前是否一致。MTK一般可以在内核或用户态通过读分区节点拿到版本信息例如cat /dev/block/by-name/preloader strings /dev/block/by-name/preloader | grep -i mt[0-9]*更稳妥的方式是直接对比整块分区的md5。刷机前记录一次dd if/dev/block/platform/bootdevice/by-name/preloader 2/dev/null | md5sum刷完差分包后再算一次两者一致就说明preloader确实没被动过。第四步连续做三次OTA递进升级验证的不是一次刷机成功而是后续版本继续升级时不会因为preloader版本不一致被防回滚机制卡住。这一步最容易被忽略但恰恰是问题最多的地方。4.2 几个容易反复出现的隐藏坑坑一删了分区文件却忘了删ab_partitions登记。手工改target-files最常犯的错。如果你只删了IMAGES/preloader_*但META/ab_partitions.txt里还留着preloaderA/B设备上update_engine会在payload生成阶段尝试对preloader做差分虽然源文件缺失会导致脚本报错但报错时机和表现形式各异有的卡在partition not found有的直接生成一个空的preloader payload刷机时因为找不到目标数据而失败。排查这个坑的时候必须同时检查IMAGES和META两个位置。坑二updater-script里残留隐藏的分支命令。MTK的releasetools封装在某些项目里会把preloader写在条件分支里比如if getprop(ro.boot.hardware) mt6765 || getprop(ro.boot.hardware) mt6768 then package_extract_file(preloader_mt6765.bin, /dev/block/by-name/preloader) endif如果你只删了preloader bin文件但没处理updater-scriptrecovery在跑脚本时会因为找不到preloader_mt6765.bin直接中断升级报E:Error in ...。所以改完以后要做的第一件事就是把updater-script从zip里拉出来全文搜索preloader关键字确认没有任何残余命令。坑三防回滚机制带来的连锁问题。如果这个项目的preloader启用了防回滚而之前的差分包已经带过一次preloader升级设备上的preloader版本已经升高了后面的差分包里如果不带preloader那没问题。但如果你在同一个设备上来回切换旧系统版本做测试lk可能因为检测到当前preloader版本比lk预期的版本新或旧而拒绝引导。这个现象表面上看像preloader OTA的问题其实是你测试流程里混用了带preloader和不带preloader的包。解决办法是测试时固定一根线要么所有包都不带preloader要么全部使用产线同版本preloader不要交叉验证。坑四误删了厂商定制分区。MTK的target-files里偶尔会有和preloader命名相似的分区比如preloader_ufs、pl_linux或者其他以pl开头的镜像。用grep -i preloader过滤时会把它们也匹配出来。删的时候只能过滤真正的preloader分区文件不要一刀切全删。否则可能导致lk或tee加载路径依赖的镜像缺失虽然开机进入系统了但设备休眠唤醒后死机、相机等外设工作异常。这类问题在回归测试里极难在短时间暴露最好通过关键字白名单严格限定删除范围。坑五手工重打包target-files后文件权限变化。用zip命令重新打包target-files时如果不注意软链接和权限位可能出现recovery阶段无法读取镜像的诡异错误。AOSP的target-files包里有部分文件是符号链接比如system/目录下存在大量symlink。手工解压再重新打包必须保留symlink属性zip -q -r -y new_tf_clean.zip .-y参数用来保留符号链接。如果没有加打包过程会解引用符号链接导致zip包体积膨胀并且刷机时恢复分区里的文件结构与原包不一致。对于A/B系统还可能影响system分区的哈希校验结果。坑六只改了出包脚本但没同步到CI配置。这个属于流程层面的坑。方案A改脚本时如果你们公司的打包任务跑在CI上而CI配置里直接调用的还是老的打包入口那么本地验证通过不代表线上有效。我见过不止一次代码改了、本地编包正常、人工验证没问题结果CI出来的差分包还是有preloader。原因就是CI调用的是另一个wapper脚本或者构建环境里还挂着一个老版本的releasetools模块。所以排查时记得把CI的构建日志拉出来确认实际执行的命令和参数。4.3 遇到旧包有 preloader、新包没有的边界情况在有历史包袱的项目里会碰到一种情况旧的target-files里带preloader新的target-files你已经按方案C去掉了。生成差分包时差分工具发现旧分区存在但新分区不存在处理逻辑可能产生两种行为行为一直接忽略这个分区不生成任何升级动作。行为二生成一个删除该分区的标记recovery阶段按标记去清空preloader分区。第二种行为是绝对不能出现的因为preloader一旦被清空或写坏设备直接变砖。碰上这种情况最稳的处理方式不是在新包上删preloader而是在旧包和新包里都保留同一个版本的不变preloader文件。具体操作是把旧target-files里的preloader_*.bin原封不动拷到新target-files的IMAGES/目录并且在META清单里也保留原始登记。这样差分工具看到新旧preloader是一模一样的就会自动跳过该分区不会生成任何升级动作。这个办法虽然看起来是在保留preloader但实际效果等于把preloader排除在升级流程之外而且不触发旧有、新无的异常分支。如果旧包已经被弄得无法追溯还有一个兜底办法直接生成完整包full OTA不做增量差分。完整包只看新target-files的内容新包里没有preloader完整包里自然也不会有。当然这会牺牲包体大小和升级流量只适合作为临时方案。5. 经验总结这三种方案怎么选放在最后说是因为我觉得这个比前面的操作步骤更值得被记住。方案A改releasetools脚本适合长期维护的项目一次修改、长期有效但要对MTK二次封装的release工具链有足够了解不建议在没搞清楚脚本调用关系的情况下盲改否则容易破坏其他分区的打包逻辑。方案B改META信息重打包target-files适合临时应急、不想重新编译整个系统的场景操作直接但只对当次出的包有效无法根治问题。方案C构建期不产出preloader目标是最干净的长期方案但它把preloader从整条OTA链路里摘除需要确认产线、售后和recovery方案里没有依赖OTA来更新preloader的逻辑。一般情况下我在正式项目里倾向组合使用方案A和方案C构建期切断preloader产出是治本releasetools脚本里加一道过滤是兜底。这样即使某个分支上因为配置原因又把这个分区带回了target-files出包时也会被脚本挡在差分流程外。最后再分享一个实际操作里的小技巧改完脚本或target-files之后可以在出包前手动跑一次最小差分包来验证——拿两个只改了版本号的构建产物去生成差分包然后解包搜索preloader关键字。这样验证成本极低不用等整段回归测试就能第一时间暴露问题。等到整包级回归时再重点看lk日志、md5对比和连续两次OTA的稳定性。踩过几次preloader的坑之后我现在的习惯是每次差分包发布清单里都要有一行明确的检查结果preloader included: NO没有这一项包不上线。
返回列表