news 2026/9/13 20:18:11

KernelSU 非 GKI 内核集成实战指南:kprobe 自动集成与手动源码补丁全解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
KernelSU 非 GKI 内核集成实战指南:kprobe 自动集成与手动源码补丁全解析

KernelSU 非 GKI 内核集成实战指南:kprobe 自动集成与手动源码补丁全解析

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

本文基于 KernelSU 官方文档(俄文版)整理,并结合当前仓库源码进行深度印证。内容面向需要为旧款(非 GKI)Android 设备自编译内核、并希望集成 KernelSU 的开发者,涵盖kprobe自动集成、手动修改内核源码两条完整路线,以及安全模式、path_umount回移(backport)等关键附加功能。

文档适用范围与版本警示

::: warning 存档说明 本文对应文档仅用于存档参考,自 KernelSU v1.0 起,官方已放弃对非 GKI 设备的支持。KernelSU 最后正式支持非 GKI 内核的版本为v0.9.5,集成时务必使用正确的版本(可参考同主题英文文档的版本提示)。本文所述流程面向旧版本 KernelSU 的集成场景。 :::

KernelSU 可以被集成进非 GKI 内核,官方曾将其移植到 Linux 4.14 及更早版本。由于非 GKI 内核碎片化严重,项目无法提供统一的构建方式,因此不会为非 GKI 设备发布现成的 boot.img;你需要自行编译集成 KernelSU 的内核镜像。

集成前必须满足一个硬性前置条件:

  • 你应当能够从内核源码编译出一个可启动的内核
  • 如果设备内核不开源,则很难为其运行 KernelSU。

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

  1. 自动集成:使用kprobe完成内核钩子;
  2. 手动集成:直接修改内核源码,手工植入调用点。

集成方式一:使用 kprobe 自动集成

KernelSU 依赖kprobe完成内核钩子(kernel hooks)。如果你的内核中kprobe运行稳定可靠,官方推荐优先使用这种方式。

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

在内核源码根目录执行官方提供的集成脚本(脚本源码位于本仓库 kernel/setup.sh):

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

该脚本的实际行为可以从 kernel/setup.sh 中直接确认:

  • 自动定位drivers/目录(支持common/driversdrivers两种布局,见initialize_variables());
  • git cloneKernelSU 仓库到KernelSU/目录;
  • 无参数时自动检出最新 tag(git checkout "$(git describe --abbrev=0 --tags)"),传入参数则检出指定 commit 或 tag;
  • drivers/下创建指向KernelSU/kernel的符号链接kernelsu
  • drivers/Makefile追加obj-$(CONFIG_KSU) += kernelsu/
  • drivers/Kconfigendmenu之前插入source "drivers/kernelsu/Kconfig"

同时脚本还提供了--cleanup参数,用于一键回滚上述所有修改(删除符号链接、还原 Makefile 与 Kconfig、删除KernelSU/目录)。

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

KernelSU 的集成依赖 Kconfig 中的KSU选项,而该选项本身依赖 kprobe 支持。查看仓库 kernel/Kconfig 可确认:

config KSU tristate "KernelSU function support" depends on KPROBES && EXT4_FS default y

因此,请检查你的内核配置文件(defconfig)中是否已开启 kprobe,若未开启则补充以下配置:

CONFIG_KPROBES=y CONFIG_HAVE_KPROBES=y CONFIG_KPROBE_EVENTS=y

重新编译内核后,KernelSU 应当能够正常工作。

第三步:kprobe 未生效与启动循环的排查

如果发现KPROBES依然没有被激活,可以尝试开启CONFIG_MODULES;若仍然无效,则使用make menuconfig搜索 KPROBES 的其他依赖项。

如果在集成 KernelSU 后出现启动循环(bootloop),则可能意味着你的内核中kprobe 本身存在缺陷。此时应修复 kprobe 的 bug,或者改用下文的手动集成方式。

