Android OTA 升级 之 UpdateEngine

Python3.8

Python3.8

Conda
Python

Python 是一种高级、解释型、通用的编程语言,以其简洁易读的语法而闻名,适用于广泛的应用,包括Web开发、数据分析、人工智能和自动化脚本

 下面根据源码和日志依次介绍Android新引入的无缝升级流程,其主要通过update_engine后台服务来完成,当然这里参与工作的肯定不仅仅只有他,但他一定是重要的入口,因此这里简称UpdateEngine。

一、流程解析

1、Application层客户端的调用

参考大佬文章:https://blog.csdn.net/guyongqiangx/article/details/80820399

2、UpdateEngine服务端进程

Android Update Engine分析(四)服务端进程_brillo::flaghelper-CSDN博客

3、bootloader如何激活分区槽

4、update_verifier 针对分区的校验

5、update_engine针对OTA升级结果的检查

二、案例介绍

1、案例之ErrorCode::kDownloadInvalidMetadataMagicString (21)

问题背景:在研究A14升级A16的时候,因为分区表存在改变,高通提供了一套方案,最后通过命令update_engine_client --update直接调用update_engine升级出现异常:

1)通过py脚本解析update.zip

高通提供了一个脚本来解析update.zip升级包,并进行拼接update_engine_client命令,python代码如下:

import sys
import zipfile
def main():
    # 调用该脚本至少传递一个参数,这个参数为ota zip升级包路径
    if len(sys.argv) != 2:
        sys.stderr.write('Use: %s <ota_file.zip>\n' % sys.arv[0])
        return 1
    # 得到ota.zip包,并解压打开
    otazip = zipfile.ZipFile(sys.argv[1], 'r')
    # 得到ota.zip包中的payload.bin文件信息
    payload_info = otazip.getinfo('payload.bin')
    # 得到payload.bin的偏移量和大小
    payload_offset = payload_info.header_offset + len(payload_info.FileHeader())
    payload_size = payload_info.file_size
    # 拼接update_engine_client命令,指定升级包在手机的路径
    payload_location = '/data/ota_package/update.zip'
    headers = otazip.read('payload_properties.txt')
    # 输出update_engine_client完整命令
    print ('update_engine_client --update --follow --payload=file://{payload_location}' ' --offset={payload_offset} --size={payload_size}' ' --headers="{headers}"').format(**locals())
    return 0

if __name__ == '__main__':
    sys.exit(main())

2)update_engine_client命令解析

update_engine_client是android内置的可执行文件,即一个客户端进程,如果想通过update_engine方式进行OTA升级,那么最终的逻辑其实就是调用update_engine_client命令的方式直接进行,update_engine_client定义如下:

如下案例update_engine_client必须要跟的几个参数:

update_engine_client --update --follow --payload=file:///data/ota_package/update.zip --offset=5078 --size=2222987234 --headers="FILE_HASH=dts7TG6XYQlR8bshAnz7dB7xP+hTmEvFgNYmj4/M+xc=
FILE_SIZE=2222987234
METADATA_HASH=HWgLVjf3mVAXgY73I97gFOJDHwIDNddjxlS4u/Vwjcs=
METADATA_SIZE=163935
"

  • --update:表示立即启动ab无缝升级,即会立即调用update_engine服务端进程,开启ab无缝升级流程

  • --follow:实时跟踪update_engine的过程,直到升级完成或者失败。通过update_engine的onStatusUpdate回调持续监控更新进度,返回最终状态码

  • --payload:指定升级包的路径,file:///data/ota_package/update.zip表示本地目录/data/ota_package/update.zip。注意需确保文件存在且可读,否则更新失败

  • --offset:指payload.bin在ota.zip压缩包中的偏移量,因为要进行魔数判断,此题的原因就是魔数判断失败

  • --size:指ota.zip升级包的总大小,如果不正确可能导致升级包文件不完整被截断

  • --headers:指payload_properties.txt在ota.zip压缩包中的信息,该文件记录了升级包元数据,非常的重要,如上脚本只是做了单纯的拷贝操作

3)ErrorCode::kDownloadInvalidMetadataMagicString

