动态替换内核函数
来源:互联网 发布:在日本护肤品档次 知乎 编辑:程序博客网 时间:2024/06/04 00:29
做这个东西源自一次面试,当时面试官仰天斜视45度,自以为很牛逼的说出了这个想法,我此奥,看了后很不爽,回来埋头一个月,做出第一个版本。已经于开源于github。
需求:在某些情况下,系统需要升级,但是不能重启,此时需要动态替换内核函数,进行动态升级。
本功能是基于2.6.38版本进行开发。 patch:git@github.com:cooboos/kxchg.git
解决思想很简单,就是利用trap指令进行函数替换(目前只支持x86架构)。下面就代码进行分析:
+struct kxchg+{+struct list_head list; //链表++const char *symbol_name;//被替换的函数名+unsigned long addr; //被替换的函数地址++const char *symbol_name_rep;//替换函数的函数名+unsigned long addr_rep; //替换函数的函数地址++int ori_ins; //被替换的原始指令的第一个字节(目前只做了x86架构的支持,int3指令为)+};+/*
将此patch到内核,编译,此时版本已经支持动态替换,对需要被替换的函数进行注册,下面举一个实例.--- kernel/kxchg.c(revision 0)+++ kernel/kxchg.c(revision 0)@@ -0,0 +1,183 @@+/*+ * <hack.coo@gmail.com>, 2012+ *+ * Released under the terms of the GNU GPL v2.0+ *+ * version 1.0 for Linux-2.6.38+ */+/*+ * trap int3 instruction+ */+#include <linux/kprobes.h>+#include <linux/hash.h>+#include <linux/init.h>+#include <linux/slab.h>+#include <linux/stddef.h>+#include <linux/module.h>+#include <linux/moduleloader.h>+#include <linux/kallsyms.h>+#include <linux/freezer.h>+#include <linux/seq_file.h>+#include <linux/debugfs.h>+#include <linux/kdebug.h>+#include <linux/kxchg.h>+#include <linux/notifier.h>+#include <linux/ptrace.h>++unsigned long kallsyms_lookup_name(const char *name);+extern void arch_trap_kxchg(struct kxchg *p);+extern void arch_saveins(struct kxchg *p);+extern int kxchg_notifier_process(struct notifier_block *self, unsigned long val, void *data);+struct kxchg_list kxchg_head;+struct kxchg_list kxchg_old;++static int kxchg_addr(struct kxchg *p)+{+if (p && p->symbol_name && p->symbol_name_rep) {++p->addr = kallsyms_lookup_name(p->symbol_name);+if(!p->addr)+return 1;++p->addr_rep = kallsyms_lookup_name(p->symbol_name_rep);+if(!p->addr_rep)+return 1;+}++return 0;+}++int get_kxchg(struct kxchg *kxchg)+{+int found = 0;+struct list_head *plist, *tmp;+struct kxchg *pkxchg = NULL;++//Fixme: rcu read lock+spin_lock(&kxchg_head.lock);+list_for_each_safe(plist, tmp, &kxchg_head.list) {+pkxchg = list_entry(plist, struct kxchg, list);+if(pkxchg->addr == kxchg->addr) {+/*+ * this symbol already resitered+ *one symbole is only registered once+ */+found = 1;+list_del(&pkxchg->list);+}+}+spin_unlock(&kxchg_head.lock);+//Fixme:rcu read unlock++if (found) {++//Fixme: rcu write lock+spin_lock(&kxchg_old.lock);+list_add(&pkxchg->list, &kxchg_old.list);+spin_unlock(&kxchg_old.lock);+//Fixme: rcu write unlock+}++return 0;+}+int kxchg_register(struct kxchg *ptr) //注册kxchg,+{+int ret;+/*+ * address the original symbol+ * and the replace symbol+ */+ret = kxchg_addr(ptr); //获取被替换函数和替换函数的地址。+if(ret) {++printk("kxchg get addr error!\n");+return 1;+}++printk("symbol [%s] address [%lx] new replace symbol_add[%lx]\n",ptr->symbol_name, ptr->addr, ptr->addr_rep);++ret = get_kxchg(ptr);+if(ret){+printk("old [%s] also exist\n",ptr->symbol_name);+return 1;+}++//Fixme: rcu write lock+spin_lock(&kxchg_head.lock);+list_add(&ptr->list, &kxchg_head.list); //将本次注册的替换函数加入链表+spin_unlock(&kxchg_head.lock);+//Fixme: rcu write unlock++/*+ *save the instruction the original symbol address pointed to+ */+printk("arch save ins...\n");+arch_saveins(ptr); //保存被替换指令的第一个字节+/*+ *replace the instruction by 0xcc,x86 for example;+ */+printk("arch trap kxchg...\n");+arch_trap_kxchg(ptr); //将被替换指令的第一个字节替换为trap指令+//sync_core();++return 0;+}+int kxchg_unregister(struct kxchg *kxchg) //unregister kxchg+{+int found = 0;+struct list_head *pos, *n;+struct kxchg *pos_k;++spin_lock(&kxchg_head.lock);+list_for_each_safe(pos, n, &kxchg_head.list){+pos_k = list_entry(pos, struct kxchg, list);+if(pos_k && !memcmp(pos_k->symbol_name, kxchg->symbol_name, 128)) {++memcpy((void *)kxchg->addr_rep, (void *)kxchg->ori_ins, 1);+list_del(pos);+found = 1;+break;+}+}+spin_lock(&kxchg_head.lock);+++if(!found) {+spin_lock(&kxchg_old.lock);+list_for_each_safe(pos, n, &kxchg_old.list){+pos_k = list_entry(pos, struct kxchg, list);+if(pos_k && !memcmp(pos_k->symbol_name, kxchg->symbol_name, 128)) {++memcpy((void *)kxchg->addr_rep, (void *)kxchg->ori_ins, 1);+list_del(pos);+break;+}+}+spin_unlock(&kxchg_old.lock);+}+++return 0;+}+struct notifier_block kxchg_notifier_block = { //注册事件通知函数+.notifier_call = kxchg_notifier_process,+.priority = 0x7fffffff+};++static int __init init_kxchg(void)+{+INIT_LIST_HEAD(&kxchg_head.list);+spin_lock_init(&kxchg_head.lock);++INIT_LIST_HEAD(&kxchg_old.list);+spin_lock_init(&kxchg_old.lock);++register_die_notifier(&kxchg_notifier_block);+return 0;+}++module_init(init_kxchg);++EXPORT_SYMBOL_GPL(kxchg_register);+EXPORT_SYMBOL_GPL(kxchg_unregister);+Index: arch/x86/kernel/kxchg.c===================================================================--- arch/x86/kernel/kxchg.c(revision 0)+++ arch/x86/kernel/kxchg.c(revision 0)@@ -0,0 +1,98 @@+/*+ * wangsongbo <hack.coo@gmail.com>, 2012+ *+ * Released under the terms of the GNU GPL v2.0+ *+ * version 1.0 for Linux-2.6.38+ */++#include <linux/ptrace.h>+#include <linux/kdebug.h>+#include <linux/kxchg.h>+#include <asm/kxchg.h>+#include <asm/kdebug.h>+#include <asm/ptrace.h>++extern struct kxchg_list kxchg_head;+int kxchg_notifier_process(struct notifier_block *self, unsigned long val, void *data)//事件通知函数+{+struct list_head *pos;+struct kxchg *kxchg;+struct die_args *args = (struct die_args *)data;+int ret = NOTIFY_DONE;++printk("kxchg_notifier_process...\n");++if (args->regs && user_mode_vm(args->regs))+return ret;+++switch(val){+case DIE_INT3: //响应trap事件通知+/*+ * the size of cc instruction is 1+ * ip--;+ */+printk("DIE_INT3...\n");+preempt_disable();++args->regs->ip = args->regs->ip - 1;+spin_lock(&kxchg_head.lock);+printk("list for each kxchg_head ...\n");//查看该trap指令是否是被注册的kxchg指令+list_for_each(pos, &kxchg_head.list){+kxchg = list_entry(pos, struct kxchg, list);+if(kxchg->addr == args->regs->ip){+/*+ * single step+ */+printk("[%s][%d] regs ip [%lx]\n",__FUNCTION__,__LINE__,args->regs->ip);+args->regs->flags |= TF_MASK;+args->regs->ip = kxchg->addr_rep; //将当前的ip指向替换的函数地址,进入替换函数++spin_unlock(&kxchg_head.lock);+return NOTIFY_STOP;+}+}+spin_unlock(&kxchg_head.lock);+/*+ * if the int3 instruction is not from kxchg_register, may be from other debuger+ * restore the original ip+ */+args->regs->ip = args->regs->ip + 1;+printk("no search regs ip [%lx]\n", args->regs->ip);+preempt_enable_no_resched();+return NOTIFY_DONE;+case DIE_DEBUG:+args->regs->flags &= ~TF_MASK; //响应debug指令+printk("DIE_DEBUG in\n");+break;+default:+printk("no val[%ld] process\n",val);+goto nokxchg;+}+return NOTIFY_STOP;+nokxchg:+return 0;+}++void arch_trap_kxchg(struct kxchg *p)+{+memcpy((void *)(p->addr), ((unsigned char []){INT3_INSTRUCTION}), 1);+//text_poke(p->addr, ((unsigned char []){BREAKPOINT_INSTRUCTION}), 1);+//text_poke((unsigned char [])(p->addr), ((unsigned char []){INT3_INSTRUCTION}), 1);++printk("[%s][%d] p addr [%lx]\n",__FILE__, __LINE__, p->addr);+return;+}++void arch_saveins(struct kxchg *p)+{+printk("[%s][%d] p addr [%lx]\n",__FILE__, __LINE__, p->addr);+memcpy((void *)&p->ori_ins, (void *)&p->addr, 1);+return;+}+void arch_restore_ins(struct kxchg *p)+{+memcpy(&(p->addr), (void *)&p->ori_ins, 1);+return;+}Index: arch/x86/kernel/traps.c===================================================================--- arch/x86/kernel/traps.c(revision 6603)+++ arch/x86/kernel/traps.c(working copy)@@ -461,6 +461,14 @@== NOTIFY_STOP)return;#endif /* CONFIG_KGDB_LOW_LEVEL_TRAP */++#define CONFIG_KXCHG+#ifdef CONFIG_KXCHG+if (notify_die(DIE_INT3, "int3", regs, error_code, 3, SIGTRAP) //单独注册int3通知函数+== NOTIFY_STOP)+return;+#endif+#ifdef CONFIG_KPROBESif (notify_die(DIE_INT3, "int3", regs, error_code, 3, SIGTRAP)== NOTIFY_STOP)
如果需要替换内核中的函数xxxx_kkkk, 替换函数函数名为xxxx_kkkk_test, 需定义xxxx_kkkk_test,该函数的参数,类型需要与xxxx_kkkk一致。以下:
+#include <linux/types.h>+#include <linux/init.h>+#include <linux/string.h>+#include <linux/in.h>+#include <linux/types.h>+#include <linux/list.h>+#include <linux/kxchg.h>+++static struct kxchg test;+extern int kxchg_register(struct kxchg *ptr);+extern int kxchg_unregister(struct kxchg *kxchg);++int netif_receiver_skb_test(struct sk_buff *skb) //+{+printk("netif_receive_skb replace ... ok");+kfree_skb(skb);+return 0;+}++int __init test_init(void)//加入需要替换netif_receiver_skb,这个例子不太合适,如果需要测试,请换一个函数,+{+memset(&test, 0x00, sizeof(test));++test.symbol_name="netif_receiver_skb";+test.symbol_name_rep="netif_receiver_skb_test";++kxchg_register(&test);+return 0;+}++void __exit test_exit(void)+{++kxchg_unregister(&test);+return;+}++module_init(test_init);+module_exit(test_exit);++MODULE_LICENSE("GPL");+MODULE_AUTHOR("cooboos");++
目前这个版本只实现了基本功能,还没进行多种情况下的测试。
- 动态替换内核函数
- Linux内核中 如何动态替换系统调用函数
- JNI_OnLoad实现动态函数替换
- 内核替换
- 内核的几个内存动态申请函数
- 动态替换Linux核心函数的原理和实现
- 动态替换Linux核心函数的原理和实现
- centos7编译替换内核
- Centos7编译替换内核
- 插件的“动态替换”
- 插件的“动态替换”
- 插件的“动态替换”
- spring bean 动态替换
- Android 动态替换桌面图标
- 替换内核的小企鹅
- linux内核配置,编译,替换
- Linux内核编译和替换
- ubuntu从内核源代码编译内核及替换内核
- 16亿美元超级计算机项目助力解读人脑
- VS2010 快捷键
- CodeSmith 使用教程(16) 使用XMLProperty
- 单片机以太网控制芯片W7100A数据手册(二)
- 高速串行总线
- 动态替换内核函数
- ubuntu基本操作 - ssh的使用
- 如何将sqlserver表中的数据导出sql语句或生成insert into语句
- CENTOS5.5 YUM安装SVN
- Oracle基于用户管理的不完全恢复
- UFIDA 简介
- Linux SSH 无密码访问设置详解
- 困扰nginx初学者的两个小问题
- U8 介绍