如何判断 kprobe 是否损坏?官方给出的排查方法:在KernelSU/kernel/ksu.c中注释掉ksu_sucompat_init()ksu_ksud_init()两个初始化调用。如果注释后设备能正常启动,则说明问题很可能出在 kprobe 上(例如ksu_ksud_init()中注册的input_eventkprobe 失败,参考 ksud_integration.c 中register_kprobe(&input_event_kp)的用法)。

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

如果 kprobe 在你的内核中不可用(可能是上游 bug,或内核版本低于 4.8),可以尝试手动集成。

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

根据需要的版本选择对应命令:

  • 最新稳定 tag
curl -LSs "https://raw.githubusercontent.com/tiann/KernelSU/main/kernel/setup.sh" | bash -
  • main 开发分支
curl -LSs "https://raw.githubusercontent.com/tiann/KernelSU/main/kernel/setup.sh" | bash -s main
  • 指定 tag(例如 v0.5.2)
curl -LSs "https://raw.githubusercontent.com/tiann/KernelSU/main/kernel/setup.sh" | bash -s v0.5.2

::: tip 关于版本选择的补充 结合英文母版文档的提示:KernelSU 1.0 及以后版本不再支持非 GKI 内核,最后支持版本为 v0.9.5,请务必使用正确版本。此外,部分设备的 defconfig 位于arch/arm64/configs,另一些则在arch/arm64/configs/vendor/your_defconfig。无论使用哪个 defconfig,都需要显式开启CONFIG_KSUy启用 /n禁用),例如:

# KernelSU CONFIG_KSU=y

该选项在仓库 kernel/Kconfig 中被定义为tristate,依赖KPROBES && EXT4_FS,默认值为y。 :::

第二步:在内核源码中植入 KernelSU 钩子调用

手动集成的核心,是在内核 VFS 层的关键路径上插入 KernelSU 的钩子函数。官方给出了四个补丁作为参考。以下补丁中的函数在当前仓库中均有对应实现,例如 sucompat.c 中的ksu_handle_faccessat_sucompat、sucompat.h 中导出的各 handler,以及 syscall_event_bridge.c 中的ksu_hook_faccessatksu_hook_execve/ksu_hook_execveatksu_hook_newfstatat等桥接逻辑。

补丁 1:fs/exec.c—— 挂钩 execve 路径
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; } +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); static int do_execveat_common(int fd, struct filename *filename, struct user_arg_ptr argv, struct user_arg_ptr envp, int flags) { + if (unlikely(ksu_execveat_hook)) + ksu_handle_execveat(&fd, &filename, &argv, &envp, &flags); + else + ksu_handle_execveat_sucompat(&fd, &filename, &argv, &envp, &flags); return __do_execve_file(fd, filename, argv, envp, flags, NULL); }

这里通过ksu_execveat_hook布尔开关选择进入普通 execve 钩子还是 su 兼容层钩子,unlikely()保证正常路径开销极小。

