博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
kprobe原理与实现笔记
阅读量:7108 次
发布时间:2019-06-28

本文共 12577 字,大约阅读时间需要 41 分钟。

很久以前挖的坑, 现在还没填上, 也许以后再详细分析吧.

kprobe是内核提供的代码跟踪工具, 其使用方法见Documentation/kprobes.txt, 此处做个简要说明. kprobe允许你在任何内核程序位置动态打断并收集调试信息. 你可以在几乎热河内核代码地址陷入中断, 指定断点触发时的处理程序. 当前有三类探测方式: kprobes, jprobes与kretprobes(也叫return probes). 一个kprobe可以插入到内核任意指令里, 一个jprobe可以插入到内核函数的入口并提供方便的函数参数获取方式, 一个return probe在指定函数返回时使用.
通常情况下基于kprobe的调试手段被包装成一个内核模块. 模块初始化函数初始化(注册)一个或多个probes, 退出函数注销它们. 一个注册函数如register_kprobe()指定probe插入的位置及probe触发时的处理程序.
register_/unregister_*probes()函数集对应一组不同的*probes的注册与注销函数, 当你需要一次注销多个probes时这些函数加速注销处理.
1.1 kprobe原理
当一个kprobe注册后, 会复制监测地址的指令并将监测地址起始的单个(或多个)字节指令替换为一个中断指令(如x86平台的int3).
当CPU触发断点指令时, 一个中断会产生, CPU的寄存器被保存, 控制权通过notifier_call_chain转移到kprobes, kprobes执行pre_handler关联kprobe, 传递句柄, kprobe结构地址与寄存器.
接着kprobes单步运行它保存的指令拷贝(直接单步运行原始指令会更简单, 但kprobes可能会短暂的移除断点指令, 这会造成一个小的时间窗口当其它CPU也路过该probe点).
再单步执行指令后kprobes执行post_handler(如果kprobe有该回调句柄). 程序继续从probe点之后继续执行.

更多实现说明见Documentation/kprobes.txt.

demo代码见samples/kprobes/kprobe_example.c.

以下分析kprobes数据结构与源码, 仅分析kprobe部分, jprobe与kretprobe不额外详述, 毕竟这块代码还是比较简单的.

 
数据结构:

1 struct kprobe {  2     //哈希链表, 被静态全局变量kprobe_table管理, 每个被监测地址作为索引  3     //如果一个地址存在多个kprobe则该哈希节点会用aggregate节点替代  4     struct hlist_node hlist;  5     //对于一个地址存在的多个kprobe的链表  6     struct list_head list;  7     //因断点指令不能重入处理, 当多个kprobe一起触发时会放弃执行后面的probe, 同时该计数增加  8     unsigned long nmissed;  9     //观察点对应的地址, 用户在调用注册接口时可以指定地址, 也可以传入函数名让内核自己查找 10     kprobe_opcode_t *addr; 11     //观察点对应的函数名, 在注册kprobe时会将其翻译为十六进制地址并修改addr 12     const char *symbol_name; 13     //相对于入口点地址的偏移, 会在计算addr以后再加上offset得到最终的addr 14     unsigned int offset; 15     //在执行kprobe地址addr指令之前执行的handler 16     kprobe_pre_handler_t pre_handler; 17     //在执行kprobe地址addr指令之后执行的handler 18     kprobe_post_handler_t post_handler; 19     //异常处理句柄, 在执行pre_handler返回值非0时会调用 20     kprobe_fault_handler_t fault_handler; 21     /* 22      * ... called if breakpoint trap occurs in probe handler. 23      * Return 1 if it handled break, otherwise kernel will see it. 24      */ 25     kprobe_break_handler_t break_handler; 26     //保存的操作码, 当注册kprobe后对应地址会用中断指令替代 27     kprobe_opcode_t opcode; 28     //平台相关结构, 具体见下 29     struct arch_specific_insn ainsn; 30     //状态标记, 被kprobe_mutex保护 31     u32 flags; 32 }; 33 struct arch_specific_insn { 34     //回调处理的指令, 具体见arch_prepare_kprobe中的初始化 35     kprobe_opcode_t             *insn; 36     kprobe_insn_handler_t       *insn_handler; 37     //指令的条件检查(通过取指令高4位)回调 38     kprobe_check_cc             *insn_check_cc; 39     //单步调试回调 40     kprobe_insn_singlestep_t    *insn_singlestep; 41     kprobe_insn_fn_t            *insn_fn; 42 };

 

注册kprobe:

1 (kernel/kprobes.c)int __kprobes register_kprobe(struct kprobe *p)  2 {  3     //获取观察点在内核文件的地址  4     //注意传入的kprobe结构中symbol与addr有且只能有一个为有效值, 否则返回错误EINVAL  5     //如果传入的是函数名symbol则内核会查找符号表并翻译为指令地址, 查找不到也返回错误ENOENT  6     addr = kprobe_addr(p);  7     //判断观察点是否已注册, 判断方式是先根据addr索引kprobe_table查找是否该地址已被监测  8     //如存在节点再根据传入的kprobe指针p索引kprobe->list查找是否有与p相同的节点  9     //如已注册p则返回错误EINVAL 10     ret = check_kprobe_rereg(p); 11     //初始化p的成员, 注意对于flags成员用户只能传递一个标记KPROBE_FLAG_DISABLED 12     p->flags &= KPROBE_FLAG_DISABLED; 13     p->nmissed = 0; 14     INIT_LIST_HEAD(&p->list); 15     //校验kprobe地址是否安全, 观察点地址首先要满足以下要求, 不符合的一律返回EINVAL: 16     //1. 必须在内核代码段(内核本身或驱动模块代码) 17     //2. 不能是禁止kprobe的函数(函数声明带__kprobes属性或在kprobe_blacklist中的函数) 18     //3. 不能是与跳转相关的代码段(不太理解这个jump table是什么, 看定义不是在数据段里吗?) 19     //对于观察点地址落在驱动模块代码中的情况还需注意两点: 20     //1. 增加模块引用计数防止在更新模块代码时模块被意外卸载(在函数返回时释放) 21     //2. 不能是模块.init.text段 22     ret = check_kprobe_address_safe(p, &probed_mod); 23     mutex_lock(&kprobe_mutex); 24     //获取地址对应的kprobe对象, 如不存在即第一次设置该地址的观察点, 反之则注册kprobe集合 25     //register_aggr_kprobe首先判断old_p的pre_handler是否为aggr_pre_handler 26     //如不相等即该地址第二次注册, 使用alloc_aggr_kprobe/init_aggr_kprobe初始化一个集合节点 27     //如果集合节点标记为GONE还需重新准备架构相关代码(详见下文), 最后将新对象加入list链表 28     old_p = get_kprobe(p->addr); 29     if (old_p) { 30         ret = register_aggr_kprobe(old_p, p); 31         goto out; 32     } 33     //准备架构相关代码, 因当前config未定义KPROBES_ON_FTRACE 34     //prepare_kprobe实际调用arch_prepare_kprobe, 该函数具体分析见下文 35     mutex_lock(&text_mutex); 36     ret = prepare_kprobe(p); 37     mutex_unlock(&text_mutex); 38     //初始化哈希表节点 39     INIT_HLIST_NODE(&p->hlist); 40     hlist_add_head_rcu(&p->hlist, 41         &kprobe_table[hash_ptr(p->addr, KPROBE_HASH_BITS)]); 42     //将观察点地址的指令替换为未定义指令 43     //如在非Thumb态就使用KPROBE_ARM_BREAKPOINT_INSTRUCTION(0x07f001f8) 44     //替换后刷新icache, 之后执行到该指令时会进入中断(在init_kprobes中注册), 实现中断处理 45     if (!kprobes_all_disarmed && !kprobe_disabled(p)) 46         arm_kprobe(p); 47 } 48 (arch/arm/kernel/kprobes.c)int __kprobes arch_prepare_kprobe(struct kprobe *p) 49 { 50     //如在异常代码中直接返回 51     if (in_exception_text(addr)) 52         return -EINVAL; 53     //未定义THUMB2_KERNEL, 忽略相关代码 54     //因ARM RISC架构必定为4字节指令且不支持THUMB保证最低位为零, 所以此处判断低二位为零 55     //因不支持THUMB指令, decode_insn函数指针直接赋值为arm_kprobe_decode_insn 56     thumb = false; 57     if (addr & 0x3) 58         return -EINVAL; 59     insn = *p->addr; 60     decode_insn = arm_kprobe_decode_insn; 61     //保存操作码 62     p->opcode = insn; 63     p->ainsn.insn = tmp_insn; 64     //解码指令步骤, 因未定义THUMB2_KERNEL此处执行arm_kprobe_decode_insn 65     //首先初始化p->ainsn, 注意asi->insn是执行指令数组 66     //数组第一个成员是观察点指令的不带条件判断的部分(因条件处理在外部) 67     //数组第二个成员固定是0xe1a0f00e, 即mov pc, lr指令, 用来跳转回原接口 68     //指令模拟的重点在数组kprobe_decode_arm_table, 对照ARM手册逐个分析, 具体就不展开了 69     //最后返回该指令是否可kprobe, INSN_REJECTED即不可kprobe, 一般为修改程序状态的指令 70     //INSN_GOOD为需要额外slot, 则需额外分配地址有限制的内存, 详见get_insn_slot 71     //INSN_GOOD_NO_SLOT即无需内存的指令 72     switch ((*decode_insn)(insn, &p->ainsn)) { 73     case INSN_REJECTED: 74         return -EINVAL; 75     case INSN_GOOD: 76         p->ainsn.insn = get_insn_slot(); 77         if (!p->ainsn.insn) 78             return -ENOMEM; 79         for (is = 0; is < MAX_INSN_SIZE; ++is) 80             p->ainsn.insn[is] = tmp_insn[is]; 81         flush_insns(p->ainsn.insn, 82             sizeof(p->ainsn.insn[0]) * MAX_INSN_SIZE); 83         p->ainsn.insn_fn = (kprobe_insn_fn_t *) 84             ((uintptr_t)p->ainsn.insn | thumb); 85         break; 86     case INSN_GOOD_NO_SLOT: 87         p->ainsn.insn = NULL; 88         break; 89     } 90 }

 

初始化kprobe:

1 (kernel/kprobes.c)static int __init init_kprobes(void)  2 {  3     //初始化哈希表节点  4     for (i = 0; i < KPROBE_TABLE_SIZE; i++) {  5         INIT_HLIST_HEAD(&kprobe_table[i]);  6         INIT_HLIST_HEAD(&kretprobe_inst_table[i]);  7         raw_spin_lock_init(&(kretprobe_table_locks[i].lock));  8     }  9     //初始化kprobe黑名单(非__krpobe属性又不能被kprobe的函数) 10     for (kb = kprobe_blacklist; kb->name != NULL; kb++) { 11         kprobe_lookup_name(kb->name, addr); 12         if (!addr) 13             continue; 14         kb->start_addr = (unsigned long)addr; 15         symbol_name = kallsyms_lookup(kb->start_addr, 16             &size, &offset, &modname, namebuf); 17         if (!symbol_name) 18             kb->range = 0; 19         else 20             kb->range = size; 21     } 22     kprobes_all_disarmed = false; 23     //架构相关初始化, 调用两个函数arm_kprobe_decode_init与register_undef_hook 24     //前者主要是arm指令相关初始化, 其中find_str_pc_offset是计算str pc指令带来多少pc偏移 25     //因为ARM是三级流水线(取指译码执行), PC地址实际领先执行指令, str pc时就需要减去偏移 26     //如果ARM架构大于7统一是8, 否则就需要计算实际偏移, 具体计算方式很简单, 不详述 27     //剩下两个判断没仔细看, 反正大于v7的架构都是空定义 28     //register_undef_hook是核心, 它注册了上文的KPROBE_ARM_BREAKPOINT_INSTRUCTION异常中断 29     //其中undef_hook是针对所有未定义指令的hook的链表头 30     err = arch_init_kprobes(); 31     //下面两个notifier没仔细看, 不知道注册了有什么用, 先放放 32     if (!err) 33         err = register_die_notifier(&kprobe_exceptions_nb); 34     if (!err) 35         err = register_module_notifier(&kprobe_module_nb); 36 }

 

kprobe中断处理:

1 (arch/arm/kernel/traps.c)asmlinkage void __exception do_undefinstr(struct pt_regs *regs)   2 {   3     pc = (void __user *)instruction_pointer(regs);   4     //未定义THUMB2_KERNEL去除相关代码   5     if (processor_mode(regs) == SVC_MODE) {   6         instr = *(u32 *) pc;   7     } else if (thumb_mode(regs)) {   8         if (get_user(instr, (u16 __user *)pc))   9             goto die_sig;  10         if (is_wide_instruction(instr)) {  11             unsigned int instr2;  12             if (get_user(instr2, (u16 __user *)pc+1))  13                 goto die_sig;  14             instr <<= 16;  15             instr |= instr2;  16         }  17     } else if (get_user(instr, (u32 __user *)pc)) {  18         goto die_sig;  19     }  20     //遍历钩子链表找到符合的钩子调用回调处理, 此处即kprobe_trap_handler  21     if (call_undef_hook(regs, instr) == 0)  22         return;  23     info.si_signo = SIGILL;  24     info.si_errno = 0;  25     info.si_code  = ILL_ILLOPC;  26     info.si_addr  = pc;  27     arm_notify_die("Oops - undefined instruction", regs, &info, 0, 6);  28 }  29 (arch/arm/kernel/traps.c)static int __kprobes kprobe_trap_handler(struct pt_regs *regs, unsigned int instr)  30 {  31     //kprobe中断处理是时关中断的!  32     local_irq_save(flags);  33     kprobe_handler(regs);  34     local_irq_restore(flags);  35 }  36 (arch/arm/kernel/kprobes.c)void __kprobes kprobe_handler(struct pt_regs *regs)  37 {  38     //这里两个变量的定义都是对于每个CPU的, why?  39     kcb = get_kprobe_ctlblk();  40     cur = kprobe_running();  41     //未定义THUMB2_KERNEL去除相关代码  42     p = get_kprobe((kprobe_opcode_t *)regs->ARM_pc);  43     //获取pc地址所对应的kprobe对象, 如果p存在即存在kprobe  44     //如果p不存在但cur存在则可能对应存在jprobe(因为cur存在即有probe但又没有该地址的kprobe, 只可能是jprobe)  45     //否则probe已被移除, 什么都不做, 等退出后看当前地址指令是否会恢复  46     if (p) {  47         //kprobe存在又区分两种情况, 如果cur也存在即之前还有kprobe在执行  48         //则kprobe->nmissed增加, 正常运行断点指令后退出, 不执行pre_handler/post_handler  49         //否则即当前kprobe是唯一中断的kprobe, 先设置当前CPU的current_kprobe与状态  50         //然后先执行pre_handler, 根据其返回值(为0)正常运行或(为1)直接跳转异常处理句柄  51         //最后复位全局current_kprobe  52         if (cur) {  53             /* Kprobe is pending, so we're recursing. */  54             switch (kcb->kprobe_status) {  55             case KPROBE_HIT_ACTIVE:  56             case KPROBE_HIT_SSDONE:  57                 /* A pre- or post-handler probe got us here. */  58                 kprobes_inc_nmissed_count(p);  59                 save_previous_kprobe(kcb);  60                 set_current_kprobe(p);  61                 kcb->kprobe_status = KPROBE_REENTER;  62                 singlestep(p, regs, kcb);  63                 restore_previous_kprobe(kcb);  64                 break;  65             default:  66                 /* impossible cases */  67                 BUG();  68             }  69         } else if (p->ainsn.insn_check_cc(regs->ARM_cpsr)) {  70             /* Probe hit and conditional execution check ok. */  71             set_current_kprobe(p);  72             kcb->kprobe_status = KPROBE_HIT_ACTIVE;  73             /*  74              * If we have no pre-handler or it returned 0, we  75              * continue with normal processing.  If we have a  76              * pre-handler and it returned non-zero, it prepped  77              * for calling the break_handler below on re-entry,  78              * so get out doing nothing more here.  79              */  80             if (!p->pre_handler || !p->pre_handler(p, regs)) {  81                 kcb->kprobe_status = KPROBE_HIT_SS;  82                 singlestep(p, regs, kcb);  83                 if (p->post_handler) {  84                     kcb->kprobe_status = KPROBE_HIT_SSDONE;  85                     p->post_handler(p, regs, 0);  86                 }  87                 reset_current_kprobe();  88             }  89         } else {  90             /*  91              * Probe hit but conditional execution check failed,  92              * so just skip the instruction and continue as if  93              * nothing had happened.  94              */  95             singlestep_skip(p, regs);  96         }  97     } else if (cur) {  98         /* We probably hit a jprobe.  Call its break handler. */  99         if (cur->break_handler && cur->break_handler(cur, regs)) { 100             kcb->kprobe_status = KPROBE_HIT_SS; 101             singlestep(cur, regs, kcb); 102             if (cur->post_handler) { 103                 kcb->kprobe_status = KPROBE_HIT_SSDONE; 104                 cur->post_handler(cur, regs, 0); 105             } 106         } 107         reset_current_kprobe(); 108     } else { 109         /* 110          * The probe was removed and a race is in progress. 111          * There is nothing we can do about it.  Let's restart 112          * the instruction.  By the time we can restart, the 113          * real instruction will be there. 114          */ 115     } 116 }

 

转载于:https://www.cnblogs.com/Five100Miles/p/8645082.html

你可能感兴趣的文章
bzoj1655[Usaco2006 Jan] Dollar Dayz 奶牛商店*
查看>>
主机不能访问虚拟机web服务的问题
查看>>
20145220&20145209&20145309信息安全系统设计基础实验报告(5)
查看>>
初识django之orm布置与增删改查
查看>>
步入正轨——以客户的视角审视软件交付
查看>>
关于数组数据容易忽略的点
查看>>
小程序: 在同一个文件夹中配置多个页面
查看>>
NLPIR-Parser智能系统文本挖掘的“挖掘机”
查看>>
GitHub上传项目到远程库
查看>>
格式化字符串
查看>>
实验五 函数程序设计
查看>>
ubuntu下lnmp的安装
查看>>
高斯消元
查看>>
关于正则表达式的一些实际应用
查看>>
Windows 建立链接
查看>>
JavaScript进行DOM操作时的一点点小经验
查看>>
Android获取手机应用
查看>>
Weblogic12c安装与配置详解
查看>>
结对-英文词频检测-结对项目总结
查看>>
php随机生成汉字
查看>>