kprobes

来源:互联网 发布:大数据项目方案 编辑:程序博客网 时间:2024/04/30 15:02

kprobes在arch_init_kprobes中调用register_undef_hook将kprobes_arm_break_hook注册到undef_hook上。

 register_undef_hook(&kprobes_arm_break_hook);

当使用register_kprobe把kprobe结构体(含要监控的指令以及在该指令之前或之后要做的动作)注册时,用一个未定义指令替换要检测的指令,当执行到该未定义指令时,会执行do_undefinstr,进而执行call_undef_hook,kprobes_arm_break_hook->fn会被依次,进而执行kprobe_handler。

static struct undef_hook kprobes_arm_break_hook = {
.instr_mask = 0x0fffffff,
.instr_val = KPROBE_ARM_BREAKPOINT_INSTRUCTION,
.cpsr_mask = MODE_MASK,
.cpsr_val = SVC_MODE,
.fn = kprobe_trap_handler,
};
static int __kprobes kprobe_trap_handler(struct pt_regs *regs, unsigned int instr)
{
unsigned long flags;
local_irq_save(flags);
kprobe_handler(regs);
local_irq_restore(flags);
return 0;


1 拷贝探测的code , 插入特殊指令(ARM是插入未定义指令)

2 CPU运行到未定义指令,会产生trap, 进入ISR,并保存当前寄出去的状态

  通过LINUX的通知机制,会执行“pre_handler”(前提是你已经注册过了)

3 进入单步模式,运行你备份出来的代码

 (此代码运行的是拷贝出来的,防止别的CPU也恰巧运行到此位置)

4 单步模式后,运行“post_handler”,恢复正常模式,接着运行下面的指令。



1.  struct kprobe

struct kprobe {

       structhlist_node hlist;

 

       /*list of kprobes for multi-handler support */

       structlist_head list;

 

       /*countthe number of times this probe was temporarily disarmed */

       unsignedlong nmissed;

 

       /*location of the probe point */

       kprobe_opcode_t*addr;

 

       /*Allow user to indicate symbol name of the probe point */

       constchar *symbol_name;

 

       /*Offset into the symbol */

       unsignedint offset;

 

       /*Called before addr is executed. */

       kprobe_pre_handler_tpre_handler;

 

       /*Called after addr is executed, unless... */

       kprobe_post_handler_t post_handler;

 

       /*

        * ... called if executing addr causes a fault(eg. page fault).

        * Return 1 if it handled fault, otherwisekernel will see it.

        */

       kprobe_fault_handler_t fault_handler;

 

       /*

        * ... called if breakpoint trap occurs inprobe handler.

        * Return 1 if it handled break, otherwisekernel will see it.

        */

       kprobe_break_handler_tbreak_handler;

 

       /*Saved opcode (which has been replaced with breakpoint) */

       kprobe_opcode_topcode;

 

       /*copy of the original instruction */

       structarch_specific_insn ainsn;

 

       /*

        * Indicates various status flags.

        * Protected by kprobe_mutex after this kprobeis registered.

        */

       u32flags;

};

 

2.  ARM架构kprobe应用及实现分析(1.0 简单示例)

http://blog.csdn.net/liyongming1982/article/details/16362147

网络对krpobe的实现机制及扩展都不是特别详细

由于工作需要及个人爱好,正好有这个机会好好学习此模块及应用到实际中

并将整个应用扩展及当时的分析情况,详细记录下来,希望对感兴趣的人有些许帮助

最开始还是先给个具体的栗子:

参考: kernel/samples 下面有不少的例子

[cpp] view plain copy
  1. /* For each probe you need to allocate a kprobe structure */  
  2. static struct kprobe kp = {  
  3.     //.symbol_name  = "do_fork",  
  4.         .symbol_name    = "testAddadd5",  
  5. };  

当运行到监控的某个点的时候,会调用此函数

[cpp] view plain copy
  1. static int handler_pre(struct kprobe *p, struct pt_regs *regs)  
  2. {  
  3.         printk(" kprobes name is %s pt_regs size is %d \n",p->symbol_name,sizeof(regs->uregs));  
  4.         return 0;  
  5. }  

初始话,注册一个kprobe,可以传入函数名或者在内存的绝对地址(system.map)

[cpp] view plain copy
  1. static int __init kprobe_init(void)  
  2. {  
  3.     int ret;  
  4.     kp.pre_handler = handler_pre;  
  5.         ret = register_kprobe(&kp);  
  6.         printk(KERN_INFO " Planted kprobe at %p\n", kp.addr);  
  7.   
  8.     if (ret < 0) {  
  9.         printk(KERN_INFO " register_kprobe failed, returned %d\n", ret);  
  10.         return ret;  
  11.     }  
  12.     return 0;  
  13. }  

注销kprobe探测点

[cpp] view plain copy
  1. static void __exit kprobe_exit(void)  
  2. {  
  3.     unregister_kprobe(&kp);  
  4.     printk(KERN_INFO " kprobe at %p unregistered\n", kp.addr);  
  5. }  
  6. module_init(kprobe_init)  
  7. module_exit(kprobe_exit)  
  8. MODULE_LICENSE("GPL");  

 

system.map
//现在我们只关心 do_fork testAddadd5 这两个函数
它们在内存的地址如下:
c0052368 T testAddadd5
c00523c0 T do_fork


static struct kprobe kp = {
         //.symbol_name = "do_fork",
        .symbol_name = "testAddadd5",
};
register_kprobe(&kp)
printk(KERN_INFO " Planted kprobe at %p\n", kp.addr);
insmod
<6>[ 9749.442971] (0)[5251:insmod] Planted kprobe at c0052368
发现这里打印的地址与system.map是一致的。

3.  ARM架构kprobe应用及实现分析(2.0 register_kprobe error38)

最开始 register_kprobe 的时候,返回错误,一直注册不成功,且返回错误号为38

最后发现是一些kernel编译的配置没有打开导致的.

所以当你编译kernel之前请确保下面选项是打开支持的:

   general setup
        --> kprobes

CONFIG_OPTPROBES=y
CONFIG_PREEMPT=y
CONFIG_OPTPROBES=y
CONFIG_MODULE_UNLOAD=y
CONFIG_MODULES=y
CONFIG_KALLSYMS=y
CONFIG_KALLSYMS_ALL=y
CONFIG_DEBUG_INFO=yAnd one more config flag I needed specific to my platform:

你可以在system.map 文件找查找 是否有register_kprobe 函数,来确定你的编译是否正确。

4.  ARM架构kprobe应用及实现分析(3.0 被探测函数说明)

在此系列中都是探测 testAddadd5 ,作为分析入口,

我在do_fork 函数的最后的位置调用了testAddadd5 ,具体模样如下:

[cpp] view plain copy
  1. int   mykProbeCount = 0;  
  2. char * mytestbuf ="this is a mytestbuf";  
  3. int  testAddadd5(int a, int b,int c,char * buf,int d,int e,int f)  
  4. {  
  5.     int aa=1;  
  6.     int bb=2;  
  7.     int cc=3;  
  8.     mykProbeCount=mykProbeCount+5;  
  9.     aa = a+b;  
  10.     bb= c+d;  
  11.     cc==e+f;  
  12.     printk(" buf is %s   aa is %d bb is %d\n",buf,aa,bb);  
  13.     mykProbeCount++;  
  14.     return 0;  
  15. }  


此函数,很方便调试模拟,随便调用个shell 命令都会调用此函数

[cpp] view plain copy
  1. long do_fork(unsigned long clone_flags,  
  2.           unsigned long stack_start,  
  3.           struct pt_regs *regs,  
  4.           unsigned long stack_size,  
  5.           int __user *parent_tidptr,  
  6.           int __user *child_tidptr)  
  7. {  
  8.   int ii=0;  
  9.   ...  
  10.   printk("[%d:%s] fork fail:[0x%x, %d]\n", current->pid, current->comm, (unsigned int)p,(int) nr);  
  11.   ii=testAddadd5(0x11,0x22,0x33,mytestbuf,0x44,0x55,0x88);  
  12.   ii=ii+5;  
  13.   return nr;  
  14. }  

5.  ARM架构kprobe应用及实现分析(5.0打印寄存器的值)

kp.pre_handler = handler_pre;

在此函数中打印寄存器的值,才能对我们分析当时的情况有帮助。(如查看调用函数的参数值等)

[cpp] view plain copy
  1. static int handler_pre(struct kprobe *p, struct pt_regs *regs)  
  2. {  
  3.         printk(" kprobes name is %s pt_regs size is %d \n",p->symbol_name,sizeof(regs->uregs));  
  4.     return 0;  
  5. }  

下面是生产一个寄存器对应的索引及寄存器的名字(用途),具体可以参考ARM的相关资料

一共18个寄存器

struct pt_regs {
 long uregs[18];
};

下面很多地方也用到了此数组的生成方法(perror等),我也班门弄斧下:)

