文章总结: 本文介绍安卓GKI内核模块开发编译工具包dddk的设计与实现。它通过用AndroidNDK替代AOSPClang实现x86_64与ARM64双架构原生编译加速,覆盖Android13到17全部GKI内核版本,并提供GitHubActions一键编译方案。核心创新在于解决原版ddk的架构限制、工具链选型、版本覆盖和CI集成问题,显著提升编译效率。 综合评分: 86 文章分类: 安全开发,安全工具,技术标准
安卓GKI内核模块开发编译工具包开发与使用
原创
非虫 非虫
软件安全与逆向分析
2026年7月26日 20:08 湖北
在小说阅读器读本章
去阅读
安卓GKI内核模块开发编译工具包开发与使用
本文介绍dddk(Droid DDK)的设计思路与实现细节,讲解如何在ddk的基础上实现x86_64与ARM64双架构原生编译加速,覆盖Android 13到Android 17全部GKI内核版本,以及通过GitHub Actions一键编译内核模块
作者:非虫
目录
- 从ddk到dddk的演进动机
- 整体架构与配置设计
- 原生编译加速的实现
- Android 17支持的关键细节
- dddk命令行工具使用
- 在GitHub Actions中编译内核模块
- 镜像构建流水线
- 总结
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内核模块开发编译工具包开发与使用》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。










评论