自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Linux 內(nèi)核動態(tài)追蹤技術的實現(xiàn)

系統(tǒng) Linux
kprobe 是內(nèi)核提供的動態(tài)追蹤技術機制,它允許動態(tài)安裝內(nèi)核模塊的方式安裝系統(tǒng)鉤子,非常強大。下面先看一個內(nèi)核中的例子。

[[434928]]

之前的文章介紹了基于 tracepoint 靜態(tài)追蹤技術的實現(xiàn),本文再介紹基于 kprobe 的動態(tài)追蹤即使的實現(xiàn)。同樣,動態(tài)追蹤也是排查問題的利器。

kprobe 是內(nèi)核提供的動態(tài)追蹤技術機制,它允許動態(tài)安裝內(nèi)核模塊的方式安裝系統(tǒng)鉤子,非常強大。下面先看一個內(nèi)核中的例子。

  1. #include <linux/kernel.h> 
  2. #include <linux/module.h> 
  3. #include <linux/kprobes.h> 
  4.  
  5. #define MAX_SYMBOL_LEN  64 
  6. // 要 hanck 的內(nèi)核函數(shù)名 
  7. static char symbol[MAX_SYMBOL_LEN] = "_do_fork"
  8. module_param_string(symbol, symbol, sizeof(symbol), 0644); 
  9. static struct kprobe kp = { 
  10.     .symbol_name    = symbol, 
  11. }; 
  12.  
  13. // 執(zhí)行系統(tǒng)函數(shù)前被執(zhí)行的鉤子 
  14. static int __kprobes handler_pre(struct kprobe *p, struct pt_regs *regs){ 
  15.     // ... 
  16.  
  17. // 執(zhí)行系統(tǒng)函數(shù)的單條指令后執(zhí)行的鉤子(不是執(zhí)行完系統(tǒng)函數(shù)) 
  18. static void __kprobes handler_post(struct kprobe *p, struct pt_regs *regs, 
  19.                 unsigned long flags){ 
  20.     // ... 
  21.  
  22. // 鉤子執(zhí)行出錯或者單條執(zhí)行執(zhí)行出錯時被執(zhí)行函數(shù)static int handler_fault(struct kprobe *p, struct pt_regs *regs, int trapnr){ 
  23.     // ... 
  24.  
  25. static int __init kprobe_init(void){ 
  26.     int ret; 
  27.     // 設置鉤子 
  28.     kp.pre_handler = handler_pre; 
  29.     kp.post_handler = handler_post; 
  30.     kp.fault_handler = handler_fault; 
  31.     // 安裝鉤子 
  32.     register_kprobe(&kp); 
  33.     return 0; 
  34.  
  35. static void __exit kprobe_exit(void){ 
  36.     unregister_kprobe(&kp); 
  37.     pr_info("kprobe at %p unregistered\n", kp.addr); 
  38.  
  39. // 安裝進內(nèi)核后的初始化和注銷函數(shù) 
  40. module_init(kprobe_init) 
  41. module_exit(kprobe_exit) 
  42. MODULE_LICENSE("GPL"); 

設置完 kprobe 后,通過 register_kprobe 注冊到內(nèi)核。

  1. int register_kprobe(struct kprobe *p){ 
  2.     int ret; 
  3.     struct kprobe *old_p; 
  4.     struct module *probed_mod; 
  5.     kprobe_opcode_t *addr; 
  6.  
  7.     // 通過系統(tǒng)函數(shù)名找到對應的地址,內(nèi)核維護了這個數(shù)據(jù) 
  8.     addr = kprobe_addr(p); 
  9.     // 記錄這個地址 
  10.     p->addr = addr; 
  11.     p->flags &= KPROBE_FLAG_DISABLED; 
  12.     p->nmissed = 0; 
  13.     INIT_LIST_HEAD(&p->list); 
  14.     // 之前是否已經(jīng)存在鉤子,是的話就插入存在的列表,否則插入一個新的記錄 
  15.     old_p = get_kprobe(p->addr); 
  16.     if (old_p) { 
  17.         /* Since this may unoptimize old_p, locking text_mutex. */ 
  18.         ret = register_aggr_kprobe(old_p, p); 
  19.         goto out
  20.     } 
  21.     // 把被 hack 的系統(tǒng)函數(shù)的指令保存到 probe 結(jié)構(gòu)體,因為下面要覆蓋這塊內(nèi)存 
  22.     /* 
  23.         prepare_kprobe => 
  24.             unsigned long addr = (unsigned long) p->addr; 
  25.             unsigned long *kprobe_addr = (unsigned long *)(addr & ~0xFULL); 
  26.             memcpy(&p->opcode, kprobe_addr, sizeof(kprobe_opcode_t)); 
  27.             memcpy(p->ainsn.insn, kprobe_addr, sizeof(kprobe_opcode_t)); 
  28.     */ 
  29.     ret = prepare_kprobe(p); 
  30.  
  31.     INIT_HLIST_NODE(&p->hlist); 
  32.     // 插入內(nèi)核維護的哈希表 
  33.     hlist_add_head_rcu(&p->hlist, 
  34.                &kprobe_table[hash_ptr(p->addr, KPROBE_HASH_BITS)]); 
  35.     // hack 掉系統(tǒng)函數(shù)所在內(nèi)存的內(nèi)容 
  36.     arm_kprobe(p); 

注冊一個 probe,首先是通過被 hack 的函數(shù)名找到對應的地址,然后保存這個地址對應內(nèi)存的信息,接著把 probe 插入哈希表,最后調(diào)用 arm_kprobe 函數(shù) hack 掉系統(tǒng)函數(shù)所在內(nèi)存的內(nèi)容??匆幌?arm_kprobe。

  1. void arch_arm_kprobe(struct kprobe *p){ 
  2.     // #define INT3_INSN_OPCODE 0xCC 
  3.     u8 int3 = INT3_INSN_OPCODE; 
  4.     // 把 int3 的內(nèi)存復制到 addr 
  5.     text_poke(p->addr, &int3, 1); 
  6.     text_poke_sync(); 
  7.     perf_event_text_poke(p->addr, &p->opcode, 1, &int3, 1); 

0xCC 是 intel 架構(gòu)下 int3 對應的指令。所以這里就是把被 hack 函數(shù)對應指令的前面部分改成 int3。完成 hack。當執(zhí)行到系統(tǒng)函數(shù)的時候,就會執(zhí)行 int3,從而觸發(fā) trap,并執(zhí)行對應的處理函數(shù) do_int3(這里比較復雜,我也沒有深入分析,大概是這個流程)。

  1. static bool do_int3(struct pt_regs *regs){ 
  2.     kprobe_int3_handler(regs);}int kprobe_int3_handler(struct pt_regs *regs){ 
  3.     kprobe_opcode_t *addr; 
  4.     struct kprobe *p; 
  5.     struct kprobe_ctlblk *kcb; 
  6.     addr = (kprobe_opcode_t *)(regs->ip - sizeof(kprobe_opcode_t)); 
  7.  
  8.     kcb = get_kprobe_ctlblk(); 
  9.     // 通過地址從 probe  哈希表拿到對應的 probe 結(jié)構(gòu)體 
  10.     p = get_kprobe(addr); 
  11.  
  12.     set_current_kprobe(p, regs, kcb); 
  13.     kcb->kprobe_status = KPROBE_HIT_ACTIVE; 
  14.  
  15.     // 執(zhí)行 pre_handler 鉤子  
  16.     if (!p->pre_handler || !p->pre_handler(p, regs)) 
  17.         setup_singlestep(p, regs, kcb, 0); 

執(zhí)行完。pre_handler 鉤子后,會通過 setup_singlestep 設置單步執(zhí)行 flag。

  1. static void setup_singlestep(struct kprobe *p, struct pt_regs *regs, 
  2.                  struct kprobe_ctlblk *kcb, int reenter){ 
  3.     // 修改寄存器的值 
  4.     // 設置 eflags 寄存器的 tf 位,允許單步調(diào)試 
  5.     regs->flags |= X86_EFLAGS_TF; 
  6.     regs->flags &= ~X86_EFLAGS_IF; 
  7.     // 設置下一條指令為系統(tǒng)函數(shù)的指令 
  8.     if (p->opcode == INT3_INSN_OPCODE) 
  9.         regs->ip = (unsigned long)p->addr; 
  10.     else 
  11.         regs->ip = (unsigned long)p->ainsn.insn; 

setup_singlestep 首先設置了允許單步調(diào)試,也就是說執(zhí)行下一條指令后會觸發(fā)一個 trap,從而執(zhí)行一個處理函數(shù)。并設置了下一條指令為被 hack 函數(shù)對應的指令,這是在注冊 probe 時保存下來的。觸發(fā)單步調(diào)試的 trap 后,最終會執(zhí)行到 kprobe_debug_handler

  1. int kprobe_debug_handler(struct pt_regs *regs){ 
  2.     struct kprobe *cur = kprobe_running(); 
  3.     struct kprobe_ctlblk *kcb = get_kprobe_ctlblk(); 
  4.     // 恢復指令為系統(tǒng)函數(shù)的指令 
  5.     resume_execution(cur, regs, kcb); 
  6.     regs->flags |= kcb->kprobe_saved_flags; 
  7.     // 執(zhí)行 post 鉤子 
  8.     if ((kcb->kprobe_status != KPROBE_REENTER) && cur->post_handler) { 
  9.         kcb->kprobe_status = KPROBE_HIT_SSDONE; 
  10.         cur->post_handler(cur, regs, 0); 
  11.     } 

在單步調(diào)試的 trap 處理函數(shù)中,會執(zhí)行 post 鉤子,并恢復真正的系統(tǒng)函數(shù)執(zhí)行。這就完成了整個過程。

我們可以看到 kprobe 可以在系統(tǒng)函數(shù)執(zhí)行前執(zhí)行我們的鉤子,另外內(nèi)核還提供了另外一個機制 kretprobe 用于在系統(tǒng)函數(shù)執(zhí)行后返回前安裝鉤子。下面通過一個例子大致看一下 kretprobe。

  1. struct my_data { 
  2.     ktime_t entry_stamp; 
  3. }; 
  4.  
  5. // 記錄函數(shù)執(zhí)行開始時間 
  6. static int entry_handler(struct kretprobe_instance *ri, struct pt_regs *regs){ 
  7.     struct my_data *data; 
  8.     data = (struct my_data *)ri->data; 
  9.     data->entry_stamp = ktime_get(); 
  10.     return 0; 
  11.  
  12. // 記錄函數(shù)執(zhí)行結(jié)束時間 
  13. static int ret_handler(struct kretprobe_instance *ri, struct pt_regs *regs){ 
  14.     unsigned long retval = regs_return_value(regs); 
  15.     struct my_data *data = (struct my_data *)ri->data; 
  16.     s64 delta; 
  17.     ktime_t now; 
  18.  
  19.     now = ktime_get(); 
  20.     delta = ktime_to_ns(ktime_sub(now, data->entry_stamp)); 
  21.     return 0; 
  22.  
  23. static struct kretprobe my_kretprobe = { 
  24.     // 函數(shù)返回前執(zhí)行 
  25.     .handler        = ret_handler, 
  26.     // 函數(shù)開始前執(zhí)行 
  27.     .entry_handler      = entry_handler, 
  28.     .data_size      = sizeof(struct my_data), 
  29.     /* Probe up to 20 instances concurrently. */ 
  30.     .maxactive      = 20, 
  31. }; 
  32.  
  33. static char func_name[NAME_MAX] = "_do_fork"
  34. module_param_string(func, func_name, NAME_MAX, S_IRUGO); 
  35. my_kretprobe.kp.symbol_name = func_name; 
  36. // 注冊 
  37. register_kretprobe(&my_kretprobe); 

我們可以看到可以通過 kretprobe 計算系統(tǒng)函數(shù)的耗時。kretprobe 是基于 kprobe 實現(xiàn)的,主要邏輯是通過通過 kprobe 注冊一個 pre_handler,在 pre_handler 中 hack 掉函數(shù)的棧,因為函數(shù)執(zhí)行時,返回地址是存在棧中的,把這個內(nèi)存改成一段內(nèi)核的代碼,等到函數(shù)執(zhí)行完后,彈出返回地址時,就會執(zhí)行內(nèi)核 hack 的代碼,從而執(zhí)行我們的鉤子,執(zhí)行完后再跳回到真正的返回地址繼續(xù)執(zhí)行。

 

總結(jié):內(nèi)核通過劫持的方式實現(xiàn)了 kprobe,基于 kprobe 的動態(tài)追蹤技術可謂是非常復雜而強大,我們可以利用這個機制,動態(tài)修改邏輯,收集信息。不過實現(xiàn)過于復雜,涉及到對 CPU 架構(gòu)和內(nèi)存模型的了解,本文也是大致分析了一下流程,有興趣的同學可以自行查看源碼。

 

責任編輯:武曉燕 來源: 編程雜技
相關推薦

2021-11-14 07:29:55

Linux 內(nèi)核靜態(tài)追蹤Linux 系統(tǒng)

2016-12-08 09:57:09

LinuxDTrace技術

2025-04-01 02:00:22

2025-04-02 00:33:00

2022-03-03 18:18:53

BPF解釋器系統(tǒng)

2021-10-06 09:46:17

trace-cmd追蹤內(nèi)核Linux

2025-03-07 08:30:00

pwruLinux網(wǎng)絡包追蹤

2023-03-01 23:56:11

2023-03-01 23:53:30

Linuxshutdown進程

2023-03-10 14:56:37

Linuxconnect系統(tǒng)

2025-01-02 11:06:22

2023-02-28 09:47:42

2014-04-01 16:52:10

SUSEkGraftLinux內(nèi)核

2021-09-30 09:43:11

Linux內(nèi)核Zstd補丁

2023-11-24 11:24:16

Linux系統(tǒng)

2024-04-15 11:24:32

庫存跟蹤技術NFC藍牙

2017-01-12 19:15:03

Linux內(nèi)核調(diào)試自構(gòu)proc

2022-05-24 12:34:32

Docker容器Linux容器進程

2020-11-13 18:33:38

跨鏡追蹤識別技術人臉識別

2018-06-04 16:20:56

Linux動態(tài)路由Quagga
點贊
收藏

51CTO技術棧公眾號