[cpp] view plain copy
  1. struct Pair {  
  2.   const char* msg;  
  3.   int code;  
  4. };  
  5. #define  _ARM_REG_DEF(x,y)  { #x, y },  
  6. struct Pair _arm_register_strings[] = {  
  7. _ARM_REG_DEF(ARM_cpsr           ,16)  
  8. _ARM_REG_DEF(ARM_pc     ,15)  
  9. _ARM_REG_DEF(ARM_lr     ,14)  
  10. _ARM_REG_DEF(ARM_sp     ,13)  
  11. _ARM_REG_DEF(ARM_ip     ,12)  
  12. _ARM_REG_DEF(ARM_fp     ,11)  
  13. _ARM_REG_DEF(ARM_r10        ,10)  
  14. _ARM_REG_DEF(ARM_r9     ,9)  
  15. _ARM_REG_DEF(ARM_r8     ,8)  
  16. _ARM_REG_DEF(ARM_r7     ,7)  
  17. _ARM_REG_DEF(ARM_r6     ,6)  
  18. _ARM_REG_DEF(ARM_r5     ,5)  
  19. _ARM_REG_DEF(ARM_r4     ,4)  
  20. _ARM_REG_DEF(ARM_r3     ,3)  
  21. _ARM_REG_DEF(ARM_r2     ,2)  
  22. _ARM_REG_DEF(ARM_r1     ,1)  
  23. _ARM_REG_DEF(ARM_r0         ,0)  
  24. _ARM_REG_DEF(ARM_ORIG_r0    ,17)  
  25. };  

