Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1566365
  • 博文数量: 77
  • 博客积分: 1205
  • 博客等级: 少尉
  • 技术积分: 4476
  • 用 户 组: 普通用户
  • 注册时间: 2010-04-22 21:48
文章分类
文章存档

2018年(1)

2017年(1)

2015年(1)

2014年(18)

2013年(12)

2012年(44)

分类: LINUX

2012-05-05 22:31:55

kprobe的意义除了通过kprobe跟踪do_execve函数的执行环境一文所说的调试功能之外,其实现源码当中还有很多值得一看的东西。比如通过一个给定的地址,判断该地 址来自内核还是加载的模块中,如果来自模块中,那么又如何得到该地址所在内核模块struct module对象指针mod。还有,kprobe的实现原来是在欲窥探的地方加入一个BREAKPOINT_INSTRUCTION指令,对于x86而 言,该指令的机器码是0xcc. 核心代码是text_poke函数,仔细研究这段代码也有很多有用的东西,比如vmalloc_to_page,virt_to_page以及如何在执行 0xcc之后再继续执行原先被覆盖的代码等等。。。
本文的侧重点是对kprobe背后的原理做一个简单的梳理,感兴趣的同学可以按照这个线索自己去看代码(我强烈建议仔细去看代码,因为里面可以挖掘到很多有价值的东西,特别是对那些在Linux系统下从事内核模块开发的同学)。

kprobe通过软中断int 3指令替换要窥探函数的开始处某条指令,达到插入自己的窥探函数的目的(这其实已经是调试中的软件断点功能的雏形了)。内核源码中关于这个int 3的调用链发生的时间先后顺序为:
1. arch/x86/kernel/traps.c --  __init early_trap_init() --> set_system_intr_gate_ist(3, &int3, DEBUG_STACK);

所以int 3指令执行时,要调用函数int3,后者的实现在:

2. arch/x86/kernel/entry_32.S:


  1. ENTRY(int3)
  2.         RING0_INT_FRAME
  3.         pushl_cfi $-1 # mark this as an int
  4.         SAVE_ALL
  5.         TRACE_IRQS_OFF
  6.         xorl %edx,%edx # zero error code
  7.         movl %esp,%eax # pt_regs pointer
  8.         call do_int3
  9.         jmp ret_from_exception
  10.         CFI_ENDPROC
  11. END(int3)
3. arch/x86/kernel/traps.c:

  1. dotraplinkage void __kprobes do_int3(struct pt_regs *regs, long error_code)
  2. {
  3.         ...
  4. #ifdef CONFIG_KPROBES
  5.         if (notify_die(DIE_INT3, "int3", regs, error_code, 3, SIGTRAP)
  6.                         == NOTIFY_STOP)
  7.                 return;
  8. #else
  9.         if (notify_die(DIE_TRAP, "int3", regs, error_code, 3, SIGTRAP)
  10.                         == NOTIFY_STOP)
  11.                 return;
  12. #endif

  13.         preempt_conditional_sti(regs);
  14.         do_trap(3, SIGTRAP, "int3", regs, error_code, NULL);
  15.         preempt_conditional_cli(regs);
  16. }
4. notify_die() --> atomic_notifier_call_chain() --> kprobe_exceptions_nb()   [注:init_kprobes()函数通过register_die_notifier向die_chain通知链注册回调函数 kprobe_exceptions_nb]
5. kprobe_exceptions_nb.notifier_call = kprobe_exceptions_notify, 所以kprobe_exceptions_notify被调用
6. kprobe_exceptions_notify() --> kprobe_handler() --> (struct kprobe *p)->pre_handler.

如果把中的init_kprobe_sample函数稍加改写,就可以验证kprobe的背后原理:

  1. static __init int init_kprobe_sample(void)
  2. {
  3.         kprobe_opcode_t *addr;
  4.         kp.symbol_name = "do_execve";
  5.         kp.pre_handler = handler_pre;
  6.         addr = (kprobe_opcode_t *)kallsyms_lookup_name(kp.symbol_name);
  7.         printk("addr = %p, op_code = 0x%lx\n", addr, (unsigned long)*addr);
  8.         register_kprobe(&kp);
  9.         printk("addr = %p, op_code = 0x%lx\n", addr, (unsigned long)*addr);
  10.         printk(KERN_INFO "---------------kprobe for execve--------------\n");
  11.         return 0;
  12. }
模块编译成功之后,通过dmesg查看,有如下输出:

  1. root@build-server:/home/dennis/debug/kprobe# dmesg -c
  2. [ 2172.049854] addr = ffffffff8119f5d0, op_code = 0x55
  3. [ 2172.050678] addr = ffffffff8119f5d0, op_code = 0xcc
可见,经过register_kprobe之后,原来的指令0x55已经被替换成了0xcc,在x86处理器中,0xcc 是int 0x3的指令码(op code),而0x55则是push   %rbp的指令码(详见intel 64 and IA-32 SW developer's manual V2B--OPCODE MAP),可以通过objdump工具来验证do_execve(0xffffffff8119f5d0)处的第一条指令:

  1. root@build-server:/home/dennis/Linux/linux-3.2.9# objdump -d vmlinux | grep ffffffff8119f5d0
  2. ffffffff810580c2: e8 09 75 14 00 callq ffffffff8119f5d0 <do_execve>
  3. ffffffff8119f5d0 <do_execve>:
  4. ffffffff8119f5d0: 55 push %rbp





阅读(13207) | 评论(1) | 转发(2) |
给主人留下些什么吧!~~

夏冰软件2012-05-07 16:35:32

写的非常的不错,我还是支持一下