KernelSU 非 GKI 设备集成指南:kprobe 自动集成与手动内核源码修改全解析
2026/9/13 12:45:24 网站建设 项目流程

KernelSU 非 GKI 设备集成指南:kprobe 自动集成与手动内核源码修改全解析

【免费下载链接】KernelSUA Kernel based root solution for Android项目地址: https://gitcode.com/GitHub_Trending/ke/KernelSU

本文基于仓库文档 website/docs/guide/how-to-integrate-for-non-gki.md(及对应 越南语文档)整理而成,面向希望在非 GKI(non-Generic Kernel Image)Android 设备上自行编译并集成 KernelSU 的内核开发者。文中将完整覆盖 kprobe 自动集成、手动修改内核源码两种路径,以及 Safe Mode、path_umount回移植等进阶技巧,并结合当前仓库内核源码(kernel/)给出实现层面的佐证。

引言:非 GKI 设备与 KernelSU 的兼容边界

KernelSU 是一种基于内核的 Android root 方案(仓库根目录的 README 即将其定位为 "A Kernel based root solution for Android")。GKI(Generic Kernel Image)设备统一了内核接口,因而官方能够直接提供通用 boot 镜像;而非 GKI 设备的内核碎片化严重——厂商各自维护内核源码、版本跨度大(本指南明确说明 KernelSU 曾被回移植到4.14 及更早版本),官方无法为这些设备提供统一的非 GKI boot.img。

因此,非 GKI 设备的集成方式只有一个:开发者自行基于设备内核源码编译带 KernelSU 的内核。在动手之前,需要先确认两个硬性前提:

  1. 你的内核源码可以构建出能正常启动的镜像——如果内核未开源(vendor 未发布源码),集成 KernelSU 将非常困难;
  2. 内核版本兼容——官方文档声明:自 KernelSU v1.0 起,官方已停止对非 GKI 设备的支持,最后一个受支持版本是v0.9.5。因此集成时务必选用正确的版本(下文所有setup.sh调用都应按此选择 tag)。

满足上述条件后,有两条集成路径可选:

集成方式适用场景侵入性
kprobe 自动集成内核的 kprobe 机制工作正常低,仅需配置内核选项
手动修改源码kprobe 不可用(上游 bug、内核低于 4.8 等)高,需手动 patch 多个内核文件

下面依次展开。

方式一:基于 kprobe 的自动集成

KernelSU 使用 kprobe 机制实现内核 hook。如果 kprobe 在你的内核上运行可靠,官方推荐优先使用这种方式——它不需要修改内核核心源码,只需把 KernelSU 源码树接入构建系统并开启相关 Kconfig 选项。

第一步:将 KernelSU 加入内核源码树

内核源码根目录(即包含drivers/目录的目录)执行官方提供的 kernel/setup.sh:

# 最新 tag(稳定版) curl -LSs "https://raw.githubusercontent.com/tiann/KernelSU/main/kernel/setup.sh" | bash - # main 分支(dev 版) curl -LSs "https://raw.githubusercontent.com/tiann/KernelSU/main/kernel/setup.sh" | bash -s main # 指定 tag,例如 v0.5.2(对非 GKI 设备请使用 v0.9.5) curl -LSs "https://raw.githubusercontent.com/tiann/KernelSU/main/kernel/setup.sh" | bash -s v0.9.5

结合仓库中的 kernel/setup.sh 源码,可以看清这个脚本到底做了什么:

  • 定位驱动目录:优先使用common/drivers(GKI 布局),否则回退到drivers/(非 GKI 传统布局),两个目录都不存在则报错退出(kernel/setup.sh第 14-26 行);
  • 克隆并检出:克隆tiann/KernelSU仓库到KernelSU/,无参数时检出最新 tag,有参数(mainv0.9.5等 commit/tag)时检出对应版本(第 40-53 行);
  • 建立符号链接:在驱动目录下创建kernelsu -> KernelSU/kernel的软链接(第 55 行);
  • 接入 Kbuild:向drivers/Makefile追加obj-$(CONFIG_KSU) += kernelsu/,向drivers/Kconfigendmenu前插入source "drivers/kernelsu/Kconfig"(第 58-59 行),使 KernelSU 作为内核驱动子目录参与编译;
  • 支持回滚--cleanup参数可移除上述符号链接、Makefile/Kconfig 改动并删除KernelSU/目录(第 29-37 行),方便集成失败后还原。