在执行如上命令的时候,终端直接打印如下表示失败:

核心日志为INFO:update_engine_client_android.cc(104)] onPayloadApplicationComplete(ErrorCode::kDownloadInvalidMetadataMagicString (21))

因为终端显示的信息太少,因此同时抓logcat | grep update_engine来执行如上命令:

核心日志:

# 偏移量为5078的时候

E update_engine: [ERROR:payload_metadata.cc(64)] Bad payload format -- invalid delta magic: 0000010b Expected: 43724155

# 偏移量为0的时候

E update_engine: [ERROR:payload_metadata.cc(64)] Bad payload format -- invalid delta magic: 504b0304 Expected: 43724155

4)ParsePayloadHeader魔数校验

如上代码逻辑,通过memcmp直接判断分区对应偏移量的四个字节,然后比较是否为43724155,如果不等,就设置状态码为ErrorCode::kDownloadInvalidMetadataMagicString

魔数是什么呢?魔数是文件格式的描述信息,一般通常为4连续的字节,如上日志中的关键魔数:

  • 43724155:对应CRAU,表示可执行bin文件
  • 504b0304:对应PK\x03\x04,表示zip文件 

5)问题解决

当前已经确认为魔数校验失败,因此可以猜测为payload.bin的offset计算有问题,因此我使用UltraEdit编辑器打开ota包,查找连续字符43724155,通过多个OTA包对比得出如下结论:

update.zip解压后的路径:META-INF/com/android/metadata中记录的offset信息才是正确的

即如上脚本不应该新增偏移量,应该改成:

import sys
import zipfile
def main():
    # 调用该脚本至少传递一个参数,这个参数为ota zip升级包路径
    if len(sys.argv) != 2:
        sys.stderr.write('Use: %s <ota_file.zip>\n' % sys.arv[0])
        return 1
    # 得到ota.zip包,并解压打开
    otazip = zipfile.ZipFile(sys.argv[1], 'r')
    # 得到ota.zip包中的payload.bin文件信息
    payload_info = otazip.getinfo('payload.bin')
    # 得到payload.bin的偏移量和大小
    payload_offset = payload_info.header_offset
    payload_size = payload_info.file_size
    # 拼接update_engine_client命令,指定升级包在手机的路径
    payload_location = '/data/ota_package/update.zip'
    headers = otazip.read('payload_properties.txt')
    # 输出update_engine_client完整命令
    print ('update_engine_client --update --follow --payload=file://{payload_location}' ' --offset={payload_offset} --size={payload_size}' ' --headers="{headers}"').format(**locals())
    return 0

if __name__ == '__main__':
    sys.exit(main())

执行如上脚本,生成update_engine命令:

解压OTA包的META-INF/com/android/metadata文件,拿到payload.bin:3296的值,并替换为update_engine命令的offset参数。

6)问题总结

ErrorCode::kDownloadInvalidMetadataMagicString表示魔数校验失败,从上面日志可以看出,当offset为0的时候,读出来的就是zip的魔数504b0304,当offset为5078的时候,读取出来是一个未知的魔数,这段逻辑其实是校验升级包中是否存在payload.bin文件的魔数,因此预期43724155,与预期不符合,认为压缩包被损坏,但是经过recovery验证改压缩包是可以正常升级的

2、案例之ErrorCode::kOverlayfsenabledError (64))

问题背景:在研究A14升级A16的时候,紧接着如上kDownloadInvalidMetadataMagicString问题解决之后,修改了offset偏移量,魔数校验失败问题已经解决,但是通过如上命令升级还是会出现异常:

1)ErrorCode::kOverlayfsenabledError

12-17 12:42:48.510  2131  2131 E update_engine: [ERROR:dynamic_partition_control_android.cc(500)] overlayfs overrides are active and can interfere with our resources.
12-17 12:42:48.510  2131  2131 E update_engine: run adb enable-verity to deactivate if required and try again.
12-17 12:42:48.517  2131  2131 E update_engine: [ERROR:delta_performer.cc(925)] Unable to initialize partition metadata for slot B ErrorCode::kOverlayfsenabledError
12-17 12:42:48.519  2131  2131 E update_engine: [ERROR:delta_performer.cc(456)] Failed to parse manifest
12-17 12:42:48.521  2131  2131 E update_engine: [ERROR:download_action.cc(224)] Error ErrorCode::kOverlayfsenabledError (64) in DeltaPerformer's Write method when processing the received payload -- Terminating processing

