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

admin 2026-08-04 07:29:26 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文介绍安卓GKI内核模块开发编译工具包dddk的设计与实现。它通过用AndroidNDK替代AOSPClang实现x86_64与ARM64双架构原生编译加速,覆盖Android13到17全部GKI内核版本,并提供GitHubActions一键编译方案。核心创新在于解决原版ddk的架构限制、工具链选型、版本覆盖和CI集成问题,显著提升编译效率。 综合评分: 86 文章分类: 安全开发,安全工具,技术标准


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.jsonmatrix数组中加一条记录。

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.1android16-6.6android17-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.jsonplatforms节中定义:

{
"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=bindprebuilts目录读取归档并解压:

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内核模块开发编译工具包开发与使用》

评论:0   参与:  0