Android 10.0 AOSP源码编译:https://edu.csdn.net/course/detail/35479
Android 10.0 根文件系统和编译系统:https://edu.csdn.net/course/detail/35480
系统:AOSP Android10.0
设备:Android模拟器
Android源码编译出来的镜像在运行的时候需要被加载, 在实际产品中, 这些镜像会通过特定的烧录方式烧录到emmc或者sd卡, 或者UFS闪存中,不过在emulator模拟中, 所有的镜像都会以文件形式挂载到系统中去,这些镜像其实就是我们平常刷机ROM中出现的文件, 本章节系统详细的介绍各种镜像的意义, 拆包的方法。这样大家在没有源码的情况, 就可以对镜像进行查看和修改,
以下红色部分是Android 10编译完成之后, 在 out/target/product/generic_x86_64/生成的各种镜像文件:
ldswfun@android:/mnt/ext-disk1/a10-aosp$ file out/target/product/generic_x86_64/*.img
out/target/product/generic_x86_64/cache.img:
Linux rev 1.0 ext4 filesystem data, UUID=b39526f5-9f30-4595-89b5-02c27f8bdc47, volume name "cache" (extents) (large files) (huge files)
out/target/product/generic_x86_64/encryptionkey.img: data
out/target/product/generic_x86_64/ramdisk.img: gzip compressed data, from Unix
out/target/product/generic_x86_64/super.img: data
out/target/product/generic_x86_64/system.img:
Linux rev 1.0 ext2 filesystem data, UUID=150757cd-b579-4f77-bda8-e30833daf100 (extents) (large files) (huge files)
out/target/product/generic_x86_64/system-qemu.img:
DOS/MBR boot sector; partition 1 : ID=0xee, start-CHS (0x0,0,2), end-CHS (0x189,6,61), startsector 1, 6313983 sectors, extended partition table (last)
out/target/product/generic_x86_64/userdata.img:
Linux rev 1.0 ext4 filesystem data, UUID=76ae5bcf-1b51-4822-b666-3124b61e4970, volume name "data" (extents) (large files) (huge files)
out/target/product/generic_x86_64/userdata-qemu.img:
Linux rev 1.0 ext4 filesystem data, UUID=57f8f4bc-abf4-655f-bf67-946fc0f9f25b (extents) (large files)
out/target/product/generic_x86_64/vbmeta.img: data
out/target/product/generic_x86_64/vendor.img:
Linux rev 1.0 ext2 filesystem data, UUID=443bb3f2-9b8d-42d1-bca1-38cc4b1294af, volume name "vendor" (extents) (large files) (huge files)
out/target/product/generic_x86_64/vendor-qemu.img:
DOS/MBR boot sector; partition 1 : ID=0xee, start-CHS (0x0,0,2), end-CHS (0xe,233,27), startsector 1, 239615 sectors, extended partition table (last)
以上镜像可以通过下列表格中的命令进行编译生成:
模块 |
make 命令 |
boot.img |
make bootimage |
system.img |
make systemimage 或者make snod(更快的直接生成system.img, 不管out/target/product/xx/system是否有变化) |
ramdisk.img |
make ramdisk |
userdata.img |
make userdataimage |
super.img |
make superimage 或者make supernod(更快的直接生成super.img, 不管文件是否有变化) |
注:以上镜像目标的编译规则定义都在源码:build/core/main.mk中。
在Android设备中,Android系统中的各个镜像都是需要烧录到emmc或者flash 存储ROM中, 而存储ROM是需要进行分区,每个分区存放不同的镜像
常见的分区表:
Boot loader |
misc |
dtbo |
vbmeta |
Kernel (dtb) |
ramdisk |
recovery |
cache |
system |
vendor |
userdata |
system-as-root 分区:
Boot loader |
misc |
dtbo |
vbmeta |
boot |
recovery |
cache |
super |
userdata |
genric_x86_64模拟器使用的镜像:
kernel+ramdisk.img+system+qemu.img(vbmeta+super.img(system+vendor))+cache.img+userdata.img
RK3399的使用镜像:
MiniLoaderAll.bin+parameter.txt+trust.img+uboot.img+misc.img+boot.img+dtbo.img+vbmeta.img+recovery.img+super.img+oem.img
ramdisk为内存文件系统,是一个最小型文件系统, 在内核启动的时候会将其作为根文件系统进行挂载,同时整个镜像是加载到内存中, 以提高android系统启动速度, 文件实际为gzip文件,需要解压, 注意记得备份,以下展示在RK3399编译Anroid10.0源码只有,生成的ramdisk进行拆包。
mkdir rootfs ; cp ramdisk.img rootfs/ ; cd rootfs/ ; mv ramdisk.img ramdisk.img.gz ; gunzip ramdisk.img.gz
此时直接得到ramdisk.img, 但是类型为ASCII cpio archive (SVR4 with no CRC), 可以通过binwalk ramdisk.img查看里面的内容, 也可以通过cpio进行解压
mkdir root ; cd root ; cpio -i -F ../ramdisk.img
最后就得到
打包ramdisk方法:
1 ,回到rootfs目录, 生成cpio 包:
cd root; find . | cpio -o -H newc > ../tmp_ramdisk.img
2 压缩cpio包:
cd ..
gzip -c tmp_ramdisk.img > ramdisk.img
在system-as-root系统中,ramdisk.img 不会作为/目录, 而只是为了兼容升级到10的设备, ramdisk作为虚拟分区来覆盖原来的ramdisk分区。
boot.img包含内Linux内核镜像zImage和根文件系统ramdisk文件。boot.img会放在一个独立分区当中。该镜像一般是通过mkbootimg(out/host/linux-x86/bin/)来打包,镜像基本构成为:头部, 内核, ramdisk镜像,第二阶段的载入程序(可选)。在真机上基本都使用boot.img, 而aosp_x86_64 产品对应的模拟器上中并没有boot.img,是直接使用独立的kernel和ramdisk。
+—————–+
| boot header | 1 page
+—————–+
| kernel | n pages
+—————–+
| ramdisk | m pages
+—————–+
| second stage | o pages
+—————–+
n = (kernel_size + page_size – 1) / page_size
m = (ramdisk_size + page_size – 1) / page_size
o = (second_size + page_size – 1) / page_size
0. all entities are page_size aligned in flash
1. kernel and ramdisk are required (size != 0)
2. second is optional (second_size == 0 -> no sec
拆包方法:拆包需要一个工具unpack_bootimg, 在源码中通过make unpack_bootimg 即可生成,
unpack_bootimg --boot_img boot.img --out boot-out/
boot_magic: ANDROID!
kernel_size: 26406920
kernel load address: 0x10008000
ramdisk size: 803395
ramdisk load address: 0x11000000
second bootloader size: 367616
second bootloader load address: 0x10f00000
kernel tags load address: 0x10000100
page size: 2048
boot image header version: 2
os version and patch level: 335544651
product name:
command line args: console=ttyFIQ0 androidboot.baseband=N/A androidboot.wificountrycode=US androidboot.veritymode=enforcing androidboot.hardware=rk30board androidboot.console=ttyFIQ0 androidboot.verifiedbootstate=orange firmware_class.path=/vendor/etc/firmware init=/init rootwait ro loop.max_part=7 androidboot.selinux=permissive
additional command line args:
recovery dtbo size: 0
recovery dtbo offset: 0x0
boot header size: 1660
dtb size: 0
dtb address: 0x11f00000
boot-out/目录出现如下内容:
因为在 Android10.0上的模拟器并没有使用boot.img, 所以上面截图展示的是rk3399的镜像, 其中second是一个自定义的数据,里面包含内核的启动logo相关资源。
dtb overlay, 叠加 DT。由原始设计制造商 (ODM)/原始设备制造商 (OEM) 提供的设备专用配置。可让主要的设备树 Blob (DTB) 叠加在设备树上。使用 DTO 的引导加载程序可以维护系统芯片 (SoC) DT,并动态叠加针对特定设备的 DT,从而向树中添加节点并对现有树中的属性进行更改.
将dtbo.img中的dtb提取出来,生成
mkdtimg dump dtbo.img -b
dtb文件转换为dts文件:
dtc -I dtb -O dts
system镜像会提供android所需要的命令,内置app,运行动态库,以及系统配置文件, 在system-as-root特性中, system镜像会被直接挂载成根目录下。system镜像在不同Android版本下,以及不同是设备厂商定制,格式会有不同, 大体有如下几种格式:
通过file查看system.img类型
system.img: Linux rev 1.0 ext4 filesystem data, UUID=da594c53-9beb-f85c-85c5-cedf76546f7a, volume name "system" (extents) (large files)
发现镜像直接是ext4格式, 此时可以直接用mount命令挂载
mout -o loop system.img /mnt
在Android10中, file查看system.img类型是如下信息:
system.img: Linux rev 1.0 ext2 filesystem data, UUID=d5afeddb-7a81-4c6c-bb5f-432134648a83 (extents) (large files) (huge files)
那么可以通过如下指令进行挂载,只能以只读的方式挂载:
sudo mount -t ext4 -o ro system.img /mnt
通过file查看system.img类型,镜像前面有一个DOS/MBR引导段, 在Android10模拟器中常用这个格式。
file system-qemu.img
system.img: DOS/MBR boot sector; partition 1 : ID=0xee, start-CHS (0x0,0,2), end-CHS (0x15,86,17), startsector 1, 4198399 sectors, extended partition table (last)
fdisk是操作MBR分区的工具,通过fdisk查看镜像的分区信息, Android10 中的system-qemu.img是如下显示:
fdisk -lu system-qemu.img
Disk system-qemu.img: 3 GiB, 3232759808 bytes, 6313984 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: gpt
Disk identifier: 03F437AC-0563-4205-A497-50E4454EDC28
设备 Start 末尾 扇区 Size 类型
system-qemu.img1 2048 4095 2048 1M Linux filesystem
system-qemu.img2 4096 6311935 6307840 3G Linux filesystem
上面会显示有两个分区,但是分区名字不知道, 此时可以使用sgdisk --print system-qemu.img 查看分区的详细信息:
Disk system-qemu.img: 6313984 sectors, 3.0 GiB
Logical sector size: 512 bytes
Disk identifier (GUID): 03F437AC-0563-4205-A497-50E4454EDC28
Partition table holds up to 128 entries
First usable sector is 34, last usable sector is 6313950
Partitions will be aligned on 2048-sector boundaries
Total free space is 4029 sectors (2.0 MiB)
Number Start (sector) End (sector) Size Code Name
1 2048 4095 1024.0 KiB 8300 vbmeta
2 4096 6311935 3.0 GiB 8300 super
如果要拆包system.img的话, 可以通过以下dd命令进行实现:
拆分vbmeta分区命令:
mkdir split/
name=vbmeta ; start=2048 ; end=4095
dd if=system-qemu.img of=split/${name}.img skip=${start} bs=512 count=$((${end} - ${start} + 1))
拆分super分区命令:
name=super ; start=4096 ; end=6311935
dd if=system-qemu.img of=split/${name}.img skip=${start} bs=512 count=$((${end} - ${start} + 1))
拆分之后得到vbmeta.img和super.img(如果是sparse类型就需要转成成raw类型, 如果不是,就直接用lpunpack解压出来, 文章后面会讲到)
Android9 中的system-qemu.img是如下显示, 只有一个分区:
fdisk -lu system.img
Disk system.img: 2 GiB, 2149580800 bytes, 4198400 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: gpt
Disk identifier: 2FCBC3C1-F125-460D-8F4C-D692F088359D
Device Start End Sectors Size Type
system.img1 2048 4196351 4194304 2G Linux filesystem
查看到sector大小和实际镜像的起始位置,得到一个偏移量: 512 * 2048 = 1048576, 如果偏移量位置的数据就是ext4格式, 那么通过如下命令进行尝试挂载:
sudo mount -o loop,offset=1048576 system.img /mnt
注意:这种挂载, 只限于一个分区的情况。
修改完毕之后直接umount
umount /mnt
在烧录镜像的时候,镜像会被先下载到设备的RAM中, 如果镜像过大,就会占用设备的更多RAM,并且也会增加烧录时间, Android就将各个镜像做成sparse格式, 该格式会将镜像分割成很多chunk(4k的倍数), 在真机RK3399中会使用sparse image, 是一个非常普通的data文件,是通过将raw ext4进行稀疏描述得到的,因此尺寸比较小(没有全零的无效填充区),可以通过以下命令查看systme.img有多少个数据块:
system/core/libsparse/simg_dump.py -v out/target/product/rk3399_roc_pc_plus/system.img
同时也可以通过simg2img工具将sparse image转换成raw ext4, (simg2img源码的位置在system/core/libsparse,编译出来的目标文件在out/host/linux-x86/bin/simg2img)
simg2img system.img system_raw.img
通过file system_raw.img查看得到:
system_raw.img: Linux rev 1.0 ext2 filesystem data, UUID=753c2954-f4f9-4846-afb1-f6eac8e43f24 (extents) (large files)
将system_raw.img直接挂载到一个挂载点上,如mp/挂载点
sudo mount system_raw.img mp/
ls ./mp 查看目录下的文件
包含有厂商私有的可执行程序、库、系统服务和app等。可以将此分区看做是system分区的补充,厂商定制ROM的一些功能都可以放在此分区,odm是贴牌厂商定制镜像, oem是代工厂商定制镜像,
自Android Q(10.0)以后,系统支持动态分区(dynamic partition),它将多个系统只读分区(包括system、product、vendor、odm或者其他厂商自定义分区)合并为一个super分区。物理分区只有super分区的概念,而没有system等分区, 在RK3399上,通过查看super.img的类型,发现也是稀疏文件格式:
super.img: Android sparse image, version: 1.0, Total of 927744 4096-byte output blocks in 2501 input chunks.
可以通过如下命令进行解包:
格式转化成raw:
simg2img ./super.img ./super_raw.img
将super数据中各个段进行拆包(在x86_64产品上编译出来的文件不是sparce image, 直接用lpunpack拆包就可以了):
lpunpack super_raw.img
得到如下:
通过file可以查看各个img的类型,发现都是ext2的,那么就可以直接mount查看了
重新打包:
make_ext4fs -l 2018M -s rootfs.sparse ../target/
用户存储空间。一般新买来的手机此分区几乎是空的,用户安装的app以及用户数据都是存放在此分区中。用户通过系统文件管理器访问到的手机存储(sdcard)即此分区的一部分,是通过fuse或sdcardfs这类用户态文件系统实现的一块特殊存储空间。
验证启动(Verified Boot)是Android 4.4 开始引入的一个新的安全功能,作用是在系统启动时校验分区是否被篡改或者发生过改动,比如用户使用 root 软件强行植入 su 文件,但最后删除了 su, 这种情况也能检测出来。一旦检验不过,系统就不能正常启动,并且有相关的图文提示, 简单描述做法就是在启动过程中增加一条校验链,即 ROM code 校验 BootLoader,确保 BootLoader 的合法性和完整性,BootLoader 则需要校验 boot image,确保 Kernel 启动所需 image 的合法性和完整性,而 Kernel 则负责校验 System 分区和 vendor 分区。
verify boot meta, 主要起检验作用的,存放有各个分区的校验数据以及签名和其他信息, vbmeta分区在哈希描述符中保存boot引导分区的哈希值, 因为vbmeta分区中的VBMeta结构体是加密签名的,所以bootlaoder可以检查签名并验证它是否由key0的所有者制作(例如通过已嵌入的key0公钥),从而信任分区中储存的boot, system,vendor分区的哈希值。bootloader在启动的时候先会校验vbmeta的有效性,vbmeta存放了boot.img的哈希值, 因为boot.img不会太大, 会被加载到内核中,bootlaoder会直接去计算内存中boot.img的哈希值, 与读出来的值进行校验。
内核会利用dm-verify机制对system进行校验,这种机制允许 system 分区在读写的时候进行校验,而不是一次性将整个 system 镜像进行校验。当校验 hash 值不一致的时候,返回 IO 错误,system分区的哈希树紧随在各自的分区文件系统数据之后, system文件较大, 所以存放的是镜像的哈希树值, 通过将system镜像切割成4k大小块,并得到每个块的哈希值, 然后每两个块的哈希值又组合成新的哈希值, 然后再继续两两组合,最后形成一个书, 树的顶端就是root hash, vbmeta中存放的就是该root hash,
可以使用android在带的avbtool对vbmeta.img进行解析:
avbtool info_image --image vbmeta.img > vbmeta.img.info
vbmeta官方文档:
https://source.android.com/security/verifiedboot
recovery分区的镜像,一般用作系统恢复和升级, 在A/B设备中,升级就不放在recovery中了。包含recovery系统的kernel和ramdisk。如果bootloader选择启动recovery模式,则会引导启动此分区的kernel并加载ramdisk,并启动其中的init继而启动recovery程序,至此可以操作recovery模式功能(主要包括OTA升级、双清等)。
boot.img中ramdisk里的init.rc位于system/core/init/init.rc,而recovery.img中ramdisk里的init.rc位于bootable/recovery/etc/init.rc
主要用于缓存系统升级OTA包等。双清就是指对userdata分区和cache分区的清理。在A/B设备中,OTA包就不需要存储在此。
主要用于Android系统和bootloader通信,使Android系统能够重启进入recovery系统并执行相应操作。如升级包下载之后,系统会将向misc分区写入指令,表明下次启动时进入recovery模式并使用该OTA包进行升级。重启后最先进入bootloader,bootloader会先判断按键组合、电源寄存器等,随后会读取misc分区的内容并解析。由于步之前系统往misc分区写入了指令,此处bootloader读取指令后会引导启动recovery系统。
通过介绍Android 10以及其他版本上的镜像,来帮助大家了解理解各种镜像的作用,拆包的方法, 方便大家可以在没有源码的时候,就可以对镜像进行查看和修改。