使用 KASAN+KCOV 构建 Pixel 内核

Kernel Address Sanitizer (KASAN) 可以帮助内核开发者和测试人员找出与运行时内存相关的错误,例如出界读取或写入操作问题,以及“释放后使用”相关问题。虽然 KASAN 因其运行时性能低以及导致内存使用量增加而未在正式 build 中启用,但它仍然是用来测试调试 build 的重要工具。

在与另一个名为 Kernel Coverage (KCOV) 的运行时工具搭配使用时,经过 KASAN 排错和 KCOV 插桩的代码可以帮助开发者与测试人员检测运行时内存错误以及获取代码覆盖率信息。在内核模糊测试(例如通过 syzkaller)的情景中,KASAN 可以协助确定崩溃的根本原因,而 KCOV 则会向模糊引擎提供代码覆盖率信息,以在测试用例或语料库重复数据删除方面提供帮助。

本页不讨论 KASAN 的内部工作原理或机制,而是指导您构建和修改 Android 开放源代码项目 (AOSP) 和 Pixel 的内核源代码,以便在开启 KASAN 和 KCOV 的情况下启动。

设置构建环境

请遵循下载和构建部分的步骤来设置构建环境。

构建 AOSP

下载 Android 源代码。为了构建 KASAN 映像,请选择未处于积极开发阶段的稳定 build。通常,最新的发布版本/稳定分支是不错的选择。如需详细了解 build 和分支,请参阅源代码标记和 build

成功检出源代码后,请从 Nexus 和 Pixel 设备的驱动程序二进制文件下载与目前所用设备和分支对应的必要设备 Blob。从系统芯片 (SOC) 制造商处同时下载供应商映像和二进制文件集。然后,解压下载的压缩包,运行其中包含的脚本,并接受许可。

接下来,请按照构建准备工作中的步骤,清理并设置您的构建环境,然后选择您的构建目标。

如需创建一个基准工作版本,请确保不要对第一个 build 进行任何修改:

make -j48

将您的构建结果刷入测试设备(例如 marlin),并使其启动:

cd out/target/product/marlin
ANDROID_PRODUCT_OUT=`pwd` fastboot flashall -w

启动到主屏幕后,您可能会看到一个显示以下信息的弹出式窗口:

There's an internal problem with your device. Contact your manufacturer for details. 此弹出式窗口的消息可能表示,您供应商的版本指纹与您 system 分区的版本指纹不一致。由于此 build 仅用于开发和测试,并非发布 build,因此您可以忽略此消息。

构建内核

要编译内核,您需要检出正确的源代码,对其进行交叉编译,然后在正确的 AOSP 目录中编译内核映像。

检出内核源代码

创建一个目录来存储内核源代码,并将 AOSP 内核 Git 代码库克隆到本地存储空间。

mkdir ~/src/marlin-kernel-src
cd ~/src/marlin-kernel-src
git clone https://android.googlesource.com/kernel/msm

完成后,您应该会看到一个名为 msm 的空目录。

进入 msm 目录,并通过 git checkout 命令检出您正在构建的源代码对应的分支。如需查看可用分支和标记的列表,请参阅 Android msm 内核源代码树

cd msm
git checkout TAG_NAME

完成此步骤后,msm 目录中应该会有相关内容。

执行交叉编译

接下来,您需要编译 Android 内核。

设置交叉编译器

如需构建内核,您需要设置交叉编译器。目前推荐的已经过测试的工具链是 Android 的 NDK 工具链的最新稳定版本。要下载 Android NDK,请访问官方 Android NDK 网站。为您的平台下载相应的 zip 文件,然后将其解压缩。这会产生类似于 android-ndk-NDK_VERSION 的目录。

下载 LZ4c 工具

Pixel 内核使用 LZ4 压缩,因此在构建内核时需要使用 lz4c 工具。如果您使用 Ubuntu,请使用以下命令安装 lz4c 工具:

sudo apt-get install liblz4-tool

构建内核

使用以下命令在 marlin-kernel-src/msm 目录中设置构建环境:

export ARCH=arm64
export CROSS_COMPILE=PATH_TO_NDK/android-ndk-NDK_VERSION/toolchains/aarch64-linux-android-TOOLCHAIN_VERSION/prebuilt/linux-x86_64/bin/aarch64-linux-android-

然后,构建一个未经修改的内核版本以创建基准工作版本:

make marlin_defconfig
make -j48

构建流程的结果可以在以下位置找到:arch/arm64/boot/Image.lz4-dtb

在 AOSP 中重新构建启动映像

构建内核映像之后,请使用以下命令将结果复制到 AOSP 的 device/google/marlin-kernel 目录下:

cp ${marlin-kernel-src}/msm/arch/arm64/boot/Image.lz4-dtb device/google/marlin-kernel
source build/envsetup.sh
lunch aosp_marlin-userdebug
make -j48

构建成功后,请使用以下命令刷入目标设备:

cd out/target/product/marlin
fastboot flashall -w

刷入之后,您的设备应该会启动。在设备完成启动后,检查 Settings -> System -> About phone 下的 Kernel version,验证您刷入设备的映像是否是您构建的内核映像。

修改内核

启用 KASAN 和 KCOV 编译选项

KASAN 和 KCOV 代码受编译标志保护,不会针对普通 build 启用。如需启用这些代码,请将 KASAN 和 KCOV 选项添加到配置文件中,但是要记得删除 LZ4 配置。

为此,请创建默认配置文件(例如 marlin_defconfig)的副本:

cd arch/arm64/configs
cp marlin_defconfig marlin-kasan_defconfig

在新的配置文件中,移除 CONFIG_KERNEL_LZ4=y 这一标志并添加以下标志:

CONFIG_KASAN=y
CONFIG_KASAN_INLINE=y
CONFIG_KCOV=y
CONFIG_SLUB=y
CONFIG_SLUB_DEBUG=y

使用新配置重新编译内核

修改完配置文件的副本后,请重新编译该内核。

重新配置内核

设置您的构建环境。构建您修改的 defconfig,并检查生成的 .config 文件中是否存在新添加的标志。

make marlin-kasan_defconfig
grep KASAN .config
CONFIG_HAVE_ARCH_KASAN=y
CONFIG_KASAN=y
# CONFIG_KASAN_OUTLINE is not set
CONFIG_KASAN_INLINE=y

您应该会看到 KASAN 标志。编译您的内核:

make -j48

查看修改后的内核映像

编译成功后,转到 arch/arm64/boot 目录查看编译结果。一般而言,Image.gz-dtb 大约为 23MB,比标准 build 大。

cd arch/arm64/boot
ls -lh Image.gz-dtb
-rw-r--r-- 1 username groupname 23M Aug 11 13:59 Image.gz-dtb

如需了解 KCOV 是否已正确编译,请针对生成的 vmlinux(位于内核源代码树的根目录)执行进一步的分析。如果您在 vmlinux 上运行 objdump,应该会看到对 __sanitizer_cov_trace_pc() 的大量调用。

sh -c '${CROSS_COMPILE}objdump -d vmlinux' | grep sanitizer
ffffffc000082030:    94040658    bl    ffffffc000183990 <__sanitizer_cov_trace_pc>
ffffffc000082050:    94040650    bl    ffffffc000183990 <__sanitizer_cov_trace_pc>
ffffffc000082078:    94040646    bl    ffffffc000183990 <__sanitizer_cov_trace_pc>
ffffffc000082080:    94040644    bl    ffffffc000183990 <__sanitizer_cov_trace_pc>
ffffffc0000820ac:    94040639    bl    ffffffc000183990 <__sanitizer_cov_trace_pc>

修改 AOSP 代码

您需要先调整 AOSP 源代码中用于控制设备启动方式的特定参数,然后再插入新的启动映像。这样做主要是为了确保新(已膨胀)映像正常启动。

调整板参数

调整设备的 BoardConfig.mk 文件中定义的启动参数。该文件位于 device/google/marlin/marlin(AOSP 源代码根目录的相对路径)下。

cd device/google/marlin/marlin
vim BoardConfig.mk

如果您不想修改 BoardConfig.mk 文件,则可以改为创建一个包含名称 marlin_kasan 的新启动目标。如需详细了解此过程,请参阅添加新设备

调整本地 Makefile 中的内核目标

新内核使用 LZ4 压缩算法来提升速度,但 KASAN 要求使用 gzip 来实现更好的压缩比。为了解决这个问题,您可以在 device/google/marlin/device-common.mk 中修改 LOCAL_KERNEL 变量指向的位置,从而指示构建系统要将哪个内核与最终目标绑定。

重新构建启动映像

如需重新构建启动映像,请将新的内核映像复制到 AOSP 树中的设备专用文件夹(例如 device/google/marlin-kernel)。请确保这是构建系统预期的内核目标映像位置(根据您之前的修改)。

接下来,请重新构建可刷入的映像,具体方式类似于您之前构建 AOSP 的方式。成功构建后,请照常刷入所有构建映像。

使用经过修改的内核映像启动设备

您现在应该有一个可启动并能进入主屏幕的 build。在该 build 中,您可以在早期启动阶段检查设备的 dmesg 输出是否存在“KernelAddressSanitizer initialized”消息。该消息表示 KASAN 已在启动期间初始化。此外,您还可以确认设备上是否存在 /sys/kernel/debug/kcov(如需执行此操作,您需要 root 权限)。

问题排查

您可以使用不同的内核版本进行实验,先将标准 build 用作基准工作版本,然后再启用 KASAN+KCOV 编译选项。如果流程中断,请先检查您设备上的引导加载程序和基带版本是否与新 build 要求的一致。最后,如果您使用的内核版本过高,那么您可能需要使用 Android 树上的较新的分支。