28、常见问题与排障:升级卡在logo、空间不足、签名错误、分区表损坏

做OTA升级开发,说白了就是跟各种意外打交道。你永远不知道用户手里的机器,在升级那一刻会出什么幺蛾子。我这些年踩过的坑,十个手指头都数不过来。今天就把最常见的四类问题——卡logo、空间不足、签名校验失败、分区表损坏——掰开揉碎了讲清楚。

核心观点:OTA升级的排障,80%的问题都可以通过升级前的校验和升级中的日志提前发现。剩下的20%,靠的是对底层机制的深刻理解。

OTA升级常见问题排障体系 卡在Logo boot.img损坏 空间不足 system分区满 签名错误 公钥不匹配 分区表损坏 GPT/LBA错误 查看last_log 检查bootargs df -h 检查 预留空间策略 verifyPackage 公钥指纹比对 sgdisk修复 备份GPT表 解决方案与预防措施 日志分析 → 根因定位 → 修复/回滚

一、升级卡在Logo:最让人头疼的「黑屏白字」

升级完成后重启,手机一直停在品牌Logo那里不动了。嗯,这种情况我遇到过不下十次。每次看到测试同事发来这个截图,心里就咯噔一下。

为什么会这样?说白了,就是内核没起来,或者内核起来了但init进程挂了。最常见的原因有三个:

  • boot.img刷写失败——升级过程中断电、USB断开,或者flash坏块导致内核镜像不完整。
  • dtb/设备树不匹配——新内核引用了旧设备树里没有的硬件节点,直接panic。
  • init.rc语法错误——我有个同事改init.rc时少写了一个空格,结果所有服务都启动不了。

我的排查习惯:先抓串口日志。如果设备有USB串口,在卡logo时插上电脑,看内核最后打印了什么。如果没有串口,就进recovery模式,查看/cache/recovery/last_log。这个文件记录了升级全过程,包括刷写每个分区时的返回值。

举个例子,有一次我排查一个卡logo问题,last_log里显示:

Writing boot partition...
I:Update: write returned 0
I:Update: flash done

看起来刷写成功了,但重启就是卡住。后来我对比了新旧boot.img的md5,发现一模一样。问题出在哪?我仔细看了日志,发现write returned 0——0表示成功,但实际写入的字节数比预期少了4KB。这是底层驱动的一个bug,在特定坏块上会静默截断数据。

避坑指南:我曾经因为信任write()的返回值而浪费了两天时间。现在我的代码里,刷写分区后一定会做一次回读校验——把刚写进去的数据读出来,跟原始数据逐字节比对。虽然慢一点,但安心。

二、空间不足:升级包解压时「撑爆」了分区

这个问题在低端机上特别常见。用户手机用了两三年,system分区剩余空间不到100MB,而你的升级包有800MB。解压到一半,系统提示No space left on device,升级直接中断。

核心原因:OTA升级包是压缩的,解压后体积会膨胀。比如一个500MB的zip包,解压出来可能有1.2GB。如果/data/cache分区不够大,就会失败。

我建议在升级前做两件事:

  1. 计算所需空间——用unzip -l查看包内所有文件的总大小,再加上20%的余量。
  2. 检查当前可用空间——在recovery里执行df -h /datadf -h /cache

下面是我常用的一个脚本片段:

# 在升级前检查空间
required_size=$(unzip -l update.zip | tail -1 | awk '{print $1}')
required_size=$((required_size * 120 / 100))  # 加20%余量
available_size=$(df /data | tail -1 | awk '{print $4}')

if [ $available_size -lt $required_size ]; then
    echo "空间不足,需要至少 $required_size 字节"
    exit 1
fi

一个容易被忽略的点:升级过程中,/cache分区也会被用来存放临时文件。我见过一个案例,/cache只有50MB,但升级包解压时需要80MB的临时空间,结果卡在50%进度。解决方案是在updater-script里显式指定临时目录到/data分区。

三、签名错误:公钥对不上,系统拒绝升级

「签名验证失败」——这个错误提示在recovery日志里很刺眼。说白了,就是系统认为升级包不是官方签发的,拒绝执行。

为什么会签名错误?常见场景有:

  • 你用自己的测试密钥签了包,但设备里烧录的是官方公钥。
  • 升级包在传输过程中被篡改(比如中间人攻击)。
  • 设备里的公钥分区(/system/etc/security/otacerts.zip)被误删或损坏。

我记得有一次,客户反馈说他们的设备无法接收OTA推送。我远程一看,recovery日志里写着:

E:failed to verify whole-file signature
E:signature verification failed

我检查了升级包的签名,没问题。后来发现,是客户在刷机时刷了一个第三方ROM,那个ROM里替换了otacerts.zip。嗯,这种问题最坑——你永远不知道用户对手机做了什么。

我的建议:RecoverySystem.verifyPackage()方法里,除了校验签名本身,还要打印出公钥的指纹(SHA256)。这样当签名失败时,你可以对比设备里的公钥指纹和服务器上的公钥指纹,快速定位是包的问题还是设备的问题。

代码示例:

// 在RecoverySystem.java中增加公钥指纹打印
public static boolean verifyPackage(File packageFile, ProgressListener listener) {
    // ... 原有校验逻辑 ...
    PublicKey key = getPublicKeyFromCerts();
    String fingerprint = getFingerprint(key);
    Log.i("OTA", "公钥指纹: " + fingerprint);
    // ... 继续校验 ...
}

四、分区表损坏:最严重的「砖机」风险

分区表损坏,说白了就是设备不知道「哪块区域是system,哪块是data」了。这种情况通常发生在:

  • 升级过程中意外断电,GPT(GUID Partition Table)写入了一半。
  • 刷写了错误的分区镜像,覆盖了分区表区域。
  • eMMC芯片出现物理坏块,恰好落在分区表所在位置。

怎么判断是分区表问题?如果你在recovery里执行ls /dev/block/by-name/,发现少了很多分区,或者分区名变成了乱码,那基本就是分区表坏了。

我经历过最严重的一次,是量产阶段有0.5%的设备升级后变砖。排查了三天,最后发现是升级脚本里有一个dd if=boot.img of=/dev/block/mmcblk0p5,但mmcblk0p5在某个批次的主板上对应的是partition_table分区,而不是boot分区。硬件工程师改了分区映射,但没通知我们。

避坑指南:千万不要在升级脚本里用/dev/block/mmcblk0pX这种硬编码!一定要用/dev/block/by-name/boot这种分区名。我曾经因为这个教训,在代码里加了一个检查:如果by-name目录不存在,直接中止升级。

如果分区表真的坏了,修复方法有两种:

方法 适用场景 操作
fastboot刷写 bootloader还能启动 fastboot flash partition gpt.bin
sgdisk修复 recovery模式可用 sgdisk -e /dev/block/mmcblk0

我个人更推荐第二种。sgdisk可以尝试从备份的GPT表恢复,成功率很高。但前提是——你得有备份。所以我在每次OTA升级前,都会在/data分区备份一份当前的分区表:

# 升级前备份分区表
sgdisk -b /data/backup/gpt_backup.bin /dev/block/mmcblk0

最后说一句:排障这件事,七分靠经验,三分靠工具。但经验是怎么来的?就是一次次踩坑踩出来的。你想想看,我今天讲的这四个问题,每一个背后都有真实的产品事故。把这些案例记在心里,下次遇到类似问题,你就能少走弯路。


公众号:蓝海资料掘金营,微信deep3321