ErrorCode::kOverlayfsenabledError (64) 表示 OverlayFS(覆盖文件系统)处于活动状态,干扰了 OTA 更新过程中的动态分区操作。

相关代码逻辑根据关键日志可以定位如下:OverlayFS 会拦截对动态分区元数据的操作,导致 PreparePartitionsForUpdate() 失败

2)ErrorCode::kInstallDeviceOpenError

11-16 12:01:29.558  1881  1881 I update_engine: [INFO:dynamic_partition_control_android.cc(351)] Loaded metadata from slot A in /dev/block/bootdevice/by-name/super
11-16 12:01:29.587  1881  1881 E update_engine: [ERROR:snapshot.cpp(3153)] Cannot create update snapshots with overlayfs setup. Run `adb enable-verity`, reboot, then try again.
11-16 12:01:29.590  1881  1881 E update_engine: [ERROR:dynamic_partition_control_android.cc(975)] Cannot create update snapshots: Error
11-16 12:01:29.592  1881  1881 E update_engine: [ERROR:dynamic_partition_control_android.cc(520)] PrepareSnapshotPartitionsForUpdate failed in Android mode
11-16 12:01:29.594  1881  1881 E update_engine: [ERROR:delta_performer.cc(844)] Unable to initialize partition metadata for slot B
11-16 12:01:29.597  1881  1881 E update_engine: [ERROR:download_action.cc(226)] Error ErrorCode::kInstallDeviceOpenError (7) in DeltaPerformer's Write method when processing the received payload -- Terminating processing
11-16 12:01:29.646  1881  1881 I update_engine: [INFO:multi_range_http_fetcher.cc(177)] Received transfer terminated.

如上的日志,第一行报错无法创建快照,是因为overlayfs被启用,执行adb enable-verity然后重启再试,即kInstallDeviceOpenError (7)和kOverlayfsenabledError (64)原因基本一致。

3)问题总结

对kInstallDeviceOpenError (7)和kOverlayfsenabledError (64)的原因都是adb disable-verity引起,是因为我在升级前需要做adb remount 需要解锁

AI推荐的一个方案,启用dm-verity会自动禁用overlayfs机制

#连接设备并获取 root 权限
adb root
#启用 verity(这会自动禁用 overlayfs)
adb shell enable-verity
#重启设备
adb reboot
#重启后验证 overlayfs 已禁用
adb shell ls -la /dev/block/dm-* | grep overlay
#没有任何输出表示已经禁用了overlay

执行如上命令之后已经启用dm-verity,然后重新执行update_engine_client --update --follow进行升级,正常进入update_engine流程

3、案例之ErrorCode其他汇总

1)ErrorCode::kDownloadTransferError (9)

该错误码表示OTA包找不到,或者没有读写权限打开,关键日志如下:

11-15 14:55:41.241  1824  1824 I update_engine: [INFO:download_action.cc(85)] Marking new slot as unbootable
11-15 14:55:41.345  1824  1824 I update_engine: [INFO:multi_range_http_fetcher.cc(45)] starting first transfer
11-15 14:55:41.348  1824  1824 I update_engine: [INFO:multi_range_http_fetcher.cc(74)] starting transfer of range 5182+2252404532
11-15 14:55:41.350  1824  1824 E update_engine: [ERROR:file_stream.cc(-1)] unknown(...): Domain=system, Code=ENOENT, Message=No such file or directory
11-15 14:55:41.352  1824  1824 E update_engine: [ERROR:file_fetcher.cc(87)] Couldn't open /data/ota_package/update.zip
11-15 14:55:41.354  1824  1824 I update_engine: [INFO:multi_range_http_fetcher.cc(172)] Received transfer complete.
11-15 14:55:41.357  1824  1824 I update_engine: [INFO:multi_range_http_fetcher.cc(129)] TransferEnded w/ code 404
11-15 14:55:41.359  1824  1824 I update_engine: [INFO:multi_range_http_fetcher.cc(144)] Didn't get enough bytes. Ending w/ failure.
11-15 14:55:41.392  1824  1824 I update_engine: [INFO:action_processor.cc(116)] ActionProcessor: finished DownloadAction with code ErrorCode::kDownloadTransferError
11-15 14:55:41.394  1824  1824 I update_engine: [INFO:action_processor.cc(121)] ActionProcessor: Aborting processing due to failure.
11-15 14:55:41.398  1824  1824 I update_engine: [INFO:update_attempter_android.cc(583)] Processing Done.
11-15 14:55:41.413  1824  1824 I update_engine: [INFO:update_attempter_android.cc(836)] Clearing update complete marker.
11-15 14:55:41.415  1824  1824 I update_engine: [INFO:update_attempter_android.cc(731)] Terminating cleanup previous update.
11-15 14:55:41.418  9419  9419 I update_engine_client: [INFO:update_engine_client_android.cc(95)] onStatusUpdate(UPDATE_STATUS_IDLE (0), 0)
11-15 14:55:41.418  9419  9419 I update_engine_client: [INFO:update_engine_client_android.cc(103)] onPayloadApplicationComplete(ErrorCode::kDownloadTransferError (9))

解决方案通常检查文件是否存在,是否有读写权限,如上例子是update.zip文件不存在,后续文件存在后问题依旧,是权限不够,执行chmod 777 update.zip解决此问题。

2)ErrorCode::kDownloadOperationHashMismatch (29)

该错误码表示升级包存在异常或被损坏,关键日志如下:

01-31 14:46:50.051  1808  1808 E update_engine: [ERROR:delta_performer.cc(1219)] Hash verification failed for operation 418. Expected hash = CBF1FD7E89A084DDA3278F46854DA5D2647B45EBA57AE877D76E3903FC679D12
01-31 14:46:50.056  1808  1808 E update_engine: [ERROR:delta_performer.cc(1222)] Calculated hash over 611592 bytes at offset: 292336026 = C6918AF8DFE8B20CCC7122DAE0DD9ED0FC6CBFDEEB8AC89058CADB4DDAC51F73
01-31 14:46:50.059  1808  1808 E update_engine: [ERROR:delta_performer.cc(630)] Mandatory operation hash check failed
01-31 14:46:50.061  1808  1808 E update_engine: [ERROR:download_action.cc(226)] Error ErrorCode::kDownloadOperationHashMismatch (29) in DeltaPerformer's Write method when processing the received payload -- Terminating processing
01-31 14:46:50.578  5011  5138 V Software update: SotaIntentHandler: onUpdateEngineStatusUpdate: prog: 0, status 0
01-31 14:46:50.578 13727 13727 I update_engine_client: [INFO:update_engine_client_android.cc(103)] onPayloadApplicationComplete(ErrorCode::kDownloadOperationHashMismatch (29))

期望ota包的hash值与实际计算出来的hash值完全不一样,可能远程下载ota包或者拷贝ota包过程中存在传输异常,重新拷贝,即可正常。

3)ErrorCode::kDownloadMetadataSignatureMismatch (26)

如上关键日志可以看出来,是在对ota.zip包进行签名校验,校验失败,关键日志:

11-15 14:48:30.849  1858  1858 I update_engine: [INFO:delta_performer.cc(984)] Verifying using certificates: /system/etc/security/otacerts.zip
11-15 14:48:30.855  1858  1858 I update_engine: [INFO:payload_verifier.cc(101)] signature blob size = 267
11-15 14:48:30.859  1858  1858 I update_engine: [INFO:payload_verifier.cc(117)] Truncating the signature to its unpadded size: 256.
11-15 14:48:30.862  1858  1858 I update_engine: [INFO:payload_verifier.cc(190)] Failed to verify the signature with 1 keys.

