KernelSU 非 GKI 设备集成指南:kprobe 自动集成与手动内核源码修改全解析
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及对应 越南语文档整理而成面向希望在非 GKInon-Generic Kernel ImageAndroid 设备上自行编译并集成 KernelSU 的内核开发者。文中将完整覆盖 kprobe 自动集成、手动修改内核源码两种路径以及 Safe Mode、path_umount回移植等进阶技巧并结合当前仓库内核源码kernel/给出实现层面的佐证。引言非 GKI 设备与 KernelSU 的兼容边界KernelSU 是一种基于内核的 Android root 方案仓库根目录的 README 即将其定位为 A Kernel based root solution for Android。GKIGeneric Kernel Image设备统一了内核接口因而官方能够直接提供通用 boot 镜像而非 GKI 设备的内核碎片化严重——厂商各自维护内核源码、版本跨度大本指南明确说明 KernelSU 曾被回移植到4.14 及更早版本官方无法为这些设备提供统一的非 GKI boot.img。因此非 GKI 设备的集成方式只有一个开发者自行基于设备内核源码编译带 KernelSU 的内核。在动手之前需要先确认两个硬性前提你的内核源码可以构建出能正常启动的镜像——如果内核未开源vendor 未发布源码集成 KernelSU 将非常困难内核版本兼容——官方文档声明自 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/driversGKI 布局否则回退到drivers/非 GKI 传统布局两个目录都不存在则报错退出kernel/setup.sh第 14-26 行克隆并检出克隆tiann/KernelSU仓库到KernelSU/无参数时检出最新 tag有参数main或v0.9.5等 commit/tag时检出对应版本第 40-53 行建立符号链接在驱动目录下创建kernelsu - KernelSU/kernel的软链接第 55 行接入 Kbuild向drivers/Makefile追加obj-$(CONFIG_KSU) kernelsu/向drivers/Kconfig的endmenu前插入source drivers/kernelsu/Kconfig第 58-59 行使 KernelSU 作为内核驱动子目录参与编译支持回滚--cleanup参数可移除上述符号链接、Makefile/Kconfig 改动并删除KernelSU/目录第 29-37 行方便集成失败后还原。第二步确认并开启 kprobe 相关内核配置KernelSU 的 kprobe 钩子依赖内核的 kprobe 基础设施。检查你的 defconfig若未开启则补充以下配置CONFIG_KPROBESy CONFIG_HAVE_KPROBESy CONFIG_KPROBE_EVENTSy这一点在当前仓库的 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_MODULESy若依然无效则使用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_KSUy结合 kernel/Kconfig 可以看到CONFIG_KSU是 tristate 选项默认值为y编译进内核时直接y若想以独立模块方式编译LKM 模式可设为m此时模块名为kernelsu对应 kernel/Kbuild 中的obj-$(CONFIG_KSU) kernelsu.o。同一 Kconfig 下还有KSU_DEBUG、KSU_DISABLE_MANAGER、KSU_DISABLE_POLICY等衍生选项可供按需裁剪。第三步在 VFS 层插入 KernelSU 调用点核心步骤手动集成需要在以下四个内核函数中插入 KernelSU 的 hook 调用每个函数都有对应实现通常位于所列文件中函数所在文件KernelSU 调用do_execveat_commonfs/exec.cksu_handle_execveat/ksu_handle_execveat_sucompatdo_faccessatfs/open.cksu_handle_faccessatvfs_readfs/read_write.cksu_handle_vfs_readvfs_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.c中do_faccessat的 patchdiff --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) /* wheres F_OK, X_OK, W_OK, R_OK? */ return -EINVAL;fs/read_write.c中vfs_read的 patchdiff --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.c中vfs_statx的 patchdiff --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) /* wheres 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失败的处理如果手动集成后在终端中执行pmpackage manager命令失败需要修改fs/devpts/inode.c。参考 patchdiff --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 mustnt 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。集成后的验证与故障排查无论选择哪种方式完成构建后建议按以下顺序验证确认 Kconfig 生效编译前在最终.config中确认CONFIG_KSUy且kprobe 方式CONFIG_KPROBESy检查编译输出观察 kernel/Kbuild 打印的KernelSU version信息形如-- KernelSU version: 30xxx确认 KernelSU 确实参与了编译而非被跳过启动验证若 kprobe 方式下出现 bootloop按前文方法注释 ksu_sucompat_init() 与 ksu_ksud_init() 定位是否为 kprobe 损坏手动集成方式下则确认已禁用CONFIG_KPROBES以避免 Safe Mode 被误触发功能验证安装 KernelSU Manager 应用确认 root 授权、模块管理、Umount modules需回移植path_umount等核心功能正常。结语非 GKI 设备集成 KernelSU 的本质是在碎片化的厂商内核中为 KernelSU 找到可靠的 hook 挂载点kprobe 可用时走自动化配置路径kprobe 不可用时则在 VFS 层的do_execveat_common、do_faccessat、vfs_read、vfs_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),仅供参考