dump出所有寄存器的值:

[cpp] view plain copy
  1. struct pt_regs regs;  
  2. char * name = NULL;  
  3. static int dump_arm_regs(unsigned long * buf)  
  4. {     
  5.    int i=0;  
  6.    int j=0;  
  7.    char * detail = NULL;  
  8.    for(i=0;i<18;i++)  
  9.    {  
  10.       for (j=0;j<sizeof(_arm_register_strings);j++)  
  11.       {  
  12.          if(_arm_register_strings[j].code==i)  
  13.          {  
  14.             detail = _arm_register_strings[j].msg;  
  15.             break;  
  16.          }  
  17.       }  
  18.       printk(" %s  %02d : 0x%08x  : %10d : %20s\n",name,i,*(buf+i),*(buf+i),detail);  
  19.    }  
  20.    return 0;  
  21. }  

最后handler_pre回调的时候,调用dump_arm_regs 到处所有寄存器的值,以便分析之用

[cpp] view plain copy
  1. static int handler_pre(struct kprobe *p, struct pt_regs *regs)  
  2. {  
  3.         printk(" kprobes name is %s pt_regs size is %d \n",p->symbol_name,sizeof(regs->uregs));  
  4.         dump_arm_regs(regs->uregs);  
  5.     return 0;  
  6. }  
[cpp] view plain copy
  1. 导出当时寄存器的值:  
  2. kprobes name is testAddadd5 pt_regs size is 72   
  3. testAddadd5  00 : 0x00000011  :         17 :               ARM_r0  
  4. testAddadd5  01 : 0x00000022  :         34 :               ARM_r1  
  5. testAddadd5  02 : 0x00000033  :         51 :               ARM_r2  
  6. testAddadd5  03 : 0xc077c670  : -1065892240 :               ARM_r3  
  7. testAddadd5  04 : 0xc4b6b000  : -994660352 :               ARM_r4  
  8. testAddadd5  05 : 0xdbe8e000  : -605495296 :               ARM_r5  
  9. testAddadd5  06 : 0x00000000  :          0 :               ARM_r6  
  10. testAddadd5  07 : 0x00001482  :       5250 :               ARM_r7  
  11. testAddadd5  08 : 0xdc38f000  : -600248320 :               ARM_r8  
  12. testAddadd5  09 : 0xdbe8e000  : -605495296 :               ARM_r9  
  13. testAddadd5  10 : 0x00000000  :          0 :               ARM_r9  
  14. testAddadd5  11 : 0xdbe8ff8c  : -605487220 :               ARM_fp  
  15. testAddadd5  12 : 0x00000088  :        136 :               ARM_ip  
  16. testAddadd5  13 : 0xdbe8ff30  : -605487312 :               ARM_sp  
  17. testAddadd5  14 : 0xc005253c  : -1073404612 :               ARM_lr  
  18. testAddadd5  15 : 0xc0052368  : -1073405080 :               ARM_pc  
  19. testAddadd5  16 : 0x60000013  : 1610612755 :             ARM_cpsr  
  20. testAddadd5  17 : 0xffffffff  :         -1 :          ARM_ORIG_r0  



ARM_pc == 0xc0052368
且system.map中: c0052368 T testAddadd5
两者相等与设想的一致

且 c0052538: ebffff8a  bl c0052368 <testAddadd5>
调用BL之后LR寄存器应该保存的是下一条指令的地址即:c005253c = c0052538 + 4 
testAddadd5  14 : 0xc005253c  : -1073404612 :               ARM_lr
上面两者也一致,证明这个时候导出的寄存器的值是可信的

