blob: 4f127a544b78e4428b60065900102ccb56b773e7 [file] [log] [blame]
<html devsite><head>
<title>使用 KASAN+KCOV 编译 Pixel 内核</title>
<meta name="project_path" value="/_project.yaml"/>
<meta name="book_path" value="/_book.yaml"/>
</head>
<body>
<!--
Copyright 2017 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<p>
Kernel Address Sanitizer (<a href="https://www.kernel.org/doc/html/latest/dev-tools/kasan.html">KASAN</a>) 可以帮助内核开发者和测试人员找出与运行时内存相关的错误,例如出界读取或写入操作问题,以及“释放后使用”相关问题。虽然 KASAN 因其运行时性能低以及导致内存使用量增加而未在正式版中启用,但它仍然是用来测试调试版本的重要工具。
</p>
<p>
在与另一个名为 Kernel Coverage (<a href="https://lwn.net/Articles/671640/">KCOV</a>) 的运行时工具搭配使用时,经过 KASAN 排错和 KCOV 检测的代码可以帮助开发者与测试人员检测运行时内存错误以及获取代码覆盖率信息。在内核模糊测试(例如通过 <a href="https://github.com/google/syzkaller">syzkaller</a>)的情景中,KASAN 可以协助确定崩溃的根本原因,而 KCOV 则会向模糊引擎提供代码覆盖率信息,以在测试用例或语料库重复数据删除方面提供帮助。
</p>
<p>
本页不讨论 KASAN 的内部工作原理或机制,而是指导您编译和修改 Android 开放源代码项目 (AOSP) 和 Pixel 的内核源代码,以便在开启 KASAN 和 KCOV 的情况下启动。
</p>
<h2 id="setting-up-your-build-environment">设置编译环境</h2>
<p>
请遵循<a href="/setup/requirements">下载和编译</a>部分的步骤来设置编译环境。
</p>
<h2 id="building-aosp">编译 AOSP</h2>
<p>
下载 <a href="/setup/downloading">Android 源代码</a>。为了编译 KASAN 映像,请选择未处于积极开发阶段的稳定版本。通常,最新的发布版本/稳定分支是不错的选择。有关版本和分支的更多信息,请参阅<a href="/setup/build-numbers#source-code-tags-and-builds">源代码标记和细分版本</a>
</p>
<p>
成功检出源代码后,请从 <a href="https://developers.google.com/android/drivers">Nexus 和 Pixel 设备的驱动程序二进制文件</a>下载与目前所用设备和分支对应的必要设备 Blob。从系统芯片 (SOC) 制造商处同时下载供应商映像和二进制文件集。然后,解压下载的压缩包,运行其中包含的脚本,并接受许可。
</p>
<aside class="note">
<strong>提示</strong>:请先仔细检查系统上是否安装了<a href="/setup/initializing#installing-the-jdk">正确版本的 JDK</a>,然后再继续下一步操作。
</aside>
<p>
接下来,请按照<a href="/setup/building#cleaning-up">编译准备工作</a>中的步骤,清理并设置您的编译环境,然后选择您的编译目标。
</p>
<p>
要创建一个基准工作版本,请确保不要对第一个版本进行任何修改:</p>
<pre class="devsite-click-to-copy">
<code class="devsite-terminal" data-terminal-prefix="~/src/aosp$ ">make -j48</code>
</pre>
<p>
将您的编译结果刷入测试设备(例如 marlin),并使其启动:
</p>
<pre class="devsite-click-to-copy">
<code class="devsite-terminal" data-terminal-prefix="~/src/aosp$ ">cd out/target/product/marlin</code>
<code class="devsite-terminal" data-terminal-prefix="~/src/aosp/out/target/product/marlin$ ">ANDROID_PRODUCT_OUT=`pwd` fastboot flashall -w</code>
</pre>
<p>
启动主屏幕后,您可能会看到一个显示以下信息的弹出式窗口:
</p>
<p>
<code>There's an internal problem with your device. Contact your manufacturer
for details.</code> 该弹出式窗口的消息可能表示,您供应商的版本指纹与您系统分区的版本指纹不一致。由于此版本仅用于开发和测试,而非用于发布,因此您可以忽略此消息。
</p>
<h2 id="building-the-kernel">编译内核</h2>
<p>要编译内核,您需要检出正确的源代码,对其进行交叉编译,然后在正确的 AOSP 目录中编译内核映像。</p>
<h3 id="checking-out-kernel-source-code">检出内核源代码</h3>
<p>
创建一个目录来存储内核源代码,并将 AOSP 内核 Git 代码库克隆到本地存储。
</p>
<pre class="devsite-click-to-copy">
<code class="devsite-terminal devsite-click-to-copy">mkdir ~/src/marlin-kernel-src</code>
<code class="devsite-terminal devsite-click-to-copy">cd ~/src/marlin-kernel-src</code>
<code class="devsite-terminal devsite-click-to-copy" data-terminal-prefix="~/src/marlin-kernel-src$ ">git clone https://android.googlesource.com/kernel/msm</code>
</pre>
<p>
完成后,您应该会看到一个名为 <code>msm</code> 的空目录。
</p>
<p>
进入 <code>msm</code> 目录并 <code>git checkout</code> 与您正在编译的源代码对应的分支。要查看可用分支和标记的列表,请参阅 <a href="https://android.googlesource.com/kernel/msm/">Android msm 内核源代码树</a>
</p>
<pre class="devsite-click-to-copy">
<code class="devsite-terminal devsite-click-to-copy" data-terminal-prefix="~/src/marlin-kernel-src$ ">cd msm</code>
<code class="devsite-terminal devsite-click-to-copy" data-terminal-prefix="~/src/marlin-kernel-src$ ">git checkout <var>TAG_NAME</var></code>
</pre>
<p>
完成此步骤后,<code>msm</code> 目录中应该会有相关内容。
</p>
<h3 id="performing-cross-compilation">执行交叉编译</h3>
<p>
接下来,您需要编译 Android 内核。
</p>
<h5 id="setting-up-your-cross-compiler">设置交叉编译器</h5>
<p>
要编译内核,您需要设置交叉编译器。目前推荐的已经过测试的工具链是 Android 的 NDK 工具链的最新稳定版本。要下载 Android NDK,请访问官方 <a href="https://developer.android.com/ndk/downloads/index.html">Android NDK 网站</a>。为您的平台下载相应的 zip 文件,然后将其解压缩。这会产生类似于 <code>android-ndk-<var>NDK_VERSION</var></code> 的目录。
</p>
<h5 id="downloading-the-lz4c-tool">下载 LZ4c 工具</h5>
<p>
Pixel 内核使用 <a hre="//lz4.github.io/lz4/">LZ4 压缩</a>算法,因此在编译内核时需要使用 <code>lz4c</code> 工具。如果您使用 Ubuntu,请使用以下命令安装 <code>lz4c</code> 工具:</p>
<pre class="devsite-terminal devsite-click-to-copy">sudo apt-get install liblz4-tool
</pre>
<h4 id="building-your-kernel">编译内核</h4>
<p>
<code>marlin-kernel-src/msm</code> 目录中使用以下命令设置编译环境:
</p>
<pre>
<code class="devsite-terminal devsite-click-to-copy" data-terminal-prefix="marlin-kernel-src/msm$ ">export ARCH=arm64</code>
<code class="devsite-terminal devsite-click-to-copy" data-terminal-prefix="marlin-kernel-src/msm$ ">export CROSS_COMPILE=<var>PATH_TO_NDK</var>/android-ndk-<var>NDK_VERSION</var>/toolchains/aarch64-linux-android-<var>TOOLCHAIN_VERSION</var>/prebuilt/linux-x86_64/bin/aarch64-linux-android-</code>
</pre>
<p>
然后,编译一个未经修改的内核版本以创建基准工作版本:
</p>
<pre class="devsite-click-to-copy">
<code class="devsite-terminal devsite-click-to-copy" data-terminal-prefix="marlin-kernel-src/msm$ ">make marlin_defconfig</code>
<code class="devsite-terminal devsite-click-to-copy" data-terminal-prefix="marlin-kernel-src/msm$ ">make -j48</code>
</pre>
<p>
编译流程的结果可以在以下位置找到:<code>arch/arm64/boot/Image.lz4-dtb</code>
</p>
<h4 id="rebuilding-the-boot-image-in-aosp">在 AOSP 中重新编译启动映像</h4>
<p>
编译内核映像之后,请使用以下命令将结果复制到 AOSP 的 <code>device/google/marlin-kernel</code> 目录下:
</p>
<pre class="devsite-click-to-copy">
<code class="devsite-terminal devsite-click-to-copy" data-terminal-prefix="~/src/aosp$ ">cp ${marlin-kernel-src}/msm/arch/arm64/boot/Image.lz4-dtb device/google/marlin-kernel</code>
<code class="devsite-terminal devsite-click-to-copy" data-terminal-prefix="~/src/aosp$ ">source build/envsetup.sh</code>
<code class="devsite-terminal devsite-click-to-copy" data-terminal-prefix="~/src/aosp$ ">lunch aosp_marlin-userdebug</code>
<code class="devsite-terminal devsite-click-to-copy" data-terminal-prefix="~/src/aosp$ ">make -j48</code>
</pre>
<p>
编译成功后,请使用以下命令刷入目标设备:
</p>
<pre class="devsite-click-to-copy">
<code class="devsite-terminal devsite-click-to-copy" data-terminal-prefix="~/src/aosp$ ">cd out/target/product/marlin</code>
<code class="devsite-terminal devsite-click-to-copy" data-terminal-prefix="~/src/aosp/out/target/product/marlin$ ">fastboot flashall -w</code>
</pre>
<p>刷入之后,您的设备应该会启动。在设备完成启动后,检查 <code>Settings -&gt; System -&gt; About phone</code> 下的 <code>Kernel
version</code>,验证您刷入设备的映像是否是您编译的内核映像。
</p>
<h2 id="modifying-the-kernel">修改内核</h2>
<h3 id="enabling-kasan-and-kcov-compile-options">启用 KASAN 和 KCOV 编译选项</h3>
<p>
KASAN 和 KCOV 代码受编译标记保护,不会针对普通版本启用。要启用这些代码,请将 KASAN 和 KCOV 选项添加到配置文件中,但是要记得删除 LZ4 配置。
</p>
<p>
为此,请创建默认配置文件的副本,例如 <code>marlin_defconfig</code>
</p>
<pre class="devsite-click-to-copy">
<code class="devsite-terminal devsite-click-to-copy" data-terminal-prefix="marlin-kernel-src/msm$ ">cd arch/arm64/configs</code>
<code class="devsite-terminal devsite-click-to-copy" data-terminal-prefix="marlin-kernel-src/msm/arch/arm64/configs$ ">cp marlin_defconfig marlin-kasan_defconfig</code>
</pre>
<p>
在新的配置文件中,移除 <code>CONFIG_KERNEL_LZ4=y</code> 这一标记并添加以下标记:
</p>
<pre class="devsite-click-to-copy">CONFIG_KASAN=y
CONFIG_KASAN_INLINE=y
CONFIG_KCOV=y
CONFIG_SLUB=y
CONFIG_SLUB_DEBUG=y
</pre>
<h2 id="recompiling-the-kernel-with-new-configuration">使用新配置重新编译内核</h2>
<p>
修改完配置文件的副本后,请重新编译该内核。
</p>
<h3 id="reconfiguring-the-kernel">重新配置内核</h3>
<p>
设置您的<a href="/setup/building-kernels#building">编译环境</a>。编译您修改的 <code>defconfig</code>,并检查生成的 <code>.config</code> 文件中是否存在新添加的标记。
</p>
<pre class="devsite-click-to-copy">
<code class="devsite-terminal devsite-click-to-copy" data-terminal-prefix="marlin-kernel-src/msm$ ">make marlin-kasan_defconfig</code>
<code class="devsite-terminal devsite-click-to-copy" data-terminal-prefix="marlin-kernel-src/msm$ ">grep KASAN .config
CONFIG_HAVE_ARCH_<strong>KASAN</strong>=y
CONFIG_<strong>KASAN</strong>=y
# CONFIG_<strong>KASAN</strong>_OUTLINE is not set
CONFIG_<strong>KASAN</strong>_INLINE=y</code>
</pre>
<p>
您应该会看到 KASAN 标记。编译您的内核:</p>
<pre class="devsite-terminal devsite-click-to-copy" data-terminal-prefix="marlin-kernel-src/msm$ ">make -j48
</pre>
<h3 id="checking-the-modified-kernel-image">查看修改后的内核映像</h3>
<p>
编译成功后,转到 <code>arch/arm64/boot</code> 目录查看编译结果。一般而言,<code>Image.gz-dtb</code> 大约为 23MB,比标准版本大。
</p>
<pre class="devsite-click-to-copy">
<code class="devsite-terminal devsite-click-to-copy" data-terminal-prefix="marlin-kernel-src/msm$ ">cd arch/arm64/boot</code>
<code class="devsite-terminal devsite-click-to-copy" data-terminal-prefix="marlin-kernel-src/msm/arch/arm64/boot$ ">ls -lh Image.gz-dtb
-rw-r--r-- 1 username groupname 23M Aug 11 13:59 Image.gz-dtb</code>
</pre>
<p>
要了解 KCOV 是否已经过正确编译,请针对生成的 <code>vmlinux</code>(位于内核源代码树的根目录)执行进一步的分析。如果您在 vmlinux 上运行 <code>objdump</code>,应该会看到对 <code>__sanitizer_cov_trace_pc()</code> 的大量调用。
</p>
<pre class="devsite-click-to-copy">
<code class="devsite-terminal devsite-click-to-copy" data-terminal-prefix="marlin-kernel-src$ ">sh -c '${CROSS_COMPILE}objdump -d vmlinux' | grep sanitizer
ffffffc000082030: 94040658 bl ffffffc000183990 &lt;__sanitizer_cov_trace_pc&gt;
ffffffc000082050: 94040650 bl ffffffc000183990 &lt;__sanitizer_cov_trace_pc&gt;
ffffffc000082078: 94040646 bl ffffffc000183990 &lt;__sanitizer_cov_trace_pc&gt;
ffffffc000082080: 94040644 bl ffffffc000183990 &lt;__sanitizer_cov_trace_pc&gt;
ffffffc0000820ac: 94040639 bl ffffffc000183990 &lt;__sanitizer_cov_trace_pc&gt;
</code></pre>
<h2 id="modifying-aosp-code">修改 AOSP 代码</h2>
<p>
您需要先调整 AOSP 源代码中用于控制设备启动方式的特定参数,然后再插入新的启动映像。这样做主要是为了确保新(已扩容)映像正常启动。
</p>
<h3 id="adjusting-board-parameters">调整板参数</h3>
<p>
调整设备的 <code>BoardConfig.mk</code> 文件中定义的启动参数。该文件位于 <code>device/google/marlin/marlin</code>(AOSP 源代码根目录的相对路径)下。
</p>
<pre class="devsite-click-to-copy">
<code class="devsite-terminal devsite-click-to-copy" data-terminal-prefix="~/src/aosp$ ">cd device/google/marlin/marlin</code>
<code class="devsite-terminal devsite-click-to-copy" data-terminal-prefix="~/src/aosp/device/google/marlin/marlin$ ">vim BoardConfig.mk</code>
</pre>
<aside class="caution">
<p>
<strong>注意</strong>:请务必先备份原始的 <code>BoardConfig.mk</code> 文件,然后再继续操作,以防出现问题。
</p>
<p>
要进行的调整可借助 <code>git diff</code> 结果总结如下:
</p>
<pre>diff --git a/marlin/BoardConfig.mk b/marlin/BoardConfig.mk
index 31533fb9..81caf05d 100644
--- a/marlin/BoardConfig.mk
+++ b/marlin/BoardConfig.mk
@@ -116,15 +116,10 @@ BOARD_EGL_CFG := device/google/marlin/egl.cfg
BOARD_KERNEL_BASE := 0x80000000
BOARD_KERNEL_PAGESIZE := 4096
<var>-ifneq ($(filter marlin_kasan, $(TARGET_PRODUCT)),)</var>
BOARD_KERNEL_OFFSET := 0x80000
BOARD_KERNEL_TAGS_OFFSET := 0x02500000
BOARD_RAMDISK_OFFSET := 0x02700000
BOARD_MKBOOTIMG_ARGS := --kernel_offset $(BOARD_KERNEL_OFFSET) --ramdisk_offset $(BOARD_RAMDISK_OFFSET) --tags_offset $(BOARD_KERNEL_TAGS_OFFSET)
<var>-else
-BOARD_KERNEL_TAGS_OFFSET := 0x02000000
-BOARD_RAMDISK_OFFSET := 0x02200000
-endif</var>
TARGET_KERNEL_ARCH := arm64
TARGET_KERNEL_HEADER_ARCH := arm64
</pre>
</aside>
<p>
如果您不想修改 <code>BoardConfig.mk</code> 文件,则可以改为创建一个包含名称 <code>marlin_kasan</code> 的新启动目标。要详细了解此过程,请参阅<a href="/setup/add-device">添加新设备</a>
</p>
<h3 id="adjusting-the-kernel-target-in-the-local-makefile">调整本地 Makefile 中的内核目标</h3>
<p>
新内核使用 LZ4 压缩算法来提升速度,但 KASAN 要求使用 gzip 来实现更好的压缩比。为了解决这个问题,您可以在 <code>device/google/marlin/device-common.mk</code> 中修改 <code>LOCAL_KERNEL</code> 变量指向的位置,从而指示编译系统要将哪个内核与最终目标绑定。
</p>
<h2 id="rebuilding-boot-image">重新编译启动映像</h2>
<p>
要重新编译启动映像,请将新的内核映像复制到 AOSP 树中的设备专用文件夹(例如 <code>device/google/marlin-kernel</code>)。请确保这是编译系统预期的内核目标映像位置(根据您之前的修改)。
</p>
<p>
接下来,请重新编译可刷入的映像,具体方式类似于您之前<a href="#building-aosp">编译 AOSP</a> 的方式。成功编译后,请照常刷入所有编译映像。
</p>
<h2 id="booting-your-device-with-a-modified-kernel-image">使用经过修改的内核映像启动设备</h2>
<p>
您现在应该有一个可启动并能进入主屏幕的版本。在该版本中,您可以在早期启动阶段检查设备的 <code>dmesg</code> 输出中是否存在“<code>KernelAddressSanitizer
initialized</code>”消息。该消息表示 KASAN 已在启动期间初始化。此外,您还可以确认设备上是否存在 <code>/sys/kernel/debug/kcov</code>(要执行此操作,您需要 root 权限)。
</p>
<h2 id="troubleshooting">问题排查</h2>
<p>
您可以使用不同的内核版本进行实验,先将标准版本用作基准工作版本,然后再启用 KASAN+KCOV 编译选项。如果流程中断,请先检查您设备上的引导加载程序和基带版本是否与新版本要求的一致。最后,如果您使用的内核版本过高,那么您可能需要使用 Android 树上的较新的分支。
</p>
</body></html>