安卓GKI内核模块开发编译工具包开发与使用"

admin 2026-10-17 14:15:11 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

cover_image

安卓GKI内核模块开发编译工具包开发与使用

原创

非虫
非虫

软件安全与逆向分析

2026年7月26日 20:08
湖北

在小说阅读器读本章

去阅读

安卓GKI内核模块开发编译工具包开发与使用

本文介绍dddk(Droid DDK)的设计思路与实现细节,讲解如何在ddk的基础上实现x86\_64与ARM64双架构原生编译加速,覆盖Android 13到Android 17全部GKI内核版本,以及通过GitHub Actions一键编译内核模块

作者:非虫

目录

  1. 从ddk到dddk的演进动机
  2. 整体架构与配置设计
  3. 原生编译加速的实现
  4. Android 17支持的关键细节
  5. dddk命令行工具使用
  6. 在GitHub Actions中编译内核模块
  7. 镜像构建流水线
  8. 总结

1 从ddk到dddk的演进动机

做过安卓内核模块开发的人都知道,编译一个.ko需要完整的内核源码树、匹配的工具链和正确的内核构建配置。Ylarod/ddk项目解决了环境搭建的问题,把AOSP Clang工具链和预编译好的内核源码打包到Docker镜像里,开发者拉取镜像后执行make即可得到.ko文件。

但在实际使用中遇到了几个瓶颈。

第一个问题是架构限制。原版ddk只提供x86\_64镜像,在ARM64服务器(如GitHub Actions的ubuntu-24.04-arm Runner或云厂商的ARM实例)上需要通过QEMU模拟运行x86\_64容器,编译速度慢到无法接受。对于拥有ARM64构建机的团队,硬件资源完全被浪费。

第二个问题是工具链选型。原版直接使用AOSP预编译的Clang工具链,但AOSP Clang仅发布x86\_64版本。在ARM64宿主机上,即使容器本身是ARM64的,也无法直接使用AOSP Clang。

第三个问题是版本覆盖。随着Android版本快速迭代,GKI内核从5.10一路演进到6.18。原版ddk支持的目标版本有限,缺少Android 16和Android 17的支持。Android 17引入了6.18内核和Rust 1.91.1工具链,构建环境发生了较大变化。

第四个问题是CI集成。原版缺少开箱即用的GitHub Actions集成方案,项目组想在CI里自动编译内核模块,需要自行编写复杂的工作流。

dddk(Droid DDK)就是在这些需求驱动下的重新实现。核心思路是用Android NDK替代AOSP Clang作为交叉编译工具链,实现x86\_64和ARM64宿主机的原生编译支持,同时覆盖从Android 13到Android 17的全部GKI内核版本。

2 整体架构与配置设计

dddk的架构分为四层,如下所示:

+----------------------------------------------------+
|           dddk 命令行工具 (scripts/dddk)             |
|         统一入口, 支持 docker/local 双模式            |
+----------------------------------------------------+
|                 Docker 镜像层                        |
|      builder, toolchain, ddk, ddk-min               |
+----------------------------------------------------+
|               构建系统 (build/)                      |
|      build-droid-ddk.py + mapping.json              |
+----------------------------------------------------+
|             GitHub Actions 工作流                    |
|      release.yml / publish-prebuilts.yml            |
+----------------------------------------------------+

2.1 mapping.json作为构建配置的单一事实源

整个项目的核心配置集中在mapping.json中,它定义了三类信息。

第一类是目标矩阵。每个构建目标由Android版本、Clang版本、Rust版本、NDK版本和支持的宿主平台组成:

{
\"android\":\"android17-6.18\",
\"clang\":\"clang-r584948c\",
\"rust\":\"rust-1.91.1\",
\"ndk\":\"r29\",
\"platforms\":[\"linux-amd64\",\"linux-arm64\"]
}

第二类是平台配置。每个宿主平台的工具链类型、NDK下载地址、Rust安装方式等:

{
\"linux-arm64\":{
\"toolchainKind\":\"android-ndk\",
\"ndks\":{
\"r29\":{
\"url\":\"https://github.com/SnowNF/ndk-aarch64-linux/releases/...\",
\"root\":\"r29\",
\"bin\":\"toolchains/llvm/prebuilt/linux-x86_64/bin\"
}
},
\"rust\":{
\"kind\":\"rustup\",
\"version\":\"1.91.1\"
}
}
}