第二步:确认并开启 kprobe 相关内核配置

KernelSU 的 kprobe 钩子依赖内核的 kprobe 基础设施。检查你的 defconfig,若未开启则补充以下配置:

CONFIG_KPROBES=y CONFIG_HAVE_KPROBES=y CONFIG_KPROBE_EVENTS=y

这一点在当前仓库的 kernel/Kconfig 中也有印证:config KSU声明为tristate,并depends on KPROBES && EXT4_FS,其 help 文本明确写道 "Requires CONFIG_KPROBES for kernel hooking support. Requires CONFIG_EXT4_FS forext4_unregister_sysfs",即KPROBES 是 KernelSU 内核侧 hook 能力的硬依赖

如果开启后 kprobe 仍未生效,可以尝试在内核配置中追加:

CONFIG_MODULES=y

若依然无效,则使用make menuconfig在配置界面中搜索 kprobe 的其他依赖项,逐一补齐。

第三步:重新编译并验证

配置完成后重新编译内核。KernelSU 作为驱动随内核一并编译进镜像,正常情况下即可正常工作。编译时可通过 kernel/Kbuild 观察版本信息:脚本会通过git rev-list --count HEAD计算KSU_VERSION = 30000 + git 提交数并注入-DKSU_VERSION(第 114-124 行),因此建议让KernelSU/目录保持为一个 git 仓库,否则会退化为默认版本号 16。

排障:集成后 bootloop 意味着什么

如果在集成 KernelSU 后出现 bootloop,一个非常可能的原因是:kprobe 在你的内核中本身是损坏的。此时要么修复内核源码中的 kprobe 缺陷,要么放弃本方式、改用下文的手动集成。

官方还给出了一个判断 kprobe 是否损坏的实用方法:注释掉KernelSU/kernel/ksu.c中的ksu_sucompat_init()ksu_ksud_init(),重新编译后如果设备能正常启动,则说明问题很可能出在 kprobe 上。这两个函数在当前仓库中依然存在,可对照查看:ksu_sucompat_init()(注册su_compat特性处理器)与 ksu_ksud_init()(在 kernel/core/init.c 的kernelsu_init()中被调用,负责初始化 ksud 用户态守护进程的集成)。

方式二:手动修改内核源码

如果 kprobe 无法在你的内核上工作——无论是由于上游 bug,还是内核版本低于 4.8——可以退而求其次,采用手动集成:不依赖 kprobe,而是直接在 VFS(虚拟文件系统)层的几个关键函数中插入 KernelSU 的调用点。

第一步:接入 KernelSU 源码树

与 kprobe 方式相同,先在内核源码根目录执行:

curl -LSs "https://raw.githubusercontent.com/tiann/KernelSU/main/kernel/setup.sh" | bash -s v0.9.5

第二步:在 defconfig 中显式启用 CONFIG_KSU

不同设备上 defconfig 的位置不同:可能在arch/arm64/configs,也可能在arch/arm64/configs/vendor/your_defconfig。无论使用哪个 defconfig,都需要显式设置CONFIG_KSUy启用 /n禁用)。启用时 defconfig 中应包含:

# KernelSU CONFIG_KSU=y

结合 kernel/Kconfig 可以看到,CONFIG_KSU是 tristate 选项,默认值为y;编译进内核时直接=y,若想以独立模块方式编译(LKM 模式)可设为=m,此时模块名为kernelsu(对应 kernel/Kbuild 中的obj-$(CONFIG_KSU) += kernelsu.o)。同一 Kconfig 下还有KSU_DEBUGKSU_DISABLE_MANAGERKSU_DISABLE_POLICY等衍生选项可供按需裁剪。

