ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

安卓系统镜像解包打包实战:img2simg、make_ext4fs 与 Windows 避坑指南

安卓系统镜像解包打包实战:img2simg、make_ext4fs 与 Windows 避坑指南

简介:这份已编译好的安卓系统镜像处理工具集,面向Android开发者、ROM制作人及设备调试人员,用于解决系统分区镜像的解包、打包与格式转换问题。包内共4个文件,包含make_ext4fs、simg2img、img2simg三个可执行程序及一个mkuserimg.sh脚本,压缩包约136KB,体积轻便、开箱即用。其中img2simg负责将完整img转为节省空间的稀疏simg,simg2img则反向还原为标准镜像,make_ext4fs用于生成ext4文件系统镜像,mkuserimg.sh脚本可将内核、根文件系统等组件整合为完整用户镜像。借助这套工具,读者能够快速完成分区镜像的格式互转与文件系统构建,理解Android系统内部结构,为定制ROM、调试分区问题提供可复用的命令行方案。目前已有2944人学习下载,适合具备基本命令行操作经验、需要处理系统级镜像的中高级开发者参考使用。

1. 安卓系统镜像解包打包:为什么你编译好的工具集在 Windows 上跑不起来

手里拿到一个已经编译好的安卓系统解包打包工具集,里面躺着 img2simg、make_ext4fs、mkuserimg.sh、simg2img 这几个可执行文件,第一反应往往是直接双击或者拖到命令行里跑。结果大概率是闪退、报错、或者干脆没反应。这不是工具坏了,而是这套东西的出身决定的——它们几乎都是在 Linux 环境下用 NDK 交叉编译出来的 ELF 可执行文件,原生属于 Android 的构建体系,不是给 Windows 双击用的。

这套工具集解决的核心问题很具体:你手上有一个 system.img、vendor.img 或者 userdata.img,想把它解开看里面有什么,改完之后再原样打包回去,让设备能正常启动。img2simg 和 simg2img 负责 raw 镜像和 sparse 镜像之间的互转,make_ext4fs 和 mkuserimg.sh 负责把目录树重新做成 ext4 文件系统镜像。做安卓 ROM 定制、车机系统改造、机顶盒固件替换、甚至 Kindle 这类设备刷安卓的人,绕不开这几个命令。下面按「先搞懂镜像格式 → 再跑通转换 → 再处理打包 → 最后避坑」的顺序,把每一步的命令、参数和失败排查讲清楚。

2. 先分清 raw 与 sparse:img2simg 和 simg2img 到底在转什么

2.1 两种镜像格式的物理差异

安卓系统镜像在磁盘上以两种形态存在。raw 格式就是最朴素的逐字节镜像,文件多大,里面就有多少有效数据,dd 出来的就是这种。sparse 格式是安卓为了节省刷机传输量引入的稀疏表示,它把镜像切成若干 chunk,只记录有数据的块,空洞部分用元数据描述,所以同样内容的 sparse 镜像体积可能只有 raw 的几分之一。

simg2img 做的是 sparse → raw,把稀疏镜像还原成完整镜像,方便挂载和编辑。img2simg 做的是 raw → sparse,把编辑好的完整镜像压回稀疏格式,方便刷入和分发。这两个命令是整套工具里最不容易翻车的,但前提是你得先确认手上镜像的真实格式,而不是靠文件名猜。

判断格式最直接的办法是看文件头。sparse 镜像的 magic 是 0xED26FF3A,raw ext4 镜像在偏移 0x438 处有 0xEF53 的 magic。用一条命令就能确认:

# 查看文件头,判断是 sparse 还是 raw ext4 xxd -l 16 system.img # sparse 镜像开头会看到 3a ff 26 ed(小端序) # raw ext4 镜像开头通常是全零,magic 在 0x438 处 xxd -s 0x438 -l 2 system.img # 输出 53 ef 说明是 ext4

逻辑说明:xxd 的 -l 参数限制读取长度,-s 参数指定偏移。sparse 的 magic 以小端序存储,所以文件里看到的是 3a ff 26 ed。ext4 的 superblock magic 固定在 1024+56 字节处,也就是 0x438。参数上不需要额外配置,这两条命令只是读文件头,不会修改任何东西。

2.2 用 simg2img 把 sparse 还原成可挂载镜像

确认是 sparse 之后,还原命令很直接:

# sparse 转 raw,输出到新文件 simg2img system.img system_raw.img # 查看转换结果大小 ls -lh system.img system_raw.img

逻辑说明:simg2img 第一个参数是输入,第二个是输出。转换过程是纯数据展开,不涉及文件系统解析,所以速度很快,一个 2GB 的 sparse 镜像通常几十秒内完成。参数方面没有需要调的,但要注意输出路径的磁盘空间必须大于 raw 镜像的完整大小,否则会写到一半失败。

转换完成后,raw 镜像可以直接用 mount 挂载(Linux 下)或者用 7z、ext4 读取工具打开。挂载命令:

# Linux 下挂载 raw ext4 镜像到目录 sudo mkdir -p /mnt/system sudo mount -o loop system_raw.img /mnt/system # 用完卸载 sudo umount /mnt/system

