Android Kernel 编译与调试指北
上一篇文章介绍了在wsl2环境下编译AOSP并将其运行到Cuttlefish中,本篇文章依赖于上文Cuttlefish,请按照顺序食用本指北
环境
本指北基于以下代码和环境编写
OS : Ubuntu 22.04.2 LTS
AOSP : master
kernel : 根据编译目标决定
target : aosp_cf_x86_64_phone-userdebug
设备 : Cuttlefish
在前一篇文章说过,因为工具链的原因,AOSP的代码不宜太旧,自上一篇文章以来Cuttlefish的功能和文档逐步健全,足以见得google对其的投入程度,所以如果版本不一样会遇见无此参数等这样那样的问题,本文在master上测试通过。
Android Kernel Repo的源码下载
Linux Kernel是Android系统运行的基础,而Linux Kernel的源码在AOSP中并不存在,通常存在的是预构建的内核映像,如果想对内核做一些定制化的修改,就需要下载代码并构建,Linux Kernel像AOSP有各种各样的分支,并不是随便选择一个分支构建就可以正常运行。编译AOSP对应的Linux Kernel版本才能避免构建过程走很多弯路。每一个AOSP构建目标都预置了预编译的内核映像,可以从内核影像中获取相应版本的蛛丝马迹。
需要说明的是Android的内核项目同样是由repo(android.googlesource.com/kernel/mani…) 管理的,其中Linux Kernel的源码存在于kernel/common(android.googlesource.com/kernel/comm…) 目录下,其他目录是与构建相关的工具链或者脚本等,在之前旧版本Linux Kernel构建中可以直接下载kernel/common的代码使用make直接编译出内核镜像,但是随着Android GKI的推出,这套方法就行不通了。读者最好使用repo提供的编译脚本等进行构建。
下面以
aosp_cf_x86_64_phone-userdebug
target为例,讲述如何一步步找到对应的分支
查找Linux Kernel的version和commitId
从Android设置界面查找
如果你编译的系统已经成功运行到虚拟机,你可以打开Settings - About Phone - Android Version -Kernel Version 可以看到对应的Kernel信息,Linux的版本号按照major.minor.patch-build.desc的格式,通过匹配屏幕输出可以得出内核版本为6.1,从附加描述中提取g开头的连续字符可以得知对应的commitId为963667856ef1
从AOSP树中查找
如果只有一个构建目标(aosp_cf_x86_64_phone-userdebug)并没有运行成功虚拟机,可以遵循以下步骤获取。
- 索引到device/google/cuttlefish目录,device目录下存放了芯片和硬件厂商的相关产品配置,其中cuttlefish作为一款虚拟器,也被添加到了该目录下。
- 通过
mgrep ":kernel"
查看配置文件(该方法不是很通用,可以通过“添加新设备”了解相关知识),最后查看搜索到的配置文件,通过下图可以看到该目标链接的kernel映像文件位于kernel/prebuilts/6.1/x86_64/kernel-6.1
- 对该文件执行
file kernel-6.1
得到以下输出,同样可以得到commmitId为963667856ef1
kernel-6.1: Linux kernel x86 boot executable bzImage, version 6.1.25-android14-7-00377-g963667856ef1-ab10271074 (build-user@build-host) #1 SMP PREEMPT Tue Jun 6 23:03:20 UTC 2023, RO-rootFS, swap_dev 0X10, Normal VGA
根据分支拉取Kernel代码
通过上面的操作得到了Linux Kernel的版本和commitId,准备就绪就可以着手拉取代码了,需要注意上文获取的commitId是指android.googlesource.com/kernel/comm… 仓库的相应提交。
根据kernel/common的commitId找到对应repo分支
直接访问android.googlesource.com/kernel/comm… (注意按照实际输出更改commitId) 就可以得到对应的changeId,通过点击changeId链接就可以看到对应gerrit 地址,如下所示,该页面同时标注了Linux kernel的分支[android14-6.1], 这里的branch还是Linux Kernel的分支,那么如何得到repo的分支内,只有猜了,通过在
android.googlesource.com/kernel/mani… 中搜索6.1找到了多个结果,通过依次查看该分支下的default.xml文件,发现common-android14-6.1分支下指向了Linux Kernel的android14-6.1分支,代码如下<project path="common" name="kernel/common" revision="android14-6.1" />
拉取Kernel代码
使用从上文得到的kernel的repo分支,使用repo下载Linux Kernel的源码和脚本等
mkdir android-kernel && cd android-kernel
repo init -u https://android.googlesource.com/kernel/manifest -b common-android14-6.1
repo sync
如果一切OK,代码就下载好了,如果你遇见了任何网络问题,可以参照上一篇文章设置镜像源下载。
Bazel编译Kernel
Android11引入了GKI的特性,用于将内核拆分为由 Google 维护的内核映像和由供应商维护的模块,两个模块分别构建。Android13开始使用Bazel进行编译,由于构建的分支是master分支,所以内核的编译需要使用bazel进行构建,并且需要分别构建两个内核。
//通用内核镜像的构建
$ tools/bazel run //common:kernel_x86_64_dist -- --dist_dir=out
//因为是虚拟机,所以是虚拟的设备GKI virtual_device
$ tools/bazel run //common-modules/virtual-device:virtual_device_x86_64_dist -- --dist_dir=out
以上如果构建没有问题,在out目录下会生成bzImage
和initramfs.img
文件,请记住他们的位置,之后会用到
Cuttlefish 应用新内核
使用kernel_path和initramfs_path即可对Cuttlefish应用新内核,十分方便,需要注意launch_cvd的运行基于上一篇文章编译成功,在AOSP根目录下运行
source build/envsetup.sh
lunch aosp_cf_x86_64_phone-userdebug
launch_cvd -kernel_path /home/prosixe/ssd/Android/android-kernel/out/bzImage -initramfs_path /home/prosixe/ssd/Android/android-kernel/out/initramfs.img
通过查看系统信息,内核已经发生了改变,系统可以正常开机
使用GDB调试Kernel
使用下面命令使内核可调试,读者实操时注意要区分aosp和android-kernel的目录。
//注意在aosp根目录
source build/envsetup.sh
lunch aosp_cf_x86_64_phone-userdebug
launch_cvd -kernel_path /home/prosixe/ssd/Android/android-kernel/out/bzImage -initramfs_path /home/prosixe/ssd/Android/android-kernel/out/initramfs.img -gdb_port 1234 -cpus=1 -extra_kernel_cmdline nokaslr
然后在另一个终端索引到android-kernel/common方便gdb索引到符号
//切换android-kernel根目录
cd common
gdb ../out/vmlinux
(gdb) target remote :1234
(gdb) hbreak start_kernel
(gdb) c
可以看到内核在start_kernel时停止了,并且可以正常显示对应的源代码。
总结
经过一步步的摸索,将Linux内核成功运行到了Cuttlefish中,在Cuttlefish的环境下,可以调试内核,调试Native代码,调试Framework代码,也可以当作你的常用“开发机”使用。
该文章是笔者学习中的一个总结,由于知识面的狭隘总有认识不到的错误产生,请大家不吝赐教。如果大家学习中遇见问题,也欢迎大家交流沟通。