第三步:在 VFS 层插入 KernelSU 调用点(核心步骤)

手动集成需要在以下四个内核函数中插入 KernelSU 的 hook 调用(每个函数都有对应实现,通常位于所列文件中):

函数所在文件KernelSU 调用
do_execveat_commonfs/exec.cksu_handle_execveat/ksu_handle_execveat_sucompat
do_faccessatfs/open.cksu_handle_faccessat
vfs_readfs/read_write.cksu_handle_vfs_read
vfs_statxfs/stat.cksu_handle_stat

fs/exec.c为例,参考 patch 如下(注意用#ifdef CONFIG_KSU包裹,保证未启用 KernelSU 时对内核零影响):

diff --git a/fs/exec.c b/fs/exec.c index ac59664eaecf..bdd585e1d2cc 100644 --- a/fs/exec.c +++ b/fs/exec.c @@ -1890,11 +1890,14 @@ static int __do_execve_file(int fd, struct filename *filename, return retval; } +#ifdef CONFIG_KSU +extern bool ksu_execveat_hook __read_mostly; +extern int ksu_handle_execveat(int *fd, struct filename **filename_ptr, void *argv, + void *envp, int *flags); +extern int ksu_handle_execveat_sucompat(int *fd, struct filename **filename_ptr, + void *argv, void *envp, int *flags); +#endif static int do_execveat_common(int fd, struct filename *filename, struct user_arg_ptr argv, struct user_arg_ptr envp, int flags) { + #ifdef CONFIG_KSU + if (unlikely(ksu_execveat_hook)) + ksu_handle_execveat(&fd, &filename, &argv, &envp, &flags); + else + ksu_handle_execveat_sucompat(&fd, &filename, &argv, &envp, &flags); + #endif return __do_execve_file(fd, filename, argv, envp, flags, NULL); }

fs/open.cdo_faccessat的 patch:

diff --git a/fs/open.c b/fs/open.c index 05036d819197..965b84d486b8 100644 --- a/fs/open.c +++ b/fs/open.c @@ -348,6 +348,8 @@ SYSCALL_DEFINE4(fallocate, int, fd, int, mode, loff_t, offset, loff_t, len) return ksys_fallocate(fd, mode, offset, len); } +#ifdef CONFIG_KSU +extern int ksu_handle_faccessat(int *dfd, const char __user **filename_user, int *mode, + int *flags); +#endif /* * access() needs to use the real uid/gid, not the effective uid/gid. * We do this by temporarily clearing all FS-related capabilities and @@ -355,6 +357,7 @@ SYSCALL_DEFINE4(fallocate, int, fd, int, mode, loff_t, offset, loff_t, len) */ long do_faccessat(int dfd, const char __user *filename, int mode) { const struct cred *old_cred; struct cred *override_cred; struct path path; struct inode *inode; struct vfsmount *mnt; int res; unsigned int lookup_flags = LOOKUP_FOLLOW; + #ifdef CONFIG_KSU + ksu_handle_faccessat(&dfd, &filename, &mode, NULL); + #endif if (mode & ~S_IRWXO) /* where's F_OK, X_OK, W_OK, R_OK? */ return -EINVAL;

fs/read_write.cvfs_read的 patch:

diff --git a/fs/read_write.c b/fs/read_write.c index 650fc7e0f3a6..55be193913b6 100644 --- a/fs/read_write.c +++ b/fs/read_write.c @@ -434,10 +434,14 @@ ssize_t kernel_read(struct file *file, void *buf, size_t count, loff_t *pos) } EXPORT_SYMBOL(kernel_read); +#ifdef CONFIG_KSU +extern bool ksu_vfs_read_hook __read_mostly; +extern int ksu_handle_vfs_read(struct file **file_ptr, char __user **buf_ptr, + size_t *count_ptr, loff_t **pos); +#endif ssize_t vfs_read(struct file *file, char __user *buf, size_t count, loff_t *pos) { ssize_t ret; + #ifdef CONFIG_KSU + if (unlikely(ksu_vfs_read_hook)) + ksu_handle_vfs_read(&file, &buf, &count, &pos); + #endif + if (!(file->f_mode & FMODE_READ)) return -EBADF; if (!(file->f_mode & FMODE_CAN_READ))

