很久以前挖的坑, 現在還沒填上, 也許以后再詳細分析吧.
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 }