补丁 2:fs/open.c—— 挂钩 faccessat 路径
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); } +extern int ksu_handle_faccessat(int *dfd, const char __user **filename_user, int *mode, + int *flags); /* * 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; + ksu_handle_faccessat(&dfd, &filename, &mode, NULL); if (mode & ~S_IRWXO) /* where's F_OK, X_OK, W_OK, R_OK? */ return -EINVAL;
补丁 3:fs/read_write.c—— 挂钩 vfs_read 路径
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); +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); ssize_t vfs_read(struct file *file, char __user *buf, size_t count, loff_t *pos) { ssize_t ret; + if (unlikely(ksu_vfs_read_hook)) + ksu_handle_vfs_read(&file, &buf, &count, &pos); + if (!(file->f_mode & FMODE_READ)) return -EBADF; if (!(file->f_mode & FMODE_CAN_READ))
补丁 4:fs/stat.c—— 挂钩 stat 路径
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); +extern int ksu_handle_stat(int *dfd, const char __user **filename_user, int *flags); + /** * 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; + ksu_handle_stat(&dfd, &filename, &flags); if ((flags & ~(AT_SYMLINK_NOFOLLOW | AT_NO_AUTOMOUNT | AT_EMPTY_PATH | KSTAT_QUERY_FLAGS)) != 0) return -EINVAL;

这四个需要查找的函数及其通常所在文件:

  1. do_faccessat,通常在fs/open.c
  2. do_execveat_common,通常在fs/exec.c
  3. vfs_read,通常在fs/read_write.c
  4. vfs_statx,通常在fs/stat.c

第三步:针对旧内核的适配变体

没有vfs_statx?改用vfs_fstatat

如果内核中没有vfs_statx函数,请改为在vfs_fstatat中植入调用:

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); +extern int ksu_handle_stat(int *dfd, const char __user **filename_user, int *flags); + 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; + ksu_handle_stat(&dfd, &filename, &flag); + 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; } +extern int ksu_handle_faccessat(int *dfd, const char __user **filename_user, int *mode, + int *flags); + /* * 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; + ksu_handle_faccessat(&dfd, &filename, &mode, NULL); + if (mode & ~S_IRWXO) /* where's F_OK, X_OK, W_OK, R_OK? */ return -EINVAL;

第四步:启用内置安全模式(强烈建议)

要启用 KernelSU 内置的安全模式(Safe Mode),需要修改drivers/input/input.c中的input_handle_event函数:

::: tip 强烈建议开启该功能,它对防止启动循环(bootloop)非常有帮助! :::

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; } +extern bool ksu_input_hook __read_mostly; +extern int ksu_handle_input_handle_event(unsigned int *type, unsigned int *code, int *value); + 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); + if (unlikely(ksu_input_hook)) + ksu_handle_input_handle_event(&type, &code, &value); if (disposition != INPUT_IGNORE_EVENT && type != EV_SYN) add_input_randomness(type, code, value);

从源码看,安全模式的判定逻辑位于 ksud_integration.c:ksu_handle_input_handle_event统计KEY_VOLUMEDOWN(音量减)按键事件,连续按下 3 次(volumedown_pressed_count >= 3)即触发安全模式,随后通过ksu_is_safe_mode()被 userspace 查询确认,并立即卸载输入钩子(ksu_stop_input_hook_runtime())。

::: warning 关于误触发的补充说明 结合英文母版文档的提示:如果你使用手动集成且没有禁用CONFIG_KPROBES,用户可能在开机后通过长按音量减键意外触发安全模式。因此,使用手动集成时有必要禁用CONFIG_KPROBES。 :::

附加修复:终端中无法执行pm命令?