fs/stat.cvfs_statx的 patch:

diff --git a/fs/stat.c b/fs/stat.c index 376543199b5a..82adcef03ecc 100644 --- a/fs/stat.c +++ b/fs/stat.c @@ -148,6 +148,8 @@ int vfs_statx_fd(unsigned int fd, struct kstat *stat, } EXPORT_SYMBOL(vfs_statx_fd); +#ifdef CONFIG_KSU +extern int ksu_handle_stat(int *dfd, const char __user **filename_user, int *flags); +#endif + /** * vfs_statx - Get basic and extra attributes by filename * @dfd: A file descriptor representing the base dir for a relative filename @@ -170,6 +172,7 @@ int vfs_statx(int dfd, const char __user *filename, int flags, int error = -EINVAL; unsigned int lookup_flags = LOOKUP_FOLLOW | LOOKUP_AUTOMOUNT; + #ifdef CONFIG_KSU + ksu_handle_stat(&dfd, &filename, &flags); + #endif if ((flags & ~(AT_SYMLINK_NOFOLLOW | AT_NO_AUTOMOUNT | AT_EMPTY_PATH | KSTAT_QUERY_FLAGS)) != 0) return -EINVAL;

这四个 hook 分别对应 KernelSU 的核心能力:execve钩子用于拦截并重定向/system/bin/su的执行(su 兼容层);faccessat/stat钩子用于在权限校验前重写路径,让合法 uid 的su访问被透明映射到 ksud 守护进程;vfs_read钩子则用于隐藏特定文件内容(SELinux 隐藏等场景)。这些逻辑在当前仓库的 kernel/feature/sucompat.c 中依然有完整实现,例如ksu_handle_faccessat_sucompat()会先判断当前 uid 是否在白名单内,再决定是否把su_path/system/bin/su)重写为ksud_user_path()并临时切换凭据后放行(第 91-124 行)。

第四步:关闭 sucompat 并重新编译

patch 完成后,编辑KernelSU/kernel/ksu.c去掉enable_sucompat()的调用(对应旧版接口;当前仓库中为 ksu_sucompat_init()),然后重新构建内核。构建成功后 KernelSU 即可正常工作。

内核版本差异的处理

非 GKI 内核跨度极大(文档说明支持范围可低至 4.14 之前),因此手动 patch 时还需要按内核版本做适配:

  • 内核没有vfs_statx:改用vfs_fstatat作为替代挂载点,patch 如下:
diff --git a/fs/stat.c b/fs/stat.c index 068fdbcc9e26..5348b7bb9db2 100644 --- a/fs/stat.c +++ b/fs/stat.c @@ -87,6 +87,8 @@ int vfs_fstat(unsigned int fd, struct kstat *stat) } EXPORT_SYMBOL(vfs_fstat); +#ifdef CONFIG_KSU +extern int ksu_handle_stat(int *dfd, const char __user **filename_user, int *flags); +#endif int vfs_fstatat(int dfd, const char __user *filename, struct kstat *stat, int flag) { @@ -94,6 +96,8 @@ int vfs_fstatat(int dfd, const char __user *filename, struct kstat *stat, int error = -EINVAL; unsigned int lookup_flags = 0; + #ifdef CONFIG_KSU + ksu_handle_stat(&dfd, &filename, &flag); + #endif + if ((flag & ~(AT_SYMLINK_NOFOLLOW | AT_NO_AUTOMOUNT | AT_EMPTY_PATH)) != 0) goto out;
  • 内核低于 4.17、找不到do_faccessat:直接定位faccessat系统调用定义处插入调用:
diff --git a/fs/open.c b/fs/open.c index 2ff887661237..e758d7db7663 100644 --- a/fs/open.c +++ b/fs/open.c @@ -355,6 +355,9 @@ SYSCALL_DEFINE4(fallocate, int, fd, int, mode, loff_t, offset, loff_t, len) return error; } +#ifdef CONFIG_KSU +extern int ksu_handle_faccessat(int *dfd, const char __user **filename_user, int *mode, + int *flags); +#endif + /* * access() needs to use the real uid/gid, not the effective uid/gid. * We do this by temporarily clearing all FS-related capabilities and @@ -370,6 +373,8 @@ SYSCALL_DEFINE3(faccessat, int, dfd, const char __user *, filename, int, mode) int res; unsigned int lookup_flags = LOOKUP_FOLLOW; + #ifdef CONFIG_KSU + ksu_handle_faccessat(&dfd, &filename, &mode, NULL); + #endif + if (mode & ~S_IRWXO) /* where's F_OK, X_OK, W_OK, R_OK? */ return -EINVAL;

进阶一:启用 Safe Mode(强烈建议)

Safe Mode 是 KernelSU 内置的防 bootloop 机制:开机时按住音量减键即可跳过所有模块加载,进入安全模式以便排查问题。官方强烈建议开启此功能,它对防止模块导致的 bootloop 非常有用。

手动集成时,需要修改drivers/input/input.c中的input_handle_event函数:

diff --git a/drivers/input/input.c b/drivers/input/input.c index 45306f9ef247..815091ebfca4 100755 --- a/drivers/input/input.c +++ b/drivers/input/input.c @@ -367,10 +367,13 @@ static int input_get_disposition(struct input_dev *dev, return disposition; } +#ifdef CONFIG_KSU +extern bool ksu_input_hook __read_mostly; +extern int ksu_handle_input_handle_event(unsigned int *type, unsigned int *code, int *value); +#endif + static void input_handle_event(struct input_dev *dev, unsigned int type, unsigned int code, int value) { int disposition = input_get_disposition(dev, type, code, &value); + #ifdef CONFIG_KSU + if (unlikely(ksu_input_hook)) + ksu_handle_input_handle_event(&type, &code, &value); + #endif if (disposition != INPUT_IGNORE_EVENT && type != EV_SYN) add_input_randomness(type, code, value);

警告:使用手动集成且未禁用 kprobe 时,用户可能在开机后仅凭按音量减键就意外触发 Safe Mode!因此采用手动集成方式时,必须在内核配置中禁用CONFIG_KPROBES

进阶二:终端执行pm失败的处理

如果手动集成后在终端中执行pm(package manager)命令失败,需要修改fs/devpts/inode.c。参考 patch:

diff --git a/fs/devpts/inode.c b/fs/devpts/inode.c index 32f6f1c68..d69d8eca2 100644 --- a/fs/devpts/inode.c +++ b/fs/devpts/inode.c @@ -602,6 +602,8 @@ struct dentry *devpts_pty_new(struct pts_fs_info *fsi, int index, void *priv) return dentry; } +#ifdef CONFIG_KSU +extern int ksu_handle_devpts(struct inode*); +#endif + /** * devpts_get_priv -- get private data for a slave * @pts_inode: inode of the slave @@ -610,6 +612,7 @@ struct dentry *devpts_pty_new(struct pts_fs_info *fsi, int index, void *priv) */ void *devpts_get_priv(struct dentry *dentry) { + #ifdef CONFIG_KSU + ksu_handle_devpts(dentry->d_inode); + #endif if (dentry->d_sb->s_magic != DEVPTS_SUPER_MAGIC) return NULL; return dentry->d_fsdata;

进阶三:回移植 path_umount 以启用 "Umount modules" 功能

如果内核版本低于 5.9,需要手动把path_umount从 5.9 回移植到fs/namespace.c,否则 "Umount modules"(卸载模块)功能无法工作。