逻辑说明:-o loop 让内核把普通文件当块设备处理。挂载后就能像普通目录一样浏览、修改、复制文件。注意挂载期间不要直接对镜像文件做转换操作,先 umount 再处理。

2.3 img2simg 回压时的块大小选择

编辑完 raw 镜像后,用 img2simg 压回 sparse:

# raw 转 sparse,指定块大小 4096 img2simg system_raw.img system_new.img 4096

逻辑说明:第三个参数是块大小,单位字节。常见取值是 4096,和 ext4 的块大小保持一致。如果省略这个参数,工具会用默认值,但不同编译版本默认值可能不同,显式指定更稳妥。块大小选错不会导致镜像损坏,但会影响 sparse 压缩率和刷机工具的兼容性。我一般固定用 4096,和绝大多数安卓设备的 flash 页大小对齐。

转换完成后,用前面的 xxd 命令再确认一次文件头,看到 3a ff 26 ed 就说明 sparse 格式正确。这一步是后悔药——如果压回去的格式不对,刷机时设备会直接报错,到时候再回头查就麻烦了。

3. make_ext4fs 与 mkuserimg.sh:从目录树到可刷入镜像

3.1 make_ext4fs 的核心参数逐个拆

make_ext4fs 是把一个目录打包成 ext4 镜像的底层工具。它的参数比前两个命令多,也更容易设错。一个典型的打包命令:

# 把 system_dir 目录打包成 ext4 镜像 make_ext4fs -s -l 2147483648 -a system -S file_contexts \ system_new.img system_dir

逻辑说明:-s 表示输出 sparse 格式,不加这个参数输出 raw。-l 指定镜像的逻辑大小,单位字节,这里 2147483648 是 2GB。-a 指定挂载点名称,安卓系统镜像通常写 system、vendor、userdata 等,这个值会写进 ext4 的 superblock,影响挂载行为。-S 指定 SELinux 的 file_contexts 文件,安卓 5.0 以后必须带,否则刷入后 SELinux 拒绝访问,系统起不来。最后两个位置参数分别是输出镜像和输入目录。

参数里最容易翻车的是 -l。这个值必须大于目录实际内容大小,同时要和设备分区表里的分区大小匹配。设小了打包失败,设大了刷入后分区尾部有空洞,虽然能启动但浪费空间。常见做法是先 du 一下目录大小,再往上取整到分区实际大小。

# 查看目录实际占用 du -sh system_dir # 查看目录内文件总字节数(更精确) find system_dir -type f -printf '%s\n' | awk '{s+=$1} END {print s}'

逻辑说明:du 算的是磁盘占用,包含块对齐的浪费;find 加 awk 算的是文件实际字节总和。打包时用后者更准,再留 5% 到 10% 余量作为 -l 的值。

3.2 mkuserimg.sh 封装了什么

mkuserimg.sh 是一个 shell 封装脚本,内部调用 make_ext4fs,把常用参数组合好了。典型用法:

# 用 mkuserimg.sh 打包 system 镜像 mkuserimg.sh -s system_dir system_new.img ext4 system 2147483648

逻辑说明:-s 同样表示 sparse 输出。后面依次是输入目录、输出镜像、文件系统类型、挂载点名称、镜像大小。这个脚本的好处是参数顺序固定,不容易记混。但它对 file_contexts 的处理依赖环境变量或者同目录下的配置文件,如果打包后 SELinux 报错,优先检查脚本有没有正确传入 -S 参数。

我一般会先看 mkuserimg.sh 的内容,确认它调用的 make_ext4fs 路径和参数,再决定直接用脚本还是绕过它自己调 make_ext4fs。已经编译好的工具集里,脚本里的路径可能是编译时的绝对路径,换机器后需要改。

# 查看脚本内容,确认内部调用路径 cat mkuserimg.sh # 如果里面有硬编码路径,改成当前目录 sed -i 's|/old/path/to/make_ext4fs|./make_ext4fs|g' mkuserimg.sh

逻辑说明:sed 的 -i 表示原地修改,s 命令做替换。改之前先备份一份,避免改坏。这一步在换机器或者换目录后几乎必做,否则脚本会报「command not found」。

3.3 打包后的验证步骤

打包完成不等于能用。刷入前至少做两件事:确认镜像格式和大小,确认文件系统能正常解析。

# 确认输出是 sparse 格式 xxd -l 4 system_new.img # 应输出 3a ff 26 ed # 用 simg2img 还原后挂载验证 simg2img system_new.img /tmp/verify.img sudo mount -o loop /tmp/verify.img /mnt/verify ls /mnt/verify sudo umount /mnt/verify

逻辑说明:先确认格式,再还原挂载,能列出目录内容说明文件系统结构完整。这一步花不了几分钟,但能挡住大部分「刷进去开不了机」的问题。验证用的临时文件记得删掉,避免占磁盘。

4. 避坑与排查:这套工具集最容易翻车的五个地方

4.1 现象:命令执行报「cannot execute binary file」