11-15 14:48:30.867  1858  1858 E update_engine: [ERROR:payload_verifier.cc(136)] None of the 1 signatures is correct. Expected hash before padding: //预期的填充前哈希值
11-15 14:48:30.869  1858  1858 I update_engine: [INFO:utils.cc(461)] Logging array of length: 32
11-15 14:48:30.872  1858  1858 I update_engine: [INFO:utils.cc(478)] 0x00000000 : cb 81 0c 08 fc c1 56 60 f6 64 e9 de 27 35 df 9d
11-15 14:48:30.874  1858  1858 I update_engine: [INFO:utils.cc(478)] 0x00000010 : 7d 5f 31 c7 4e 55 33 dd ec 74 4f 03 72 c2 8a 1b
11-15 14:48:30.877  1858  1858 E update_engine: [ERROR:payload_verifier.cc(139)] But found RSA decrypted hashes:  //但是找到的填充前哈希值
11-15 14:48:30.879  1858  1858 I update_engine: [INFO:utils.cc(461)] Logging array of length: 256
11-15 14:48:30.882  1858  1858 I update_engine: [INFO:utils.cc(478)] 0x00000000 : 45 1f 7b 22 37 85 95 e9 4f 7d d1 4d 84 ce c6 73

因此解决方案就是对确认升级底包版本和需要升级的版本的release key是否一致?

4)ErrorCode::kPayloadTimestampError (51)

如上关键日志可以看出来,是在安全补丁的校验,如果OTA包的安全补丁比底包版本的安全补丁还老,触发这个异常,关键日志

update_engine: [ERROR:delta_performer.cc(420)] Target build SPL 2025-06-05 is older than current build's SPL 2025-12-05, this OTA is an SPL downgrade. Your device's ro.boot.verifiedbootstate=green, it probably has a locked bootlaoder. Since a locked bootloader will reject SPL downgrade no matter what, we will reject this OTA.

解决方法:参考如下逻辑,判断了ro.boot.verifiedbootstate状态是否为绿色,如果解锁了的话这个状态就不是绿色的了,因此解锁之后,就可以继续往下调试升级

5)ErrorCode::kUserCanceled (48)

如上关键日志可以看出来,update_engine正常升级,升级过程中并没有什么E异常打印,就突然出现了kUserCanceled (48)的返回码,并停止了update_engine进程。

update_engine_client: [INFO:update_engine_client_android.cc(103)] onPayloadApplicationComplete(ErrorCode::kUserCanceled (48))

最初分析这道题的时候,从日志前后根本没有看出来什么原因,只知道在对system分区进行校验之后就用户取消了,但是我并没有手动去操作取消升级,这搞得我开始怀疑人生,最后走了一遍弯路,结合AI分析,找到了问题原因居然有个进程去主动调用UpdateEngineClient.cancel()函数

问题总结:针对ErrorCode::kUserCanceled (48)错误码,通常属于用户主动去取消update_engine进程,重点可以查找一下有没有去调用UpdateEngineClient.cancel()函数。在这个案例中是客户自定义的升级进程在低内存被干掉重启之后主动去调用如上方法,因此我这里打开这个应用界面提升优先级,就能暂时规避这样的情况。

4、案例之AVB校验安全补丁失败回滚

问题背景:同样还是A14升级到A16,update_engine已经成功,重启之后系统进入recovery模式,再次重启之后回滚为A14的版本。

日志分析:因为update_engine那边的日志是正常的,问题点在于重启之后,为什么会进入recovery模式,进入导致版本回滚,抓取重启之后的串口日志,关键流程如下:

代码分析:如下代码,在bootloader阶段,会进行AVB校验,详细流程可以参考https://blog.csdn.net/qq_27672101/article/details/156126737?spm=1011.2124.3001.6209

解决方案:解决AVB机制的几个重要宏控BOARD_AVB_VBMETA_SYSTEM_ROLLBACK_INDEX

这个宏控的值是从安全补丁获取,因此这道题的原因是校验AB校验VBMETA的安全补丁时间错比老版本小,因此触发回滚。

您可能感兴趣的与本文相关的镜像

Python3.8

Python3.8

Conda
Python

Python 是一种高级、解释型、通用的编程语言,以其简洁易读的语法而闻名,适用于广泛的应用,包括Web开发、数据分析、人工智能和自动化脚本

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

诸神黄昏EX

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值