R0 R1 R2 R3 保存参数:testAddadd5(0x11,0x22,0x33,mytestbuf
其它参数保存在堆栈中

好了,导出寄存器值就介绍到这里

6.  ARM架构kprobe应用及实现分析(6.0导出堆栈的值)

上篇讲过了导出寄存器的值

但是当函数参数多余4个的话(R0 R1 R2 R3 ),其他的值会保存在堆栈中,所以必须导出SP附近的值才能查看其它参数的值

此函数实现如下:

第一个参数为SP的值,第二个是一SP为中心要打印周围栈的数据

[cpp] view plain copy
  1. static int dump_arm_stack(unsigned int * _addr , unsigned int addrSize)  
  2. {     
  3.    int i=0;  
  4.    int j=0;  
  5.    int word_per_line=4;  
  6.    unsigned int * addr = _addr;  
  7.    addr = addr + word_per_line*addrSize;  
  8.   
  9.    for(i=0;i<addrSize*2;i++)  
  10.    {  
  11.          char * middlestack = "---";  
  12.          if(i==addrSize)  
  13.          {  
  14.             middlestack = "$$$";  
  15.          }  
  16.          printk(" addr:0x%08x %s 0x%08x 0x%08x 0x%08x 0x%08x \n", \  
  17.                                     (addr-(i*word_per_line+0)),\  
  18.                                     middlestack, \  
  19.                                     *(addr-(i*word_per_line+0)),\  
  20.                                     *(addr-(i*word_per_line+1)),\  
  21.                                     *(addr-(i*word_per_line+2)),\  
  22.                                     *(addr-(i*word_per_line+3)) \  
  23.                                     );  
  24.    }  
  25.    return 0;  
  26. }  

实际调用(regs->uregs[13] 即为stack pointer):

[cpp] view plain copy
  1. static int handler_pre(struct kprobe *p, struct pt_regs *regs)  
  2. {  
  3.         printk(" kprobes name is %s pt_regs size is %d \n",p->symbol_name,sizeof(regs->uregs));  
  4.         dump_arm_regs(regs->uregs);  
  5.         dump_arm_stack((unsigned int *)regs->uregs[13],5);  
  6.     return 0;  
  7. }  

当探测到实际printk输出如下:

//导出当时堆栈的值
<4>[ 9749.267927]-(0)[186:adbd] addr:0xdbe8ff80 --- 0xdbe8ffa4 0x00000000 0xdbe8e000 0xc000e0a4 
<4>[ 9749.269044]-(0)[186:adbd] addr:0xdbe8ff70 --- 0x00000002 0x00043d34 0x00042ff4 0x00000035 
<4>[ 9749.270161]-(0)[186:adbd] addr:0xdbe8ff60 --- 0x00000035 0x80045430 0x00000000 0xc8131300 
<4>[ 9749.271278]-(0)[186:adbd] addr:0xdbe8ff50 --- 0xdbe8ff7c 0x00000000 0x00000001 0x00000000 
<4>[ 9749.272395]-(0)[186:adbd] addr:0xdbe8ff40 --- 0x00000001 0xc063bd50 0x00000088 0x00000055 
<4>[ 9749.273512]-(0)[186:adbd] addr:0xdbe8ff30 $$$ 0x00000044 0xc00872a4 0xc00524fc 0xdbe8ff30   //这里是当时堆栈的中心
<4>[ 9749.274629]-(0)[186:adbd] addr:0xdbe8ff20 --- 0xdbe8ff8c 0x00000000 0xdbe8e000 0xdc38f000 
<4>[ 9749.275746]-(0)[186:adbd] addr:0xdbe8ff10 --- 0x00001482 0x00000000 0xdbe8e000 0xc4b6b000 
<4>[ 9749.276864]-(0)[186:adbd] addr:0xdbe8ff00 --- 0xc06309f4 0x60000013 0xdbe8ff1c 0xc063bd98 
<4>[ 9749.277981]-(0)[186:adbd] addr:0xdbe8fef0 --- 0xc00873c8 0xffffffff 0x60000013 0xc0052368

testAddadd5(0x11,0x22,0x33,mytestbuf,0x44,0x55,0x88);

可以看出上面printk输出红色部分与第5,6,7的传入的参数是一致的。

说明导出的stack的值是可信的。

7.  ARM架构kprobe应用及实现分析(7.0自动显示参数的值)

通过前面的介绍

知道参数在寄存器及堆栈的位置,我们就有可能显示参数的值

jprobe也可以显示参数的值,但是其有缺点:不能探测函数时加上偏移量

具体上下文请参考: ARM架构kprobe应用及实现分析(3.0 被探测函数说明)

导出参数的函数:

[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. static int dump_arm_parameter(struct pt_regs *regs)  
  2. {     
  3.    int i=0;  
  4.    unsigned int * sp = regs->uregs[13];  
  5.    printk(" func paras maybe : (0x%08x,0x%08x,0x%08x,0x%08x,0x%08x,0x%08x) \n",\  
  6.                                     regs->uregs[0],\  
  7.                                     regs->uregs[1],\  
  8.                                     regs->uregs[2],\  
  9.                                     regs->uregs[3],\  
  10.                                     *sp,\  
  11.                                     *(sp+1)\  
  12.                                     );  
  13.   
  14.    return 0;  
  15. }  

使用情形:

[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. static int handler_pre(struct kprobe *p, struct pt_regs *regs)  
  2. {  
  3.         printk("shitshit kprobes name is %s pt_regs size is %d \n",p->symbol_name,sizeof(regs->uregs));  
  4.         dump_arm_regs(regs->uregs);  
  5.         dump_arm_stack((unsigned int *)regs->uregs[13],5);  
  6.         dump_arm_parameter(regs);  
  7.     return 0;  
  8. }  

 

kernel log 输出如下:

func paras maybe : (0x00000011,0x00000022,0x00000033,0xc077c670,0x00000044,0x00000055)

与我们实际传入的一致

8.  ARM架构kprobe应用及实现分析(8.0 register_kprobe实现)

1.    

  1. int __kprobes register_kprobe(struct kprobe *p)  
  2. {  
  3.     int ret = 0;  
  4.     struct kprobe *old_p;  
  5.     struct module *probed_mod;  
  6.     kprobe_opcode_t *addr;  
  7.         //返回要探测的决定地址  
  8.     addr = kprobe_addr(p);  
  9.     if (IS_ERR(addr))  
  10.         return PTR_ERR(addr);  
  11.     p->addr = addr;  
  12.         // 检查此地址是否已经注册过,不能重复注册  
  13.     ret = check_kprobe_rereg(p);  
  14.     if (ret)  
  15.         return ret;  
  16.         //检查地址合法性  
  17.         //是否在代码段  
  18.         //是否在kprobes 本身的代码段中,呵呵,不能监守自盗  
  19.         //或者代码时候在黑名单中  
  20.         if (!kernel_text_address((unsigned long) p->addr) ||  
  21.         in_kprobes_functions((unsigned long) p->addr) ||  
  22.         ftrace_text_reserved(p->addr, p->addr) ||  
  23.         jump_label_text_reserved(p->addr, p->addr)) {  
  24.         ret = -EINVAL;  
  25.         goto cannot_probe;  
  26.     }  
  27.         // 平台相关注册(ARM)  
  28.         // 每种平台的break,exception,trap等指令不一样,单步时候的回调函数  
  29.     ret = arch_prepare_kprobe(p);  
  30.         // 把break指令写入到探测的地址中  
  31.     if (!kprobes_all_disarmed && !kprobe_disabled(p))  
  32.         __arm_kprobe(p);  
  33.         ......  
  34. }  


参考: kernel/Documentation/kprobes.txt 有如下一段话:
3. Specify either the kprobe "symbol_name" OR the "addr". If both are
specified, kprobe registration will fail with -EINVAL.

[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. static kprobe_opcode_t __kprobes *kprobe_addr(struct kprobe *p)  
  2. {  
  3.     kprobe_opcode_t *addr = p->addr;  
  4.         //假如同时设置函数名和函数地址的话,注册kprobe会失败  
  5.         //其实个人认为不太合理,尤其是支持KALLSYM的情况下  
  6.         //应该两个都都设置的话,应该以函数名的优先级高  
  7.     if ((p->symbol_name && p->addr) ||  
  8.         (!p->symbol_name && !p->addr))  
  9.         goto invalid;  
  10.     if (p->symbol_name) {  
  11.                 // 参考 kallsyms.c  
  12.                 // 输入函数名,返回函数的地址  
  13.         kprobe_lookup_name(p->symbol_name, addr);  
  14.         if (!addr)  
  15.             return ERR_PTR(-ENOENT);  
  16.     }  
  17.         //最终返回的地址,应该是函数地址加上指定的偏移量  
  18.     addr = (kprobe_opcode_t *)(((char *)addr) + p->offset);  
  19.     if (addr)  
  20.         return addr;  
  21. invalid:  
  22.     return ERR_PTR(-EINVAL);  
  23. }  
[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. // 在HASH数组中,得到该HASH值的表头  
  2. // 已函数地址为检测值,检查是否重复注册  
  3. struct kprobe __kprobes *get_kprobe(void *addr)  
  4. {  
  5.     struct hlist_head *head;  
  6.     struct hlist_node *node;  
  7.     struct kprobe *p;  
  8.   
  9.     head = &kprobe_table[hash_ptr(addr, KPROBE_HASH_BITS)];  
  10.     hlist_for_each_entry_rcu(p, node, head, hlist) {  
  11.         if (p->addr == addr)  
  12.             return p;  
  13.     }  
  14.     return NULL;  
  15. }  


大概查找形式如下图:(mm slab 等地方都用到了此算法)
唯一的缺点就是当HASH值比较大的时候,会占用比较多的内存
太小的话,算法的时间复杂度又退化成list

[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. // 判断地址是否在代码段  
  2. // 假如是数据段或者其它段的话,插入断点是毫无意义的,且会破坏数据  
  3. int core_kernel_text(unsigned long addr)  
  4. {  
  5.     if (addr >= (unsigned long)_stext &&  
  6.         addr <= (unsigned long)_etext)  
  7.         return 1;  
  8.   
  9.     if (system_state == SYSTEM_BOOTING &&  
  10.         init_kernel_text(addr))  
  11.         return 1;  
  12.     return 0;  
  13. }  


 

[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. //检测地址是否在kprobe自身中  
  2. static int __kprobes in_kprobes_functions(unsigned long addr)  
  3. {  
  4.     struct kprobe_blackpoint *kb;  
  5.   
  6.     if (addr >= (unsigned long)__kprobes_text_start &&  
  7.         addr < (unsigned long)__kprobes_text_end)  
  8.         return -EINVAL;  
  9.   
  10.     for (kb = kprobe_blacklist; kb->name != NULL; kb++) {  
  11.         if (kb->start_addr) {  
  12.             if (addr >= kb->start_addr &&  
  13.                 addr < (kb->start_addr + kb->range))  
  14.                 return -EINVAL;  
  15.         }  
  16.     }  
  17.     return 0;  
  18. }  

 

[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. // 检测地址在黑名单中,目前的黑名单如下:  
  2. static struct kprobe_blackpoint kprobe_blacklist[] = {  
  3.     {"preempt_schedule",},  
  4.     {"native_get_debugreg",},  
  5.     {"irq_entries_start",},  
  6.     {"common_interrupt",},  
  7.     {"mcount",},    /* mcount can be called from everywhere */  
  8.     {NULL}    /* Terminator */  
  9. };  
[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. //在函数init_kprobes 会初始化每个黑名单函数的起始地址及地址范围  
  2.     for (kb = kprobe_blacklist; kb->name != NULL; kb++) {  
  3.         kprobe_lookup_name(kb->name, addr);  
  4.         if (!addr)  
  5.             continue;  
  6.   
  7.         kb->start_addr = (unsigned long)addr;  
  8.         symbol_name = kallsyms_lookup(kb->start_addr,  
  9.                 &size, &offset, &modname, namebuf);  
  10.         if (!symbol_name)  
  11.             kb->range = 0;  
  12.         else  
  13.             kb->range = size;  
  14.     }  

9.  ARM架构kprobe应用及实现分析(9.0 arch_prepare_kprobe平台相关注册)

1.    

  1. // ARM 架构注册kprobe  
  2. int __kprobes arch_prepare_kprobe(struct kprobe *p)  
  3. {  
  4.     kprobe_opcode_t insn;  
  5.     kprobe_opcode_t tmp_insn[MAX_INSN_SIZE];  
  6.     unsigned long addr = (unsigned long)p->addr;  
  7.     bool thumb;  
  8.     kprobe_decode_insn_t *decode_insn;  
  9.     int is;  
  10.         // 检测地址是否在异常代码段中  
  11.     if (in_exception_text(addr))  
  12.         return -EINVAL;  
  13.         //地址应该为4的整数倍 ,呵呵我把CONFIG_THUMB2_KERNEL   
  14.         // 删除掉了,方便阅读,但是不影响原理分析  
  15.     if (addr & 0x3)  
  16.         return -EINVAL;  
  17.         // 取出探测点的汇编指令  
  18.     insn = *p->addr;  
  19.     decode_insn = arm_kprobe_decode_insn;  
  20.         //暂时保存探测指令,发生trap的时候执行  
  21.     p->opcode = insn;  
  22.     p->ainsn.insn = tmp_insn;  
  23.         ......  
  24.     return 0;  
  25. }  


 

[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1.  *   INSN_REJECTED     If instruction is one not allowed to kprobe,  
  2.  *   INSN_GOOD         If instruction is supported and uses instruction slot,  
  3.  *   INSN_GOOD_NO_SLOT If instruction is supported but doesn't use its slot.  
  4. // 判断监控的指令是什么类型,不是所有指令都可以监控的  
  5. enum kprobe_insn __kprobes  
  6. arm_kprobe_decode_insn(kprobe_opcode_t insn, struct arch_specific_insn *asi)  
  7. {  
  8.     asi->insn_singlestep = arm_singlestep;  
  9.     asi->insn_check_cc = kprobe_condition_checks[insn>>28];  
  10.     return kprobe_decode_insn(insn, asi, kprobe_decode_arm_table, false);  
  11. }  
  12. //单步执行,只是简单PC = PC + 4  
  13. static void __kprobes arm_singlestep(struct kprobe *p, struct pt_regs *regs)  
  14. {  
  15.     regs->ARM_pc += 4;  
  16.     p->ainsn.insn_handler(p, regs);  
  17. }  


 

[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. // 把break指令写入到探测的地址中  
  2. #define KPROBE_ARM_BREAKPOINT_INSTRUCTION   0x07f001f8  
  3. #define KPROBE_THUMB16_BREAKPOINT_INSTRUCTION   0xde18  
  4. #define KPROBE_THUMB32_BREAKPOINT_INSTRUCTION   0xf7f0a018  
  5. void __kprobes arch_arm_kprobe(struct kprobe *p)  
  6. {  
  7.     unsigned int brkp;  
  8.     void *addr;  
  9.     brkp = KPROBE_ARM_BREAKPOINT_INSTRUCTION;  
  10.     patch_text(addr, brkp);  
  11. }  
  12. void __kprobes __patch_text(void *addr, unsigned int insn)  
  13. {  
  14.     int size;  
  15.         insn = __opcode_to_mem_arm(insn);  
  16.         //往探测地址写入新的指令  
  17.     *(u32 *)addr = insn;  
  18.     size = sizeof(u32);  
  19.         // 把修改从cache真正写到内存中  
  20.     flush_icache_range((uintptr_t)(addr),  
  21.                (uintptr_t)(addr) + size);  

 

10.         ARM架构kprobe应用及实现分析(10 trap中断注册及回调)

首先可以看下探测点检测到非法指令时候,产生中断的dump_stack:

symbol<c0011df0>] (dump_backtrace+0x0/0x10c) from [<c0630370>] (dump_stack+0x18/0x1c)
symbol<c0630358>] (dump_stack+0x0/0x1c) from [<bf00c208>] (handler_pre+0x144/0x19c [kk])
symbol<bf00c0c4>] (handler_pre+0x0/0x19c [kk]) from [<c063d174>] (kprobe_handler+0x194/0x234)
symbol<c063cfe0>] (kprobe_handler+0x0/0x234) from [<c063d23c>] (kprobe_trap_handler+0x28/0x54)
symbol<c063d214>] (kprobe_trap_handler+0x0/0x54) from [<c00082ac>] (do_undefinstr+0x118/0x1b0)
symbol<c0008194>] (do_undefinstr+0x0/0x1b0) from [<c063ca68>] (__und_svc+0x48/0x60)
symbol<c00523e0>] (do_fork+0x0/0x464) from [<c0011aec>] (sys_clone+0x34/0x3c)
symbol<c0011ab8>] (sys_clone+0x0/0x3c) from [<c000df20>] (ret_fast_syscall+0x0/0x30)

[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. // 向中断列表注册一个中断回调函数  
  2. static struct undef_hook kprobes_arm_break_hook = {  
  3.     .instr_mask = 0x0fffffff,  
  4.     .instr_val  = KPROBE_ARM_BREAKPOINT_INSTRUCTION,  
  5.     .cpsr_mask  = MODE_MASK,  
  6.     .cpsr_val   = SVC_MODE,  
  7.     .fn     = kprobe_trap_handler,  
  8. };  


 

[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. // 目前只有 kgdb , ptrace , kprobe 注册过  
  2. // 初始化  
  3. int __init arch_init_kprobes()  
  4. {  
  5.     arm_kprobe_decode_init();  
  6.     register_undef_hook(&kprobes_arm_break_hook);  
  7.     return 0;  
  8. }  
  9. // 注册函数很简单,只是往list添加一个节点而已  
  10. void register_undef_hook(struct undef_hook *hook)  
  11. {  
  12.     unsigned long flags;  
  13.   
  14.     raw_spin_lock_irqsave(&undef_lock, flags);  
  15.     list_add(&hook->node, &undef_hook);  
  16.     raw_spin_unlock_irqrestore(&undef_lock, flags);  
  17. }  


 

[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. // 当然你也可以注销  
  2. void unregister_undef_hook(struct undef_hook *hook)  
  3. {  
  4.     unsigned long flags;  
  5.   
  6.     raw_spin_lock_irqsave(&undef_lock, flags);  
  7.     list_del(&hook->node);  
  8.     raw_spin_unlock_irqrestore(&undef_lock, flags);  
  9. }  


 

[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. //当CPU检测到没有定义的指令时候,ISR中会调用do_undefinstr:  
  2. asmlinkage void __exception do_undefinstr(struct pt_regs *regs)  
  3. {  
  4.     unsigned int instr;  
  5.     void __user *pc;  
  6.         //获取导致trap的指令  
  7.     regs->ARM_pc -= correction;  
  8.     pc = (void __user *)instruction_pointer(regs);  
  9.     instr = *(u32 *) pc;  
  10.         //回调  
  11.     if (call_undef_hook(regs, instr) == 0)  
  12.         goto do_undefinstr_exit;  
  13. }  


 

[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. // 查找列表,对已经注册的函数,进行回调  
  2. static int call_undef_hook(struct pt_regs *regs, unsigned int instr)  
  3. {  
  4.     struct undef_hook *hook;  
  5.     unsigned long flags;  
  6.     int (*fn)(struct pt_regs *regs, unsigned int instr) = NULL;  
  7.         // 遍历list,查看哪个注册的指令值与当前的一致  
  8.     list_for_each_entry(hook, &undef_hook, node)  
  9.                 // instr 与注册的要一致的  
  10.                 // kgdb , ptrace , kprobe 的都不一致  
  11.         if ((instr & hook->instr_mask) == hook->instr_val &&  
  12.             (regs->ARM_cpsr & hook->cpsr_mask) == hook->cpsr_val)  
  13.                          //回调函数  
  14.             fn = hook->fn;  
  15.   
  16.     return fn ? fn(regs, instr) : 1;  

11.         ARM架构kprobe应用及实现分析(11原理)



1 拷贝探测的code , 插入特殊指令(ARM是插入未定义指令)

2 CPU运行到未定义指令,会产生trap, 进入ISR,并保存当前寄出去的状态

  通过LINUX的通知机制,会执行“pre_handler”(前提是你已经注册过了)

3 进入单步模式,运行你备份出来的代码

 (此代码运行的是拷贝出来的,防止别的CPU也恰巧运行到此位置)

4 单步模式后,运行“post_handler”,恢复正常模式,接着运行下面的指令。

参考: kprobes.txt

How Does a Kprobe Work?

When a kprobe is registered, Kprobes makes a copy of the probed

instruction and replaces the first byte(s) of the probed instruction

with a breakpoint instruction (e.g., int3 on i386 and x86_64).

When a CPU hits the breakpoint instruction, a trap occurs, the CPU's

registers are saved, and control passes to Kprobes via the

notifier_call_chain mechanism.  Kprobes executes the "pre_handler"

associated with the kprobe, passing the handler the addresses of the

kprobe struct and the saved registers.

Next, Kprobes single-steps its copy of the probed instruction.

(It would be simpler to single-step the actual instruction in place,

but then Kprobes would have to temporarily remove the breakpoint

instruction.  This would open a small time window when another CPU

could sail right past the probepoint.)

After the instruction is single-stepped, Kprobes executes the

"post_handler," if any, that is associated with the kprobe.

Execution then continues with the instruction following the probepoint.

1 拷贝探测的code , 插入特殊指令(ARM是插入未定义指令)

2 CPU运行到未定义指令,会产生trap, 进入ISR,并保存当前寄出去的状态

  通过LINUX的通知机制,会执行“pre_handler”(前提是你已经注册过了)

3 进入单步模式,运行你备份出来的代码

 (此代码运行的是拷贝出来的,防止别的CPU也恰巧运行到此位置)

4 单步模式后,运行“post_handler”,恢复正常模式,接着运行下面的指令。

参考: kprobes.txt

How Does a Kprobe Work?

When a kprobe is registered, Kprobes makes a copy of the probed

instruction and replaces the first byte(s) of the probed instruction

with a breakpoint instruction (e.g., int3 on i386 and x86_64).

When a CPU hits the breakpoint instruction, a trap occurs, the CPU's

registers are saved, and control passes to Kprobes via the

notifier_call_chain mechanism.  Kprobes executes the "pre_handler"

associated with the kprobe, passing the handler the addresses of the

kprobe struct and the saved registers.

Next, Kprobes single-steps its copy of the probed instruction.

(It would be simpler to single-step the actual instruction in place,

but then Kprobes would have to temporarily remove the breakpoint

instruction.  This would open a small time window when another CPU

could sail right past the probepoint.)

After the instruction is single-stepped, Kprobes executes the

"post_handler," if any, that is associated with the kprobe.

Execution then continues with the instruction following the probepoint.


 
0 0