原因:在 Windows 的 cmd 或 PowerShell 里直接运行了 Linux ELF 可执行文件。这套工具集是交叉编译产物,不是 Windows PE 格式。

解决:在 Linux 环境里跑,或者用 WSL。如果必须在 Windows 下操作,把工具集和镜像放到 WSL 的文件系统里,不要在 /mnt/c 下直接执行,跨文件系统执行会报权限或格式错误。WSL 里先chmod +x img2simg simg2img make_ext4fs mkuserimg.sh给执行权限。

4.2 现象:simg2img 转换到一半报「No space left on device」

原因:输出目录所在分区剩余空间小于 raw 镜像的完整大小。sparse 镜像可能只有 800MB,但还原后是 2GB,空间不够就写不下去。

解决:先df -h看目标分区剩余空间,换到大分区或者外接存储。转换前用du -h估算 raw 大小,sparse 镜像的 raw 大小可以用simg2img -i或者直接看镜像头里的 total_blks 字段乘以块大小。

4.3 现象:make_ext4fs 打包成功,刷入后设备卡在开机动画

原因:-S 参数指定的 file_contexts 和目录内文件的实际 SELinux 上下文不匹配,或者根本没传 -S。安卓 5.0 以后 SELinux 是 enforcing 模式,上下文不对会拒绝访问关键文件。

解决:确认 file_contexts 文件来自当前系统版本,不要混用不同安卓版本的上下文文件。打包前用ls -Z检查目录内文件的 SELinux 标签,和 file_contexts 里的规则对照。如果只是临时测试,可以在刷入后通过内核参数临时设 permissive,但正式包必须修上下文。

4.4 现象:img2simg 压回的镜像刷入时报「sparse image format error」

原因:块大小和刷机工具预期不一致,或者输入 raw 镜像本身不是完整的 ext4 结构(比如挂载状态下直接拷贝出来的)。

解决:固定用 4096 块大小。raw 镜像必须是从 umount 后的文件或者 simg2img 还原出来的完整镜像,不要在挂载状态下用 dd 抓取。刷机工具对 sparse 头的校验比较严格,块大小、chunk 数量、总块数任何一个对不上都会拒绝。

4.5 现象:mkuserimg.sh 执行报「make_ext4fs: not found」

原因:脚本内部调用 make_ext4fs 时用的是绝对路径或者依赖 PATH 环境变量,换目录后找不到。

解决:cat mkuserimg.sh看内部调用路径,改成相对路径或者把工具集目录加入 PATH。临时办法是export PATH=$PATH:$(pwd),长期办法是改脚本里的路径。改完用bash -x mkuserimg.sh ...跟踪执行,确认每一步调用的命令都存在。

5. 进阶技巧:用循环挂载和差异对比做镜像级调试

前面讲的都是单次转换和打包。实际做 ROM 定制时,更高效的方式是把镜像当文件系统来调试,而不是每次改一点就重新打包刷机。这里分享两个我常用的技巧。

第一个是循环挂载加 overlay。把 raw 镜像挂载到只读目录,上面叠一层可写 overlay,所有修改落在 overlay 目录里,原镜像不动。调试满意后再把 overlay 里的改动同步回目录树重新打包。

# 准备工作目录 mkdir -p /tmp/rom/{lower,upper,work,merged} # 挂载 raw 镜像为只读下层 sudo mount -o loop,ro system_raw.img /tmp/rom/lower # 叠加 overlay sudo mount -t overlay overlay \ -o lowerdir=/tmp/rom/lower,upperdir=/tmp/rom/upper,workdir=/tmp/rom/work \ /tmp/rom/merged # 现在 /tmp/rom/merged 里可以随意改,改动记录在 upper ls /tmp/rom/merged

逻辑说明:overlay 的 lowerdir 是只读层,upperdir 记录所有写操作,workdir 是 overlay 内部用的临时目录,merged 是最终看到的合并视图。这样调试时不用反复解包打包,改完直接看效果。参数上 workdir 必须和 upperdir 在同一个文件系统,否则 mount 会报错。

第二个是差异对比。改完目录树后,用 diff 或者 rsync 的 dry-run 模式对比原始目录和修改后目录,确认只改了预期文件,没有误删或误加。

# 对比两个目录的差异,只列出文件名 diff -rq original_dir modified_dir # 用 rsync dry-run 看会同步哪些文件 rsync -avn --delete original_dir/ modified_dir/

逻辑说明:diff 的 -r 递归,-q 只报差异不显示内容。rsync 的 -n 是 dry-run,不实际传输,只列出会变动的文件。打包前跑一遍,能发现意外改动。我一般会把 diff 结果存成日志,和打包命令一起归档,出问题时能回溯。

最后说一个习惯:每次打包前,先确认工具集里四个可执行文件的权限和格式,再确认镜像的格式和大小,最后才执行打包。这三步检查花不到一分钟,但能挡住九成以上的翻车。这套工具集本身不复杂,复杂的是镜像格式、SELinux 上下文和分区大小这些周边约束。把周边搞对了,命令就是敲几下的事。希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进