深入研究Android的img镜像生成
前面有很多起因与废话,想看研究直接点标题跳转哈
参考文章
前言&起因
- 我也算入了官改的圈子,这个圈子几乎没有任何的技术共享,开源,因为很多都涉及到私人利益,但我始终将自己独有的技术与可以分享的技术分开来,我也会分享一些其他作者觉得时自己独有的技术的教程,也遭到了一系列的攻击,但向我要技术的只要不是绝对私人的技术我都都会告诉他
- MIUI 系统 4.27 发布 MIUI12,官改圈的作者们都来适配上了这个最新系统的官改,几乎每个人都遇到了 Mi10 刷了自己官改后相机删退的问题,我所知的一些资深官改作者在 5.1 假期基本都修复了这一问题
- 而这一问题的导致就是我所开发的工具箱生成的官改系统相机也会闪退,那么除非我能忍受我在测试系统的时候就不能使用任何的扫码功能,否则我之后的任何相关功能都无法继续开发
5.1 一个人到成都租房,第一天住酒店,第二天坐公交到 20 公里外的表哥家住,第三天才把房租下来,之后两天也忙个不停,那几天一言难尽。。。🤬
异常表现 🤔
- 在最新小米 10/10pro,k20,一加 8 等设备上都采用了动态分区机制,引用了 super 这一分区,刷机的时候动态设置了 system/vender/odm/product 等分区的大小,节省了空间
- 写这个的起因就是在二次打包小米 10/10pro 这两款机型的时候,system/vendor 分区的一些权限会不正常,表现在于相机闪退,抓 log 显示 getCap()空指针异常,但是官方包刷入后是没有问题的,所以异常的导致肯定不是在相机 app 上,并且 qq/微信等所有的 app 都无法正常打开摄像头。
这部分的资料真的实在太少了,除了参考文章有帮到我,其他的更多的人根本不愿意分享这部分的知识
- 而我作为官改制作工具的开发者,我的工具箱存在的意义就是让喜欢刷机的人可以自己制作官改,这也涉及利益,所以,他们的任何人都不可能告诉我解决方向。
相机报错详情 😑
1 | io.reactivex.exceptions.OnErrorNotImplementedException: Attempt to invoke virtual method 'boolean com.android.camera2.CameraCapabilities.isSupportParallelCameraDevice()' on a null object reference |
错误追踪 😑
通过 logcat 可以看到在设备开机启动相机服务的时候就失败了,如下:
1 | 1636 W CameraService_proxy: Could not notify cameraserver, camera service not available. |
由于任何地方对摄像头的使用都会用到这个服务,但是它启动失败了,所以它会一直尝试启动,手机会发烫,电量消耗加快
定位到这个服务的 rc 文件
/vendor/etc/init/android.hardware.camera.provider@2.4-service_64.rc
1 |
|
找到它的服务可执行文件
/vendor/bin/hw/android.hardware.camera.provider@2.4-service_64
是一个二进制,我们直接执行一下,输出如下:
1 | ardware.camera.provider@2.4-service_64; exit < |
全是错,官方的系统执行这个文件是没有这个错的,我也只能确定是二次打包后才导致的,无法再从程序层定位
尝试从而 ext4 img 信息定位 🧐
fs_config 是从 ext 分区解析出来的包含分区内每一节点的权限/uid/gid 信息,在重新生成镜像时会用到,它的格式如下:
1 | lost+found 0000 0000 00700 |
file_context 是从 ext4 分区解析出来的包含分区内每一节点的 Selinux 信息,生成镜像时会用到,格式如下:
1 | /lost\+found u:object_r:rootfs:s0 |
初步定位
- 1.使用工具箱二次打包官方系统
- 2.分析出工具箱二次处理后 system/vendor 镜像大的 fs_config/file_context/capabilities
- 3.对比官方镜像中的信息(未发现任何差异,这是最头疼的 🤯)
以上的各种确定大致就花了我两天吧,一遍上课一边定位 bug,后来想起问题的关键是在凌晨两三点躺在床上的时候,想起来在 Android Pie(9)的时候有一个错误是 file_context 缺失根目录 Selinux 的问题,也就是它记录了分区下所有节点的信息,就是没有记录自己所挂载到的节点信息
二次定位
system.img 是系统分区,里面有大量的系统软件/framework 层,本文不做详细研究
vendor.img 是底层分区,里面有设备底层驱动
google 为解决系统升级,系统适配带来的大量数据替换,从最初的 a only 到现在的 sar 结构,将系统层与驱动层分离出来(早期是在一起的)
- 查看 system.img 所挂载点的信息,在 sar(system as root)分区结构的系统中,system.img 的挂载点就是/
确认信息与官方源信息无差异 - 查看 vendor.mg 所在挂载点的信息,挂载点为/vendor,发现异常
vendor 挂载点异常如下
官方系统挂载点信息
1 | /vendor 0000 2000 00755 |
也就是 uid=0(root)、gid=2000(shell)
二次打包后 vendor 挂载点信息
1 | /vendor 0000 0000 00755 |
gid 从 2000 到了 0000,所以分区根目录的权限就变成了 root,从而导致没有 root 权限的程序根本访问不到这个分区的根目录
例如相机服务的 services 的 gid 为 2000,他就拿不到底层第驱动的权限
Android 镜像文件的生成
要了解这部分无疑逃不开看大量的帖子和看安卓系统的编译源码
鄙人 3 年前开始自己实现镜像的二次生成,自己写了一套万能的生成方案,覆盖安卓 5-10,最近 MIUI12 出来才 gg
旧的生成方案
也是我一直在用的方案
使用 make_ext4fs 程序生成(不能使用 Android SDK 中 platform-tools 中的这个程序,不能兼容到安卓 9 及以后,需要自行编译 github 第三方开发者针对 pie 后的修复)
make_ext4fs 的作用是将整合好的文件夹生成标准的 ext4 分区 img
make_ext4fs 生成镜像方法
1 | make_ext4fs -L $point -T 1230739200 $simg -s -S file_contexts -C fs_config -l $filesize -a $point volume.img $flodername |
参数解析
- -L:lable,镜像的标签(这个好像具体不影响,我 a/b 分区用的是/,a only 用的是 system)
- -T:分区内所有节点的时间戳,在 MIUI 的系统上一般为 0 或者 1230739200,这两者等效,都是 1970/01/01,这个不一致也会导致系统很多异常 bug
- -s:生成 sparse img(这里不做 simg 与 rimg 的区别分析),取消该开关即生成 rimg
- -S:分区 Selinux 信息
- -l:分区的大小
- -a:分区的挂载点(a/b 分区是/,a only 是 system)
- volume.img:镜像的名字
- $flodername:需要打包成镜像的文件夹
更多参数执行 make_ext4fs -h 查看
Android8.0 后引入的生成方案
可以知道的是 google 在 Android 9.0 的时候便删除了 platform-tools 中的 make_ext4fs 这个文件
我在最初编译一套兼容的打包方案就考虑过使用这方式,当时由于自己能力有限便放弃了,如果你 google make_ext4fs 应该能直接搜到我在别人开源下的 issue
- 需要阅读大量的安卓编译源码(我个人是看了大量的第三方文献)
- 阅读 github 中 aosp-mirror 的 Android 镜像编译代码
最后在 aosp-mirror platform_build 库中的 tools/releasetools 找到了这一相关方案
涉及关键文件
- build_image.py:将整合好的文件夹打包成 ext4,将接收到的参数解析,并调用 mkuserimg_mke2fs 生成 img 镜像,也就是使用新的方案打包,需要一个 system_image_info.txt 作为参数
- mkuserimg_mke2fs.sh:负责根据参数调用 mke2fs、e2fsdroid
- mke2fs:用来生成空白分区
- e2fsdroid:用来将文件夹拷贝到空白分区内,并恢复 fs_config、file_conetxt
以下为动态分区相关,本篇不做研究(自己试过了没有问题)
- build_super_image.py:将已有的 system/vendor/product/odm 镜像生成 super.img 镜像
- lpmake:用来生成 super.img,参数为已经生成好的各个分区
- lpunpack:用来加压 super.img
第一次尝试生成镜像(以生成 system.img 为例)
来源本文顶部的几个参考帖子
使用以下命令
1 | build_image.py ./system ./system_image_info.txt system.img |
system_image_info.txt
1 | ext_mkusering=./mkusering_mke2fs |
结果就是,各种失败
脚本执行后 Linux 系统 ram 瞬间从 1g 多到 7g 多,偶尔直接满(满的情况电脑就死机了),最后提示什么内存超出,输出本人是完全定位不到问题所在,这部分没有注意记录输出
以上我在手机本地生成与在 Linux 上的结果都是一样,各部分第三方的配置方案我都试过了,都不行
第二次尝试生成镜像
读了 build_image.py 后,它会根据接收到的参数,将接收到的参数放到字典,去生成与计算新的参数给 mkeusering_mke2fs.sh
所以我们直接使用 mkeusering_mke2fs.sh 脚本
先看 help 输出
1 | mkuserimg.sh [-s] SRC_DIR OUTPUT_FILE EXT_VARIANT MOUNT_POINT SIZE [-j <journal_size>] |
经过很多次的尝试,总结出命令
1 | mkuserimg.sh -s "\$PATH" "\$OUTPUT" ext4 / \$SIZE -T 0 -L / \$FILE_CONTEXTS |
fs_config 改动
添加 vendor 根挂载点权限
1 | / 0000 2000 00755 |
生成刷机包,刷入小米 10,相机修复,以上所有工具用到的 fs_config 在 linux 设备上如已经保留分区所有节点的权限(就是打包前的文件夹内的节点权限都是正确的),可以直接省略。
生成的坑
正是由于我看了比较多的相关博客,绝大部分的作者加上了-M 0 -j 0 这两个参数,最后经过了我大量的测试,这两个参数有一个就不开机!!!🤪
快捷参考
在修复的后期发现,用于生成 gsi 的开源库 ErfanGSIs 中也有新的打包方案 🤯
结语
也就是说,make_ext4fs 这一工具是无法恢复分区的根权限的,只有新的方案
- 文章有任何的错误感谢各位指出。
- 我不在乎这些圈子对我的攻击,我只需要做好自己的事(曾在酷安被一群人怼,说我偷他们的技术,就因为我指出了它们不对的地方,加上我又会一点逆向 apk,在酷安,粉丝多是惹不起的)
- 工具箱两个月能带给我的收入连一个月生活都维持不了,相比最初,已经添加了大量的付费功能,价格也没做任何上调,而官改巨头们所获得的的收入我应该至少得花上几年吧
- 世上多的是比你过的轻松与比你过得艰难的人,不用太抱怨当下的生活
- 大三开始休学一年,我做不好一边拼命学其他的技术还能念好书,这最终导致的是我挂的科越来越多,我还有太多都没有开始做的事,我最喜欢的,从来都不是计算机,身体现在也有比较明显的…一言难尽 😑
深入研究Android的img镜像生成