为什么必须回移植它?可以看当前仓库的 kernel/feature/kernel_umount.c 第 44-52 行:内核侧卸载逻辑直接extern int path_umount(struct path *path, int flags);并调用它——也就是说path_umount是 KernelSU 内核卸载功能的外部符号依赖,如果宿主内核(如 pre-GKI 的老版本)没有这个导出函数,功能就无法编译通过或运行时报错。5.9 内核才引入该接口,所以老内核需要回移植。

官方给出的参考 patch:

--- a/fs/namespace.c +++ b/fs/namespace.c @@ -1739,6 +1739,39 @@ static inline bool may_mandlock(void) } #endif +static int can_umount(const struct path *path, int flags) +{ + struct mount *mnt = real_mount(path->mnt); + + if (flags & ~(MNT_FORCE | MNT_DETACH | MNT_EXPIRE | UMOUNT_NOFOLLOW)) + return -EINVAL; + if (!may_mount()) + return -EPERM; + if (path->dentry != path->mnt->mnt_root) + return -EINVAL; + if (!check_mnt(mnt)) + return -EINVAL; + if (mnt->mnt.mnt_flags & MNT_LOCKED) /* Check optimistically */ + return -EINVAL; + if (flags & MNT_FORCE && !capable(CAP_SYS_ADMIN)) + return -EPERM; + return 0; +} + +int path_umount(struct path *path, int flags) +{ + struct mount *mnt = real_mount(path->mnt); + int ret; + + ret = can_umount(path, flags); + if (!ret) + ret = do_umount(mnt, flags); + + /* we mustn't call path_put() as that would clear mnt_expiry_mark */ + dput(path->dentry); + mntput_no_expire(mnt); + return ret; +} /* * Now umount can handle mount points as well as block devices. * This is important for filesystems which use unnamed block devices.

patch 中can_umount()负责校验 flags 合法性、挂载权限(may_mount())、挂载点根目录、check_mnt()MNT_LOCKED标志以及MNT_FORCE所需的CAP_SYS_ADMIN能力;通过校验后调用do_umount()真正执行卸载,最后手动dput()+mntput_no_expire()释放引用(刻意不用path_put(),以免清除mnt_expiry_mark)。

集成后的验证与故障排查

无论选择哪种方式,完成构建后建议按以下顺序验证:

  1. 确认 Kconfig 生效:编译前在最终.config中确认CONFIG_KSU=y且(kprobe 方式)CONFIG_KPROBES=y
  2. 检查编译输出:观察 kernel/Kbuild 打印的KernelSU version信息(形如-- KernelSU version: 30xxx),确认 KernelSU 确实参与了编译而非被跳过;
  3. 启动验证:若 kprobe 方式下出现 bootloop,按前文方法注释 ksu_sucompat_init() 与 ksu_ksud_init() 定位是否为 kprobe 损坏;手动集成方式下则确认已禁用CONFIG_KPROBES以避免 Safe Mode 被误触发;
  4. 功能验证:安装 KernelSU Manager 应用,确认 root 授权、模块管理、"Umount modules"(需回移植path_umount)等核心功能正常。

结语

非 GKI 设备集成 KernelSU 的本质,是在碎片化的厂商内核中为 KernelSU 找到可靠的 hook 挂载点:kprobe 可用时走自动化配置路径,kprobe 不可用时则在 VFS 层的do_execveat_commondo_faccessatvfs_readvfs_statx四个函数上手动打补丁。记住两个关键约束——非 GKI 支持止步于 KernelSU v0.9.5手动集成必须禁用CONFIG_KPROBES——再配合 Safe Mode 与path_umount回移植,即可在旧设备上获得稳定可用的基于内核的 root 能力。如需深入了解 KernelSU 内核侧各模块(su 兼容层、内核卸载、SELinux 隐藏等)的实现细节,可直接研读仓库 kernel/ 目录下的源码。

【免费下载链接】KernelSUA Kernel based root solution for Android项目地址: https://gitcode.com/GitHub_Trending/ke/KernelSU

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询