如果遇到该问题,需要修改fs/devpts/inode.c,参考补丁如下:

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; } +extern int ksu_handle_devpts(struct inode*); + /** * 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) { + ksu_handle_devpts(dentry->d_inode); if (dentry->d_sb->s_magic != DEVPTS_SUPER_MAGIC) return NULL; return dentry->d_fsdata;

附加功能:手动回移path_umount(pre-GKI 的模块卸载支持)

如果你希望"卸载模块(Umount modules)"功能在 pre-GKI 内核上正常工作,而你的内核版本低于 5.9,则需要手动将path_umount从 5.9 回移到fs/namespace.c。参考补丁如下:

--- 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.

如果不回移path_umount,"卸载模块"功能将无法工作。该补丁将校验逻辑封装在can_umount()中(检查标志合法性、挂载权限、挂载点根目录、check_mntMNT_LOCKED标志以及MNT_FORCE所需的CAP_SYS_ADMIN能力),再调用do_umount()执行真正的卸载,并手动管理dentry/mount引用计数以避免清除mnt_expiry_mark

源码层面的补充印证

手动集成补丁中引用的各钩子函数,在当前仓库源码中均有对应实现,可作为集成正确性的参照:

  • execve 相关ksu_handle_execveat/ksu_handle_execveat_sucompat的声明与实现分布在 sucompat.h、sucompat.c(ksu_handle_execveat_sucompat)以及 syscall_event_bridge.c(ksu_hook_execve/ksu_hook_execveat桥接,内部通过static_branch控制 ksud 钩子与 su 兼容层分派);
  • faccessat 相关ksu_handle_faccessat_sucompat定义于 sucompat.c,桥接入口ksu_hook_faccessat位于 syscall_event_bridge.c;
  • 安全模式输入钩子ksu_handle_input_handle_event实现于 ksud_integration.c,其 kprobe 注册方式(input_event_kp)见同文件ksu_ksud_init()
  • Kconfig 依赖CONFIG_KSUKPROBES && EXT4_FS的依赖关系,可直接在 kernel/Kconfig 中核实,这解释了为何两种集成方式都绕不开 kprobe 相关配置;
  • 集成脚本:手动方式第一步使用的setup.sh即仓库内的 kernel/setup.sh,支持 tag/分支参数与--cleanup清理。

总结

非 GKI 内核集成 KernelSU 的核心决策路径如下:

  1. 确认内核可自编译且开源,这是所有操作的前提;
  2. 优先尝试 kprobe 自动集成:运行setup.sh添加 KernelSU → 确保CONFIG_KPROBES/CONFIG_HAVE_KPROBES/CONFIG_KPROBE_EVENTS开启 → 重新编译;
  3. 若 kprobe 不可用(内核 < 4.8 或上游 bug)导致 bootloop,则改用手动集成:添加 KernelSU → 开启CONFIG_KSU=y→ 在fs/exec.cfs/open.cfs/read_write.cfs/stat.c四个文件中按上文补丁植入钩子(旧内核注意vfs_fstatatfaccessat变体);
  4. 按需补充安全模式input.c)、pm命令修复devpts/inode.c)、path_umount回移fs/namespace.c,内核 < 5.9);
  5. 手动集成时记得禁用CONFIG_KPROBES,避免误触发安全模式。

完成上述全部修改后重新编译内核,KernelSU 即可在非 GKI 设备上正常工作。需要注意,以上能力以 KernelSU v0.9.5 及更早版本为准,新版本已不再维护非 GKI 支持,本指南仅作历史存档与旧设备维护参考。

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

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/13 20:14:59

大模型与NLP技术演进:从Transformer到实践应用

1. 大模型与NLP技术演进全景 自然语言处理&#xff08;NLP&#xff09;领域正在经历从传统方法到大型语言模型&#xff08;LLM&#xff09;的范式转移。传统NLP技术依赖精心设计的特征工程和统计模型&#xff0c;如隐马尔可夫模型&#xff08;HMM&#xff09;和条件随机场&…

作者头像 李华
网站建设 2026/9/13 20:14:05

车规级CAN-LIN网关OTA升级实战:LIN从机刷写全链路解析

1. 项目概述&#xff1a;为什么一个车规级网关的OTA升级不能“随便刷”在汽车电子开发一线干了十多年&#xff0c;我经手过不下三十个ECU项目的刷写方案设计&#xff0c;从早期用CANoe手动发诊断请求、U盘拷贝bin文件到产线烧录&#xff0c;到如今要求整车上电后自动完成全链路…

作者头像 李华
网站建设 2026/9/13 20:11:28

MobaXterm 高效运维配置指南:SSH/SFTP/RDP/串口全场景实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/13 20:09:31

Rust 错误处理工程学:从 thiserror 到 anyhow 的分层落地

Rust 错误处理工程学&#xff1a;从 thiserror 到 anyhow 的分层落地在工业级 Rust 系统工程的演进中&#xff0c;“错误处理&#xff08;Error Handling&#xff09;”绝不仅仅是在每个函数后面加上一个 ? 操作符那么简单。 在很多中大型项目中&#xff0c;如果缺乏清晰的错误…

作者头像 李华