曾经遇到过这样的一个问题:有一位同事误操作把windows下编辑过的一个文件覆盖了ubuntu下android 源码下的同样的文件,造成某个apk在运行的时候出错。由于不同版本更新做了好多的改动,调查了许久查不出缘由,查看repo-git历史修改信息也没看出任何相关的改动。终于在万分无助之际相处了一个办法:把旧机器上的system.img 跟boot.img 抽出来,烧录到新的板子上,来缩小问题原因的范畴。终于一步一步地定位到原因,最终把问题解决。
这是一个真实得经历,抽离系统分区镜像解决问题。于是在日后的工作中加深研究:怎样使用这种方法来修改系统镜像。实际上,市面上一下平板方案商的客制化软件修改image的原理也是这样来的。掌握这种方法,在工作中有时候可以给予我们很大的帮助。
首先是把img从系统分区里面抽离出来:(以boot.img为例)
dd if=/dev/block/mmcblk0p1 of=/data/boot.img
之所以是partition1 ,是因为在烧录flash镜像的时候把boot.img烧录到了partition1,mmcblk0p1 是分区的名字,这个名字是在android 的devices下定义的,详细情况可以具体了解。实际上我们会发现,dd 出来的boot.img 比系统编译出来的boot.img 大小要大。这是因为我们dd的时候把整个partition1 都dd出来了,不单单是boot.img本身。
接下来我们把抽离出来的boot.img 放到ubuntu下面去解压。使用split_bootimg.pl脚本:
./split_bootimg.pl boot.img
于是我们解压出来了三个文件:
boot.img-dt.img
boot.img-kernel
boot.img-ramdisk.gz
1、 boot.img-dt.img 这是linux kernel 3.x以后加入的dts后多出来的文件,也是android4.x之后,在boot.img里面所具备的文件。在这里,这个文件是由split_bootimg.pl 脚本生成的。可以不用太在意。
2、 boot.img-kernel,kernel的镜像文件,不是我们关注的重点。
3、 boot.img-ramdisk.gz,这是我们需要修改的文件,ramdisk 。顾名思义,内存当作硬盘使用。实际上,android为了加快对一些只读文件的访问速度,于是加入了ramdisk。每次机器启动后,cpu就会把ramdisk.img的内容加载到内存当中,这也是问什么我们在android下修改init.rc 等文件是不能够生效的。重启之后又恢复原样。
在解压的过程中,我们会发现,把内核的command line打印出来的,下文会详细说到,command line在这里怎样使用。其中command line 是在mk文件里面定义的。或者是由uboot传参进来。
接下来我们要把ramdisk解压出来,使用命令:
./split_bootimg.pl boot.img
解压完毕后,我们可以使用ls命令查看,原理ramdisk里面的内容跟我们实际在android启动后,串口命令行ls命令看到的内容十分相像。事实上,android启动后,根目录下的内容很多都是由加载ramdisk得到的。说到这里,我们也会清楚,问什么称之为ramdisk了。
做到这一步,我们就可以安装我们所想的去修改里面的内容。如,我把init.rc 里面默认设置的 setpropservice.adb.tcp.port 5555 改为 setpropservice.adb.tcp.port 6666.
修改完毕后,压缩ramdisk,使用命令:
./mkbootfs ./ramdisk | gzip >ramdisk-new.gz
mkbootfs是android源码包编译的时候生成的制作工具,路径是:out/host/linux-x86/bin/
压缩完ramdisk-new.gz后,我们就可以重新压缩boot.img ,实际上,我们压缩boot.img 的过程跟android源码编译时压缩boot.img 的过程无异。在这里,我们可以参考android源码是怎样做的。
看到build/core/tasks/factory_ramdisk.mk 里面:
87 $(INSTALLED_FACTORY_RAMDISK_TARGET) : $(MKBOOTIMG) $(TARGET_RAMDISK_KERNEL) $(INSTALLED_FACTORY_RAMDISK_FS)
88 $(call pretty,"Target factory ram disk img format: $@")
89 $(MKBOOTIMG) --kernel $(TARGET_RAMDISK_KERNEL) --ramdisk $(INSTALLED_FACTORY_RAMDISK_FS) \
90 --base $(BOARD_KERNEL_BASE) $(BOARD_MKBOOTIMG_ARGS) $(RAMDISK_CMDLINE) --output $@
语法上不解释。首先我们关注几个变量:
1、 BOARD_KERNEL_BASE
这是kernel镜像的基地址,机器启动后,cpu会到这个地方需找kernel的img文件,如果这个地址错了,那么机器也就启动不了了。于是我们查找是否有BOARD_KERNEL_BASE的定义,在device下的mk文件:
BOARD_KERNEL_BASE := 0x10800000
于是在 –base参数后面,我们跟着基地址0x10800000 ,具体平台有所差异。
2、 RAMDISK_CMDLINE
我们还是看到build/core/tasks/factory_ramdisk.mk文件里面,对RAMDISK_CMDLINE的引用:
82 ifneq (,$(BOARD_KERNEL_CMDLINE_FACTORY_BOOT))
83 RAMDISK_CMDLINE := --cmdline "$(BOARD_KERNEL_CMDLINE_FACTORY_BOOT)"
84 else
85 RAMDISK_CMDLINE :=
86 endif
而BOARD_KERNEL_CMDLINE_FACTORY_BOOT 没有被定义,所以RAMDISK_CMDLINE为空,可以不用理会。
3、 BOARD_MKBOOTIMG_ARGS
此字符串也没被定义,所以不需要理会。
接下来我们使用mkbootimg来压缩,同样的,此工具也是android源码包编译的时候生成的压缩boot.img 的工具,路径是:out/host/linux-x86/bin/
可以直接执行这个工具来查看其用法,./ mkbootimg ,会打印出用法:
error: no output filename specified
usage: mkbootimg
--kernel <filename>
--ramdisk <filename>
[ --second <2ndbootloader-filename> ]
[ --cmdline <kernel-commandline> ]
[ --board <boardname> ]
[ --base <address> ]
[ --pagesize <pagesize> ]
-o|--output <filename>
接下来,我们的思路就很清晰了,执行命令:
./mkbootimg --base 0x10800000 --kernelboot.img-kernel --ramdisk ramdisk-new.gz -o newboot.img
生成的newboot.img 就是我们修改过后生成的新的boot.img 。
把newboot.img 拷贝到u盘,插上u盘启动机器。串口命令行使用命令:
dd if=/mnt/udisk/boot.img of=/dev/block/mmcblk0p1
然后系统更新完毕。重启,在串口命令行输入:
getprop service.adb.tcp.port
就可以看到,默认端口被改为了 6666 。