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(https://android.googlesource.com/kernel/manifest/) 管理的,其中Linux Kernel的源码存在于kernel/common(https://android.googlesource.com/kernel/common/) 目录下,其他目录是与构建相关的工具链或者脚本等,在之前旧版本Linux Kernel构建中可以直接下载kernel/common的代码使用make直接编译出内核镜像,但是随着Android GKI的推出,这套方法就行不通了。读者最好使用repo提供的编译脚本等进行构建。

下面以aosp_cf_x86_64_phone-userdebugtarget为例,讲述如何一步步找到对应的分支

查找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)并没有运行成功虚拟机,可以遵循以下步骤获取。

  1. 索引到device/google/cuttlefish目录,device目录下存放了芯片和硬件厂商的相关产品配置,其中cuttlefish作为一款虚拟器,也被添加到了该目录下。
  2. 通过mgrep ":kernel"查看配置文件(该方法不是很通用,可以通过“添加新设备”了解相关知识),最后查看搜索到的配置文件,通过下图可以看到该目标链接的kernel映像文件位于kernel/prebuilts/6.1/x86_64/kernel-6.1
  3. 对该文件执行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是指https://android.googlesource.com/kernel/common/ 仓库的相应提交。

根据kernel/common的commitId找到对应repo分支

直接访问https://android.googlesource.com/kernel/common/+/963667856ef1 (注意按照实际输出更改commitId) 就可以得到对应的changeId,通过点击changeId链接就可以看到对应gerrit 地址,如下所示,该页面同时标注了Linux kernel的分支[android14-6.1], 这里的branch还是Linux Kernel的分支,那么如何得到repo的分支内,只有了,通过在 https://android.googlesource.com/kernel/manifest/+refs 中搜索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目录下会生成bzImageinitramfs.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代码,也可以当作你的常用“开发机”使用。
该文章是笔者学习中的一个总结,由于知识面的狭隘总有认识不到的错误产生,请大家不吝赐教。如果大家学习中遇见问题,也欢迎大家交流沟通。

#AOSP #WSL2 #Cuttlefish #Kernel