28、常见问题与排障:升级卡在logo、空间不足、签名错误、分区表损坏
做OTA升级开发,说白了就是跟各种意外打交道。你永远不知道用户手里的机器,在升级那一刻会出什么幺蛾子。我这些年踩过的坑,十个手指头都数不过来。今天就把最常见的四类问题——卡logo、空间不足、签名校验失败、分区表损坏——掰开揉碎了讲清楚。
核心观点:OTA升级的排障,80%的问题都可以通过升级前的校验和升级中的日志提前发现。剩下的20%,靠的是对底层机制的深刻理解。
一、升级卡在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分区不够大,就会失败。
我建议在升级前做两件事:
- 计算所需空间——用
unzip -l查看包内所有文件的总大小,再加上20%的余量。 - 检查当前可用空间——在recovery里执行
df -h /data和df -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