第三类是镜像仓库,支持Docker Hub、GHCR和CNB三个镜像源:

{
\"registry\":{
\"droid-ddk\":{
\"github\":\"ghcr.io/feicong/droid-ddk\",
\"docker\":\"docker.io/fsx199/droid-ddk\",
\"cnb\":\"docker.cnb.cool/feicong/droid-ddk/droid-ddk\"
}
}
}

所有构建脚本、Dockerfile、CI工作流和dddk命令行工具都从这一个文件读取配置。新增一个内核版本,只需在mapping.json的matrix数组中加一条记录。

2.2 支持的GKI目标版本

dddk当前覆盖9个目标版本,横跨Android 13到Android 17,如表1所示。

dddk目标 ACK源码分支 NDK Rust
android13-5.15 android13-5.15-lts r25c 无
android14-5.15 android14-5.15-lts r25c 无
android14-6.1 android14-6.1-lts r25c 无
android15-6.1 android14-6.1-lts r25c 无
android15-6.6 android15-6.6-lts r25c 无
android16-6.6 android15-6.6-lts r25c 无
android16-6.12 android16-6.12-lts r29 1.82.0
android17-6.12 android16-6.12-lts r29 1.82.0
android17-6.18 android17-6.18-lts r29 1.91.1

表1 dddk支持的GKI目标版本

其中android15-6.1、android16-6.6和android17-6.12是三个\"跨代\"目标,用于设备升级了Android大版本但仍使用上一代内核的场景。选择目标时以设备uname -r输出中的ACK代际和内核版本为准。

3 原生编译加速的实现

3.1 用NDK替代AOSP Clang

AOSP预编译的Clang工具链只有x86\_64版本。要在ARM64宿主机上原生编译ARM64内核模块,需要一套能在ARM64上运行、输出ARM64目标代码的交叉编译工具链。

Android NDK天然满足这个需求。NDK中的Clang本身就是交叉编译器,支持多种目标架构。Google官方发布的NDK包含x86\_64宿主版本,社区项目SnowNF/ndk-aarch64-linux提供了ARM64宿主版本。

dddk的解法是x86\_64宿主机使用Google官方NDK,ARM64宿主机使用SnowNF ARM64 NDK。两者的Clang版本、头文件和链接器完全一致,区别仅在于宿主二进制文件的架构。

映射关系在mapping.json的platforms节中定义:

{
\"linux-amd64\":{
\"toolchainKind\":\"android-ndk\",
\"ndks\":{
\"r25c\":{
\"url\":\"https://dl.google.com/android/repository/android-ndk-r25c-linux.zip\",
\"sha256\":\"769ee342ea75f80619d985c2da990c48b3d8eaf45f48783a2d48870d04b46108\"
},
\"r29\":{
\"url\":\"https://dl.google.com/android/repository/android-ndk-r29-linux.zip\",
\"sha256\":\"4abbbcdc842f3d4879206e9695d52709603e52dd68d3c1fff04b3b5e7a308ecf\"
}
}
},
\"linux-arm64\":{
\"toolchainKind\":\"android-ndk\",
\"ndks\":{
\"r25c\":{
\"url\":\"https://github.com/SnowNF/ndk-aarch64-linux/releases/download/0.0.1/android-ndk-r25c-aarch64-linux.tgz\"
},
\"r29\":{
\"url\":\"https://github.com/SnowNF/ndk-aarch64-linux/releases/download/0.0.2/android-ndk-r29-linux-aarch64.tar.gz\"
}
}
}
}

Android 13到15使用NDK r25c,Android 16和17使用NDK r29。

3.2 NDK作为内核编译工具链的适配

NDK虽然自带Clang,但内核构建系统默认按AOSP Clang的目录布局寻找工具。使用NDK需要显式指定每一个工具的路径。dddk通过环境变量完成这个映射:

# dddk脚本中的NDK工具链配置
export CC=\"$CLANG_PATH/clang\"
export LD=\"$CLANG_PATH/ld.lld\"
export AR=\"$CLANG_PATH/llvm-ar\"
export NM=\"$CLANG_PATH/llvm-nm\"
export OBJCOPY=\"$CLANG_PATH/llvm-objcopy\"
export OBJDUMP=\"$CLANG_PATH/llvm-objdump\"
export STRIP=\"$CLANG_PATH/llvm-strip\"
export HOSTCC=\"$CLANG_PATH/clang\"
export HOSTCXX=\"$CLANG_PATH/clang++\"
export CLANG_TRIPLE=aarch64-linux-gnu-
export CROSS_COMPILE=aarch64-linux-gnu-
export ARCH=arm64
export LLVM=1
export LLVM_IAS=1

Docker镜像的Toolchain层(ddk-toolchain/Dockerfile)中,NDK被下载后,其Clang目录通过符号链接统一到/opt/droid-ddk/toolchain/bin:

RUNset -eux; \\
    toolchain_bin=\"/opt/droid-ddk/ndk/${NDK_ROOT}/toolchains/llvm/prebuilt/linux-x86_64/bin\"; \\
mkdir -p /opt/droid-ddk/toolchain; \\
ln -s \"$toolchain_bin\" /opt/droid-ddk/toolchain/bin

这样上层的DDK镜像和dddk命令行都通过/opt/droid-ddk/toolchain/bin访问工具链,无需关心底层是AOSP Clang还是NDK。

3.3 Ubuntu 26.04构建环境适配

dddk的基础构建镜像(ddk-builder)基于Ubuntu 26.04。这个选择带来了一些兼容性问题需要处理。

第一个问题是libxml2版本冲突。Ubuntu 26.04自带libxml2 2.14,而部分旧版内核的构建脚本依赖libxml2 2.9的API。Builder镜像单独编译了一份libxml2 2.9.14放在/opt/droid-ddk/compat/lib,通过LD_LIBRARY_PATH注入:

RUNset -eux; \\
    curl --retry 3 -fsSL \"$LIBXML2_COMPAT_URL\" -o /tmp/libxml2.tar.xz; \\
cd /tmp/libxml2-2.9.14; \\
    ./configure --prefix=/opt/droid-ddk/compat \\
      --without-python --without-lzma --without-iconv \\
      --disable-static; \\
    make -j\"$(nproc)\"; make install

ENV LD_LIBRARY_PATH=/opt/droid-ddk/compat/lib

第二个问题是宿主编译器兼容性标志。Android 14的6.1内核在新版Clang下会出现incompatible-pointer-types-discards-qualifiers编译错误。dddk为这些目标设置了额外的HOSTCFLAGS:

{
\"android\":\"android14-6.1\",
\"hostCFlags\":\"-Wno-error=incompatible-pointer-types-discards-qualifiers -DUSE_PKCS11_ENGINE\"
}

这些标志在mapping.json中按目标配置,构建脚本自动读取并传入。

3.4 原生编译与模拟编译的性能对比

原生编译和QEMU模拟编译的性能差距很大。如表2所示,以编译一个典型的内核模块为例。

场景 耗时
x86\_64 Runner加x86\_64镜像(原生) 约30秒
ARM64 Runner加ARM64镜像(原生) 约30秒
ARM64 Runner加x86\_64镜像(QEMU模拟) 5分钟以上

表2 原生编译与模拟编译耗时对比

双架构原生支持让ARM64 Runner不再是二等公民,CI矩阵可以自由选择宿主架构。

4 Android 17支持的关键细节

Android 17带来了两个重要变化,分别是6.18内核分支和Rust内核模块支持的升级。

4.1 新内核分支android17-6.18

Android 17引入了android17-6.18-lts分支,使用最新的Clang clang-r584948c(来自main-kernel-2026分支)和NDK r29。这是截至目前ACK支持的最新内核版本。

4.2 Rust工具链的双平台安装

从Android 16的6.12内核开始,GKI引入了Rust内核模块支持。Android 17进一步将Rust版本从1.82.0升级到1.91.1。

x86\_64和ARM64宿主机上的Rust工具链安装方式不同。

x86\_64宿主机使用AOSP预编译的Rust工具链,从platform/prebuilts/rust-toolchain/linux-x86仓库下载归档包,与AOSP Clang类似的分发方式:

# build-droid-ddk.py中的Rust预构建下载
url = f\"https://android.googlesource.com/{repo}/+archive/refs/heads/{branch}/{archive_path}.tar.gz\"

ARM64宿主机因为AOSP不提供ARM64版本的预编译Rust,需要通过rustup在线安装:

# ARM64 Rust安装流程
curl --proto '=https' --tlsv1.2 -fsSL https://sh.rustup.rs | sh -s -- -y --no-modify-path
rustup toolchain install 1.91.1 --profile minimal --component rust-src,rustfmt
cargo +1.91.1 install bindgen-cli --version 0.72.1 --locked

这里有一个不明显但关键的细节,即bindgen与CLANG_PATH的冲突。dddk的CLANG_PATH环境变量指向NDK的Clang目录,但bindgen-cli在检测到CLANG_PATH是一个目录(而非文件路径)时会行为异常。解法是为ARM64创建一个wrapper脚本,在调用bindgen前清除CLANG_PATH:

#!/bin/sh
unset CLANG_PATH
exec /opt/droid-ddk/.cargo/bin/bindgen \"$@\"

这个wrapper放在/opt/droid-ddk/bin/bindgen,PATH优先级高于cargo安装的原始bindgen。

4.3 CFI整数规范化配置

Android 16和17的6.12内核还需要启用CFI_ICALL_NORMALIZE_INTEGERS配置项,否则CFI(控制流完整性)检查会在模块加载时触发保护。构建脚本中对应的处理如下:

# build-droid-ddk.py中的内核配置
if android_branch in (\"android16-6.12\", \"android17-6.12\"):
    run(f\"{scripts_config} --file {config_file} -e CFI_ICALL_NORMALIZE_INTEGERS\")

需要注意的是,这里使用CFI_ICALL_NORMALIZE_INTEGERS而不是带CONFIG_前缀的完整名,因为scripts/config工具会自动添加前缀。

5 dddk命令行工具使用

dddk是一个独立的Bash脚本,安装后作为统一入口管理所有操作。

5.1 安装dddk

在Linux宿主机上执行以下命令安装:

curl -fsSL https://raw.githubusercontent.com/feicong/droid-ddk/main/host/install.sh | sudo bash

安装脚本将dddk放到/usr/local/bin/dddk。首次运行时会引导选择运行模式(docker或local)和镜像源(docker、github或cnb)。配置保存在~/.droid-ddk/目录下。

5.2 基本工作流

安装完成后,通过以下命令完成首次环境准备:

# 更新dddk脚本和mapping.json
dddk update

# 查看所有可用目标
dddk list-all

# 拉取目标镜像
dddk pull --target android17-6.18

# 查看已拉取的镜像
dddk list

5.3 编译内核模块

模块目录至少需要包含源码文件和一个Kbuild兼容的Makefile:

my-driver/
  my_driver.c
  Makefile

Makefile内容如下:

obj-m += my_driver.o

编译、清理和进入构建环境的命令:

MODULE_DIR=\"$PWD/my-driver\"
TARGET=android17-6.18

dddk build --target \"$TARGET\" --module \"$MODULE_DIR\"
dddk build --target \"$TARGET\" --module \"$MODULE_DIR\" -- -j8 V=1
dddk clean --target \"$TARGET\" --module \"$MODULE_DIR\"
dddk shell --target \"$TARGET\" --module \"$MODULE_DIR\"

--module参数将模块目录挂载到镜像内的/build,执行与镜像内核构建目录匹配的标准Kbuild命令。生成的.ko文件直接写入模块目录。

5.4 使用.ddk-version固定目标

在项目根目录创建.ddk-version文件可以省去每次输入--target:

echo android17-6.18 > .ddk-version
dddk pull
dddk build --module \"$PWD/my-driver\" -- -j8
dddk clean --module \"$PWD/my-driver\"

目标解析的优先级为:--target命令行参数优先于.ddk-version文件,.ddk-version文件优先于DDK_TARGET环境变量。

5.5 传递环境变量和自定义make参数

# 传递内核配置
dddk build -t android17-6.18 --env CONFIG_FOO=y -e CONFIG_BAR=n

# 传递make参数,双横线之后的参数传给make
dddk build -t android17-6.18 -- CFLAGS=-O2 V=1

# 生成编译数据库,用于clangd代码补全
dddk compdb android17-6.18

5.6 Docker模式与Local模式

dddk支持两种运行模式。

Docker模式是默认模式,通过Docker容器运行编译,无需在宿主机安装任何工具链。镜像包含完整的编译环境。

Local模式直接在宿主机上使用本地安装的工具链编译,适合已经部署好DDK环境的服务器或需要更快编译速度的场景。配置后通过DDK_ROOT环境变量(默认/opt/droid-ddk)定位工具链和内核源码。Local模式下的目录布局如下:

/opt/droid-ddk/
  ndk/              NDK工具链
  rust/             Rust工具链
  src/              内核源码
    android13-5.15/
    android17-6.18/
    ...
  kdir/             内核构建输出
    linux-amd64/
      android17-6.18/
    linux-arm64/
      android17-6.18/

Local模式对CI环境或构建服务器特别有用,避免每次构建都拉取数GB的Docker镜像。

6 在GitHub Actions中编译内核模块

dddk提供了feicong/android-kernel-build-action@v2 Action,用于在GitHub Actions中编译内核模块。

6.1 基本用法

工作流分为两个Job,先上传模块源码为Artifact,再调用Action编译:

name:BuildKernelModule

on:
push:
branches: [main]
workflow_dispatch:

jobs:
upload-module:
runs-on:ubuntu-24.04
steps:
-uses:actions/checkout@v6
-uses:actions/upload-artifact@v7
with:
name:hello-ko
path:path/to/hello-ko

build-module:
needs:upload-module
runs-on:ubuntu-24.04
steps:
-uses:feicong/android-kernel-build-action@v2
with:
tag:android17-6.18
arch:aarch64
module-path:hello-ko
module-name:hello-ko

模块目录需要包含Makefile和同名.c源文件。编译完成后输出Artifact名为Image-TAG-ARCH,其中的模块文件名为TAG_MODULE_NAME.ko。

6.2 多目标矩阵编译

如果需要同时编译多个内核版本的模块,可以使用矩阵策略:

jobs:
upload-module:
runs-on:ubuntu-24.04
steps:
-uses:actions/checkout@v6
-uses:actions/upload-artifact@v7
with:
name:hello-ko
path:drivers/hello-ko

build-module:
needs:upload-module
runs-on:ubuntu-24.04
strategy:
fail-fast:false
matrix:
target:
-android14-6.1
-android15-6.6
-android16-6.12
-android17-6.18
steps:
-uses:feicong/android-kernel-build-action@v2
with:
tag:${{matrix.target}}
arch:aarch64
module-path:hello-ko
module-name:hello-ko

这样一次Push就能自动产出四个内核版本的.ko文件。

6.3 使用ARM64 Runner加速

如果仓库有ARM64 Runner可用,可以利用原生编译避免模拟开销:

build-module:
needs:upload-module
runs-on:ubuntu-24.04-arm
steps:
-uses:feicong/android-kernel-build-action@v2
with:
tag:android17-6.18
arch:aarch64
module-path:hello-ko
module-name:hello-ko

dddk镜像同时发布x86\_64和ARM64版本,Action会根据Runner架构自动拉取匹配的镜像。

7 镜像构建流水线

dddk的镜像构建本身也完全自动化。理解这个流水线有助于需要自建私有镜像的场景。

7.1 四层镜像体系

dddk的镜像采用四层结构,如表3所示。

镜像 基础镜像 内容
droid-ddk-builder Ubuntu 26.04 gcc、make、flex、bison、libelf-dev、libssl-dev等构建依赖
droid-ddk-toolchain droid-ddk-builder NDK和Rust工具链,按目标版本分tag
droid-ddk droid-ddk-toolchain 内核源码加完整kdir,用于高级开发和调试
droid-ddk-min droid-ddk-toolchain 内核源码加精简kdir,满足绝大多数模块编译需求

表3 dddk镜像层次结构

droid-ddk-builder层与目标版本无关,所有目标共用。droid-ddk-toolchain层每个目标版本有独立的tag,例如droid-ddk-toolchain:android17-6.18。精简镜像droid-ddk-min的内核构建输出仅包含modules_prepare的结果加上补齐的头文件和构建文件,体积显著小于完整镜像。

7.2 预构建产物管理

内核源码和kdir构建输出以.tar.zst归档的形式存储在GitHub Release(tag prebuilts-v1)中。Dockerfile通过--mount=type=bind从prebuilts目录读取归档并解压:

RUN --mount=type=bind,source=prebuilts,target=/mnt/prebuilts,readonly \\
set -eux; \\
    tar -xf \"/mnt/prebuilts/src/src.${ANDROID_VER}.tar.zst\" -C /opt/droid-ddk/src; \\
    tar -xf \"/mnt/prebuilts/kdir/${artifact_platform}/kdir.${ANDROID_VER}.tar.zst\" \\
        -C \"/opt/droid-ddk/kdir/${artifact_platform}\"

publish-prebuilts.yml工作流负责在ARM64和x86\_64 Runner上分别编译内核并打包上传归档。release.yml工作流则从归档构建最终的Docker镜像。整个流程为:先发布预构建产物,然后构建builder镜像,接着在两个平台上并行构建toolchain镜像并合并manifest,最后在两个平台上并行构建ddk和ddk-min镜像并合并manifest。

每个目标版本的镜像都发布为多架构manifest,docker pull时自动选择匹配的架构。镜像同时推送到Docker Hub(docker.io/fsx199/droid-ddk)和GHCR(ghcr.io/feicong/droid-ddk)。

7.3 本地构建镜像

如果需要修改基础环境或添加自定义依赖,可以在本地构建:

# 构建builder镜像
make -C docker builder

# 构建特定目标的toolchain镜像
make -C docker toolchains VER=android17-6.18

# 构建DDK镜像,需要先准备prebuilts
make -C docker build VER=android17-6.18

# 构建精简镜像
make -C docker build-min VER=android17-6.18

8 总结

dddk在原版ddk的基础上解决了三个核心问题。

第一是原生编译加速。用Android NDK替代AOSP Clang,实现x86\_64和ARM64双架构原生编译,消除QEMU模拟带来的数倍性能损失。

第二是Android 17全版本覆盖。从Android 13的5.15内核到Android 17的6.18内核,覆盖9个GKI目标版本,包括Rust内核模块编译支持。

第三是CI开箱即用。提供feicong/android-kernel-build-action@v2 Action和完整的镜像构建流水线,一段YAML即可在GitHub Actions中编译内核模块。

项目地址:https://github.com/feicong/droid-ddk

安装命令:

curl -fsSL https://raw.githubusercontent.com/feicong/droid-ddk/main/host/install.sh | sudo bash

安卓GKI内核模块开发编译工具包开发与使用" 网络安全文章

安卓GKI内核模块开发编译工具包开发与使用"

本文介绍dddk工具包的设计与实现,通过使用AndroidNDK替代AOSPClang实现x86\\_64与ARM64双架构原生编译加速,覆盖Android13到17的GKI内核版本,并提供GitHubActions一键编译方案。核心结论是dddk解决了原版ddk的架构限制、工具链选型和版本覆盖问题,显著提升编译效率。可操作建议是开发者可直接使用dddk在CI中编译内核模块。
Windows应急响应排查框架(上)" 网络安全文章

Windows应急响应排查框架(上)"

本文梳理了Windows应急响应前半段排查框架,强调先问清异常情况再反推入侵路径。核心排查包括查隐藏克隆账号、分析日志定位异常登录、排查可疑网络连接与进程,以及利用Autoruns等工具全面检查启动项、计划任务和服务等持久化机制,以精准还原入侵入口与驻留方式。
特斯拉一夜蒸发2000亿美元,卖车赚的都砸进AI和机器人" 网络安全文章

特斯拉一夜蒸发2000亿美元,卖车赚的都砸进AI和机器人"

特斯拉2026年第二季度营收286.2亿美元同比增长26%,但GAAP营业利润暴跌57%至3.98亿美元,自由现金流转负至-11亿美元,导致股价单日暴跌14.5%,市值蒸发2000亿美元。利润下滑主因碳积分收入骤降、研发及管理费用攀升、资本开支暴增。公司正全面押注AI、自动驾驶和Optimus机器人,同时与SpaceX协同加深。市场关注其能否在资金耗尽前实现商业化回报。
评论:0   参与:  0