openEuler Kernel 特性解读 |
您所在的位置:网站首页 › openeuler社区只关注linuxkernel开发和优化 › openEuler Kernel 特性解读 |
什么是 livepatchLivepatch 即内核热补丁,通常在系统不可重启的情况下,用于修复内核以及内核模块的函数 bug。简单地说,livepatch 将待修复函数的开头几条指令替换为特定的跳转指令,让其跳转至修复函数中,这样该函数每次被调用,都会自动执行替换后的函数,达到修复函数的效果。 openEuler 上的 livepatch 与 linux 主线上的实现略有不同,主要是 openEuler 上采用的方法是直接修改指令,而 linux 主线上采用的方法是基于 ftrace 实现跳转。Linux 社区主线采用的 ftrace 方案,新函数运行前必须先跳转至ftrace_caller中,并且通过klp_ftrace_handler将 ip 值修改为新函数的地址,最后才能执行新函数的指令。整个流程相对来说比较冗长,每次运行都需要跳转至ftrace_caller中查找klp_ftrace_handler,效率较低。而 openEuler 上的方案,在运行时直接跳转至新函数,无需经过中转,效率较高。
livepatch 内部框架热补丁内部主要暴露的直接接口有klp_register_patch和klp_unregister_patch,分别用于注册和卸载热补丁。在热补丁 ko 的 init 函数和 exit 函数中会对应地进行调用。热补丁内部还有两个非直接暴露,但是可以通过命令调用的接口__klp_enable_patch和__klp_disable_patch,分别用于激活以及去使能热补丁。 如下图所示,热补丁注册过程中会执行的操作大致包含以下几个部分:符号重定向、jump_label 初始化以及 hook 函数执行。热补丁激活以及去使能过程中会执行栈检查以及指令替换的操作。当热补丁卸载时,需要执行 hook 函数以及资源回收。
如下图所示,在内核有一个全局链表管理所有的已经激活的 livepatch 函数,横向管理的数据结构是klp_func_node,表示不同的热补丁函数,此链表上的所有函数可以乱序地去使能(比如先 disable C 函数再 disable A 函数)。纵向管理的数据结构是klp_func,表示同一个函数的多个热补丁版本,此链表上的多个版本,只允许去使能最后一个(去使能后从链表摘除),并且生效的永远是链表上的最后一个(比如 A 函数激活了四个补丁,分别是 A1、A2、A3、A4,此时生效的是 A4,当去使能时,只能去使能 A4,并且去使能后 A4 从链表摘除,并且 A3 生效)。
livepatch 使用内核热补丁功能需要使能以下这些 config: CONFIG_HAVE_LIVEPATCH_WO_FTRACE=y CONFIG_LIVEPATCH=y CONFIG_LIVEPATCH_WO_FTRACE=y CONFIG_LIVEPATCH_STOP_MACHINE_CONSISTENCY=y CONFIG_LIVEPATCH_RESTRICT_KPROBE=y CONFIG_KALLSYMS=y CONFIG_KALLSYMS_ALL=y CONFIG_DEBUG_INFO=y制作热补丁 ko 有两种方式,第一种方式是通过编写模块代码的方式,可以参考内核源码的samples/livepatch/livepatch-sample.c文件。如下所示,编写的过程需要构造关键的数据结构,包括klp_func、klp_object、klp_patch。 static struct klp_func funcs[] = { { .old_name = "cmdline_proc_show", .new_func = livepatch_cmdline_proc_show, }, { } }; static struct klp_object objs[] = { { /* name being NULL means vmlinux */ .funcs = funcs, .hooks_load = hooks_load, .hooks_unload = hooks_unload, }, { } }; static struct klp_patch patch = { .mod = THIS_MODULE, .objs = objs, };然后就是热补丁函数,以及通过 init 函数调用klp_register_patch接口。 static int livepatch_cmdline_proc_show(struct seq_file *m, void *v) { seq_printf(m, "%s\n", "this has been live patched"); return 0; } static int livepatch_init(void) { return klp_register_patch(&patch); } static void livepatch_exit(void) { WARN_ON(klp_unregister_patch(&patch)); }第二种方式是通过 kpatch 工具来制作(可以参考https://gitee.com/src-openeuler/kpatch),大致的流程如下:①安装kpatch工具:yum install kpatch kpatch-runtime;②准备好一个修改函数内容的patch;③export NO_PROFILING_CALLS=1;④设置对应的ARCH和CROSS_COMPILE(如果是在本机使用则无需设置);⑤在kpatch-build目录下执行:./kpatch-build -s -v /vmlinux /xxxx.patch --skip-gcc-check -c /.config。 得到热补丁 ko 之后,使用比较简单,大致内容如下: • 插入内核:insmod xxx.ko• 激活热补丁:echo 1 > /sys/kernel/livepatch/xxx/enabled• 去使能热补丁:echo 0 > /sys/kernel/livepatch/xxx/enabled• 卸载 ko:rmmod xxx• 查询当前热补丁状态:cat /proc/livepatch/state livepatch 实例① 准备好 patch。可用git format-patch来生成:git format-patch -1。注意:生成 patch 后记得回退该补丁; --- a/kernel/cgroup/cgroup.c +++ b/kernel/cgroup/cgroup.c @@ -6204,6 +6204,7 @@ void cgroup_exit(struct task_struct *tsk) struct css_set *cset; int i; + printk("livepatch: out of cgroup\n"); spin_lock_irq(&css_set_lock); WARN_ON_ONCE(list_empty(&tsk->cg_list)); ② 使用 kpatch 工具制作生成 livepatch,-s 表示源码路径,-c 表示.config,-v 表示 vmlinux; ./kpatch-build -s /home/yeweihua/projects/hulk/hulk-5.10/ -c build/.config -v build/vmlinux --skip-gcc-check /home/yeweihua/projects/hulk/hulk-5.10/0001-test.patch③ 将生成的热补丁插入内核; insmod livepatch-0001-test.ko④ 将热补丁激活; echo 1 > /sys/kernel/livepatch/livepatch_0001_test/enabled⑤ 验证热补丁是否生效(cgroup_exit函数只要进程退出便会执行); /modules # ls [ 382.479847] livepatch: out of cgroup livepatch-0001-test.ko⑥ 去使能热补丁; echo 0 > /sys/kernel/livepatch/livepatch_0001_test/enabled⑦ 查询当前系统的热补丁状况; /modules # cat /proc/livepatch/state Index Patch State ---------------------------------------------------- 1 livepatch_0001_test disabled ----------------------------------------------------⑧ 卸载热补丁 rmmod livepatch-0001-test.ko(来源公众号:openEuler ) |
今日新闻 |
点击排行 |
|
推荐新闻 |
图片新闻 |
|
专题文章 |
CopyRight 2018-2019 实验室设备网 版权所有 win10的实时保护怎么永久关闭 |