kprobe原理与实现笔记
很久以前挖的坑, 现在还没填上, 也许以后再详细分析吧.
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 }