在 AVD 上使用 KernelSU

曾经尝试过给 AVD 构建带有 KernelSU 的内核,方便开发测试,但是发现内核模块不兼容,现在的方法是同时构建内核模块,略为复杂。

构建内核

https://source.android.com/docs/setup/build/building-kernels?hl=zh-cn

Android 14 的 AVD 内核版本:

1
Linux version 6.1.23-android14-4-00257-g7e35917775b8-ab9964412 (build-user@build-host) (Android (9796371, based on r487747) clang version 17.0.0 (https://android.googlesource.com/toolchain/llvm-project d9f89f4d16663d5012e5c09495f3b30ece3d2362), LLD 17.0.0) #1 SMP PREEMPT Mon Apr 17 20:50:58 UTC 2023

根据 KernelSU CI 进行操作,拉取对应版本源码

1
2
3
4
repo init --depth=1 --u https://android.googlesource.com/kernel/manifest -b common-android14-6.1-2023-12 --repo-rev=v2.16
REMOTE_BRANCH=$(git ls-remote https://android.googlesource.com/kernel/common android14-6.1-2023-12)
DEFAULT_MANIFEST_PATH=.repo/manifests/default.xml
repo --trace sync -c -j$(nproc --all) --no-tags

同步完成后可以用 bazel 构建

1
tools/bazel build //common:kernel_x86_64_dist

产物位置: bazel-out/k8-fastbuild/bin/common/kernel_x86_64/bzImage

解决模块不兼容

上面构建出来的内核与 AVD 原镜像中的模块不兼容,按理来说 GKI 是确保了模块接口稳定性的,虽然我们的 KMI Generation 不同。总之需要构建对应的模块。

有三个地方的模块会被加载:ramdisk ,/system(_dlkm)/lib/modules , /vendor/lib/modules 。

构建

上面执行的 dist 已经构建好了 system_dlkm ,输出在 bazel-out/k8-fastbuild/bin/common/kernel_x86_64/unstripped/ (其实应该有一个 dist 目录,不过我没指定,也没找到默认的在哪)。

我们还需要构建 vendor (以及 boot )的 ko 。根据文档,执行 tools/bazel run //common-modules/virtual-device:virtual_device_x86_64_dist 即可。输出在 out/virtual_device_x86_64/dist/

ramdisk

使用 magiskboot 查看 ramdisk ,发现在 lib/modules 下。

1
2
/data/adb/magisk/magiskboot decompress ramdisk.img ramdisk.cpio
/data/adb/magisk/magiskboot cpio ramdisk.cpio 'ls -r'

因此构建好模块后,只需要把新的 ko 添加到 cpio 即可。

1
2
3
/data/adb/magisk/magiskboot decompress ramdisk.img ramdisk.cpio
for f in *.ko; do /data/adb/magisk/magiskboot cpio ramdisk.cpio "add 0644 lib/modules/$f $f"; done
/data/adb/magisk/magiskboot compress=gzip ramdisk.cpio ramdisk.gz

需要用到的 ko 大概是这些:

1
cp out/virtual_device_x86_64/dist/{virtio-rng.ko,virtio_blk.ko,virtio_console.ko,virtio_dma_buf.ko,virtio_pci.ko,virtio_pci_legacy_dev.ko,virtio_pci_modern_dev.ko,vmw_vsock_virtio_transport.ko} /mnt/d/Documents/tmp/kmods

system & vendor

观察发现 /system/lib/modules/modules.load 是空的,只有 /vendor/lib/modules 有。于是写一个脚本解析需要的 ko ,并从构建好的 ko 中搜索,复制出来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
import os
import re
import sys
import shutil
from pathlib import Path

os.system('adb.exe pull /vendor/lib/modules/modules.dep /vendor/lib/modules/modules.load .')
os.system('adb.exe pull /system/lib/modules/modules.dep sys_modules.dep')

with open('modules.load', 'r') as f:
vendor_modules_to_load = re.split(r'\s+', f.read().strip())

vendor_modules_to_load = list(map(lambda x: '/vendor/lib/modules/' + x, vendor_modules_to_load))

to_load = set(vendor_modules_to_load)
to_load.add('/system/lib/modules/zram.ko')
deps = {}

def load_dep(fn):
global deps
with open('modules.dep', 'r') as f:
for l in f.readlines():
k, v = l.split(':')
k = k.strip()
v = v.strip()
if v == '':
continue
deps[k] = re.split(r'\s+', v)
# print(k, '->', deps[k])

load_dep('modules.dep')
load_dep('sys_odules.dep')

print(len(to_load))

while True:
orig_size = len(to_load)
for k in list(to_load):
if k in deps:
for v in deps[k]:
to_load.add(v)
new_size = len(to_load)
if orig_size == new_size:
break

print(len(to_load), to_load)
paths = {}

search_paths = ['/home/five_ec1cff/android-kernel/bazel-out/k8-fastbuild/bin/common/kernel_x86_64/unstripped/', '/home/five_ec1cff/android-kernel/out/virtual_device_x86_64/dist/']

for mod in to_load:
path = None
for search_path in search_paths:
tp = search_path + '/' + mod.split('/')[-1]
if os.path.exists(tp):
path = tp
break
paths[mod] = path

target_path = '/mnt/d/Documents/tmp/kmods'

for p in paths:
print(p, '<-', paths[p])
pp = Path(target_path + p)
pp.parent.mkdir(parents=True, exist_ok=True)
shutil.copy(paths[p], pp)

之后需要把构建好的 ko 放到 /system_dlkm 和 /vendor ,我们需要开启 avd 的 writable system ,这样会使用 overlayfs 提供一个可写的叠加层。

1
2
3
4
5
# 启动 writable system
emulator @API34 -writable-system
# 启用 writable system
adb root
adb remount # rw

首次启用需要先 adb remount 并重启才生效,之后如果启动 avd 指定了 -writable-system 才会使用叠加层,不指定则不会。

之后就可以直接写这些目录了,通过 adb push 推送 ko 即可。

KernelSU

也是参考 CI 的方式,注意构建内核必须用 CI 的命令,否则无法正确找到 KernelSU 版本。似乎是默认的构建方式用了 sandbox ,导致 Makefile 中 srctree 传入的路径不对。

1
tools/bazel run --config=fast --config=stamp --lto=thin //common:kernel_x86_64_dist -- --dist_dir=dist

指定了 dist 目录后,构建好的内核(bzImage)及 system_dlkm 都在 dist 目录下。

启动

带着构建好的内核和 patch 的 ramdisk 启动:

1
emulator @API34 -kernel D:\Documents\tmp\kmods\bzImage -no-snapshot-load -show-kernel -writable-system -ramdisk D:\Documents\tmp\kmods\ramdisk.gz

总结

虽然 KernelSU 支持 arm 和 x86,但自动构建一直以来只有 arm ,因此在 x86 上使用只能靠自己。像笔者这样坚持在 x86 上使用 KernelSU 也许是少数人,(也许你们不信 前 ZygiskOnKernelSU 现 ZygiskNext 的主要开发者开发和测试这个模块的主要环境是 AVD 上的 Magisk ,而 Magisk + ZygiskNext 是不推荐的,)毕竟笔者既没有新手机也没有 M1 M2 只能在 x86 PC 上跑模拟器来研究真是抱歉呢。总之,作为 x86 模拟器用户想用 KernelSU 只有两种选择,无论是 cuttlefish 还是 AVD ,集成和使用体验都很痛苦。

最近 KernelSU 正在筹划模块化的实现,如果能用于 AVD 将可以减少这种痛苦,值得一试。