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.youkuaiyun.com/liyongming1982/article/details/16362147
网络对krpobe的实现机制及扩展都不是特别详细
由于工作需要及个人爱好,正好有这个机会好好学习此模块及应用到实际中
并将整个应用扩展及当时的分析情况,详细记录下来,希望对感兴趣的人有些许帮助
最开始还是先给个具体的栗子:
参考: kernel/samples 下面有不少的例子
-
- static struct kprobe kp = {
-
- .symbol_name = "testAddadd5",
- };
当运行到监控的某个点的时候,会调用此函数
- static int handler_pre(struct kprobe *p, struct pt_regs *regs)
- {
- printk(" kprobes name is %s pt_regs size is %d \n",p->symbol_name,sizeof(regs->uregs));
- return 0;
- }
初始话,注册一个kprobe,可以传入函数名或者在内存的绝对地址(system.map)
- static int __init kprobe_init(void)
- {
- int ret;
- kp.pre_handler = handler_pre;
- ret = register_kprobe(&kp);
- printk(KERN_INFO " Planted kprobe at %p\n", kp.addr);
-
- if (ret < 0) {
- printk(KERN_INFO " register_kprobe failed, returned %d\n", ret);
- return ret;
- }
- return 0;
- }
注销kprobe探测点
- static void __exit kprobe_exit(void)
- {
- unregister_kprobe(&kp);
- printk(KERN_INFO " kprobe at %p unregistered\n", kp.addr);
- }
- module_init(kprobe_init)
- module_exit(kprobe_exit)
- 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 ,具体模样如下:
- int mykProbeCount = 0;
- char * mytestbuf ="this is a mytestbuf";
- int testAddadd5(int a, int b,int c,char * buf,int d,int e,int f)
- {
- int aa=1;
- int bb=2;
- int cc=3;
- mykProbeCount=mykProbeCount+5;
- aa = a+b;
- bb= c+d;
- cc==e+f;
- printk(" buf is %s aa is %d bb is %d\n",buf,aa,bb);
- mykProbeCount++;
- return 0;
- }
此函数,很方便调试模拟,随便调用个shell 命令都会调用此函数
- long do_fork(unsigned long clone_flags,
- unsigned long stack_start,
- struct pt_regs *regs,
- unsigned long stack_size,
- int __user *parent_tidptr,
- int __user *child_tidptr)
- {
- int ii=0;
- ...
- printk("[%d:%s] fork fail:[0x%x, %d]\n", current->pid, current->comm, (unsigned int)p,(int) nr);
- ii=testAddadd5(0x11,0x22,0x33,mytestbuf,0x44,0x55,0x88);
- ii=ii+5;
- return nr;
- }
kp.pre_handler = handler_pre;
在此函数中打印寄存器的值,才能对我们分析当时的情况有帮助。(如查看调用函数的参数值等)
- static int handler_pre(struct kprobe *p, struct pt_regs *regs)
- {
- printk(" kprobes name is %s pt_regs size is %d \n",p->symbol_name,sizeof(regs->uregs));
- return 0;
- }
下面是生产一个寄存器对应的索引及寄存器的名字(用途),具体可以参考ARM的相关资料

一共18个寄存器
struct pt_regs {
long uregs[18];
};
下面很多地方也用到了此数组的生成方法(perror等),我也班门弄斧下:)
- struct Pair {
- const char* msg;
- int code;
- };
- #define _ARM_REG_DEF(x,y) { #x, y },
- struct Pair _arm_register_strings[] = {
- _ARM_REG_DEF(ARM_cpsr ,16)
- _ARM_REG_DEF(ARM_pc ,15)
- _ARM_REG_DEF(ARM_lr ,14)
- _ARM_REG_DEF(ARM_sp ,13)
- _ARM_REG_DEF(ARM_ip ,12)
- _ARM_REG_DEF(ARM_fp ,11)
- _ARM_REG_DEF(ARM_r10 ,10)
- _ARM_REG_DEF(ARM_r9 ,9)
- _ARM_REG_DEF(ARM_r8 ,8)
- _ARM_REG_DEF(ARM_r7 ,7)
- _ARM_REG_DEF(ARM_r6 ,6)
- _ARM_REG_DEF(ARM_r5 ,5)
- _ARM_REG_DEF(ARM_r4 ,4)
- _ARM_REG_DEF(ARM_r3 ,3)
- _ARM_REG_DEF(ARM_r2 ,2)
- _ARM_REG_DEF(ARM_r1 ,1)
- _ARM_REG_DEF(ARM_r0 ,0)
- _ARM_REG_DEF(ARM_ORIG_r0 ,17)
- };
dump出所有寄存器的值:
- struct pt_regs regs;
- char * name = NULL;
- static int dump_arm_regs(unsigned long * buf)
- {
- int i=0;
- int j=0;
- char * detail = NULL;
- for(i=0;i<18;i++)
- {
- for (j=0;j<sizeof(_arm_register_strings);j++)
- {
- if(_arm_register_strings[j].code==i)
- {
- detail = _arm_register_strings[j].msg;
- break;
- }
- }
- printk(" %s %02d : 0x%08x : %10d : %20s\n",name,i,*(buf+i),*(buf+i),detail);
- }
- return 0;
- }
最后handler_pre回调的时候,调用dump_arm_regs 到处所有寄存器的值,以便分析之用
- static int handler_pre(struct kprobe *p, struct pt_regs *regs)
- {
- printk(" kprobes name is %s pt_regs size is %d \n",p->symbol_name,sizeof(regs->uregs));
- dump_arm_regs(regs->uregs);
- return 0;
- }
- 导出当时寄存器的值:
- kprobes name is testAddadd5 pt_regs size is 72
- testAddadd5 00 : 0x00000011 : 17 : ARM_r0
- testAddadd5 01 : 0x00000022 : 34 : ARM_r1
- testAddadd5 02 : 0x00000033 : 51 : ARM_r2
- testAddadd5 03 : 0xc077c670 : -1065892240 : ARM_r3
- testAddadd5 04 : 0xc4b6b000 : -994660352 : ARM_r4
- testAddadd5 05 : 0xdbe8e000 : -605495296 : ARM_r5
- testAddadd5 06 : 0x00000000 : 0 : ARM_r6
- testAddadd5 07 : 0x00001482 : 5250 : ARM_r7
- testAddadd5 08 : 0xdc38f000 : -600248320 : ARM_r8
- testAddadd5 09 : 0xdbe8e000 : -605495296 : ARM_r9
- testAddadd5 10 : 0x00000000 : 0 : ARM_r9
- testAddadd5 11 : 0xdbe8ff8c : -605487220 : ARM_fp
- testAddadd5 12 : 0x00000088 : 136 : ARM_ip
- testAddadd5 13 : 0xdbe8ff30 : -605487312 : ARM_sp
- testAddadd5 14 : 0xc005253c : -1073404612 : ARM_lr
- testAddadd5 15 : 0xc0052368 : -1073405080 : ARM_pc
- testAddadd5 16 : 0x60000013 : 1610612755 : ARM_cpsr
- 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
其它参数保存在堆栈中
好了,导出寄存器值就介绍到这里
上篇讲过了导出寄存器的值
但是当函数参数多余4个的话(R0 R1 R2 R3 ),其他的值会保存在堆栈中,所以必须导出SP附近的值才能查看其它参数的值
此函数实现如下:
第一个参数为SP的值,第二个是一SP为中心要打印周围栈的数据
- static int dump_arm_stack(unsigned int * _addr , unsigned int addrSize)
- {
- int i=0;
- int j=0;
- int word_per_line=4;
- unsigned int * addr = _addr;
- addr = addr + word_per_line*addrSize;
-
- for(i=0;i<addrSize*2;i++)
- {
- char * middlestack = "---";
- if(i==addrSize)
- {
- middlestack = "$$$";
- }
- printk(" addr:0x%08x %s 0x%08x 0x%08x 0x%08x 0x%08x \n", \
- (addr-(i*word_per_line+0)),\
- middlestack, \
- *(addr-(i*word_per_line+0)),\
- *(addr-(i*word_per_line+1)),\
- *(addr-(i*word_per_line+2)),\
- *(addr-(i*word_per_line+3)) \
- );
- }
- return 0;
- }
实际调用(regs->uregs[13] 即为stack pointer):
- static int handler_pre(struct kprobe *p, struct pt_regs *regs)
- {
- printk(" kprobes name is %s pt_regs size is %d \n",p->symbol_name,sizeof(regs->uregs));
- dump_arm_regs(regs->uregs);
- dump_arm_stack((unsigned int *)regs->uregs[13],5);
- return 0;
- }
当探测到实际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的值是可信的。
通过前面的介绍
知道参数在寄存器及堆栈的位置,我们就有可能显示参数的值
jprobe也可以显示参数的值,但是其有缺点:不能探测函数时加上偏移量
具体上下文请参考: ARM架构kprobe应用及实现分析(3.0 被探测函数说明)
导出参数的函数:
- static int dump_arm_parameter(struct pt_regs *regs)
- {
- int i=0;
- unsigned int * sp = regs->uregs[13];
- printk(" func paras maybe : (0x%08x,0x%08x,0x%08x,0x%08x,0x%08x,0x%08x) \n",\
- regs->uregs[0],\
- regs->uregs[1],\
- regs->uregs[2],\
- regs->uregs[3],\
- *sp,\
- *(sp+1)\
- );
-
- return 0;
- }
使用情形:
- static int handler_pre(struct kprobe *p, struct pt_regs *regs)
- {
- printk("shitshit kprobes name is %s pt_regs size is %d \n",p->symbol_name,sizeof(regs->uregs));
- dump_arm_regs(regs->uregs);
- dump_arm_stack((unsigned int *)regs->uregs[13],5);
- dump_arm_parameter(regs);
- return 0;
- }
kernel log 输出如下:
func paras maybe : (0x00000011,0x00000022,0x00000033,0xc077c670,0x00000044,0x00000055)
与我们实际传入的一致
- int __kprobes register_kprobe(struct kprobe *p)
- {
- int ret = 0;
- struct kprobe *old_p;
- struct module *probed_mod;
- kprobe_opcode_t *addr;
-
- addr = kprobe_addr(p);
- if (IS_ERR(addr))
- return PTR_ERR(addr);
- p->addr = addr;
-
- ret = check_kprobe_rereg(p);
- if (ret)
- return ret;
-
-
-
-
- if (!kernel_text_address((unsigned long) p->addr) ||
- in_kprobes_functions((unsigned long) p->addr) ||
- ftrace_text_reserved(p->addr, p->addr) ||
- jump_label_text_reserved(p->addr, p->addr)) {
- ret = -EINVAL;
- goto cannot_probe;
- }
-
-
- ret = arch_prepare_kprobe(p);
-
- if (!kprobes_all_disarmed && !kprobe_disabled(p))
- __arm_kprobe(p);
- ......
- }
参考: kernel/Documentation/kprobes.txt 有如下一段话:
3. Specify either the kprobe "symbol_name" OR the "addr". If both are
specified, kprobe registration will fail with -EINVAL.
- static kprobe_opcode_t __kprobes *kprobe_addr(struct kprobe *p)
- {
- kprobe_opcode_t *addr = p->addr;
-
-
-
- if ((p->symbol_name && p->addr) ||
- (!p->symbol_name && !p->addr))
- goto invalid;
- if (p->symbol_name) {
-
-
- kprobe_lookup_name(p->symbol_name, addr);
- if (!addr)
- return ERR_PTR(-ENOENT);
- }
-
- addr = (kprobe_opcode_t *)(((char *)addr) + p->offset);
- if (addr)
- return addr;
- invalid:
- return ERR_PTR(-EINVAL);
- }
-
-
- struct kprobe __kprobes *get_kprobe(void *addr)
- {
- struct hlist_head *head;
- struct hlist_node *node;
- struct kprobe *p;
-
- head = &kprobe_table[hash_ptr(addr, KPROBE_HASH_BITS)];
- hlist_for_each_entry_rcu(p, node, head, hlist) {
- if (p->addr == addr)
- return p;
- }
- return NULL;
- }
大概查找形式如下图:(mm slab 等地方都用到了此算法)
唯一的缺点就是当HASH值比较大的时候,会占用比较多的内存
太小的话,算法的时间复杂度又退化成list
-
-
- int core_kernel_text(unsigned long addr)
- {
- if (addr >= (unsigned long)_stext &&
- addr <= (unsigned long)_etext)
- return 1;
-
- if (system_state == SYSTEM_BOOTING &&
- init_kernel_text(addr))
- return 1;
- return 0;
- }
-
- static int __kprobes in_kprobes_functions(unsigned long addr)
- {
- struct kprobe_blackpoint *kb;
-
- if (addr >= (unsigned long)__kprobes_text_start &&
- addr < (unsigned long)__kprobes_text_end)
- return -EINVAL;
-
- for (kb = kprobe_blacklist; kb->name != NULL; kb++) {
- if (kb->start_addr) {
- if (addr >= kb->start_addr &&
- addr < (kb->start_addr + kb->range))
- return -EINVAL;
- }
- }
- return 0;
- }
-
- static struct kprobe_blackpoint kprobe_blacklist[] = {
- {"preempt_schedule",},
- {"native_get_debugreg",},
- {"irq_entries_start",},
- {"common_interrupt",},
- {"mcount",},
- {NULL}
- };
-
- for (kb = kprobe_blacklist; kb->name != NULL; kb++) {
- kprobe_lookup_name(kb->name, addr);
- if (!addr)
- continue;
-
- kb->start_addr = (unsigned long)addr;
- symbol_name = kallsyms_lookup(kb->start_addr,
- &size, &offset, &modname, namebuf);
- if (!symbol_name)
- kb->range = 0;
- else
- kb->range = size;
- }
-
- int __kprobes arch_prepare_kprobe(struct kprobe *p)
- {
- kprobe_opcode_t insn;
- kprobe_opcode_t tmp_insn[MAX_INSN_SIZE];
- unsigned long addr = (unsigned long)p->addr;
- bool thumb;
- kprobe_decode_insn_t *decode_insn;
- int is;
-
- if (in_exception_text(addr))
- return -EINVAL;
-
-
- if (addr & 0x3)
- return -EINVAL;
-
- insn = *p->addr;
- decode_insn = arm_kprobe_decode_insn;
-
- p->opcode = insn;
- p->ainsn.insn = tmp_insn;
- ......
- return 0;
- }
- * INSN_REJECTED If instruction is one not allowed to kprobe,
- * INSN_GOOD If instruction is supported and uses instruction slot,
- * INSN_GOOD_NO_SLOT If instruction is supported but doesn't use its slot.
-
- enum kprobe_insn __kprobes
- arm_kprobe_decode_insn(kprobe_opcode_t insn, struct arch_specific_insn *asi)
- {
- asi->insn_singlestep = arm_singlestep;
- asi->insn_check_cc = kprobe_condition_checks[insn>>28];
- return kprobe_decode_insn(insn, asi, kprobe_decode_arm_table, false);
- }
-
- static void __kprobes arm_singlestep(struct kprobe *p, struct pt_regs *regs)
- {
- regs->ARM_pc += 4;
- p->ainsn.insn_handler(p, regs);
- }
-
- #define KPROBE_ARM_BREAKPOINT_INSTRUCTION 0x07f001f8
- #define KPROBE_THUMB16_BREAKPOINT_INSTRUCTION 0xde18
- #define KPROBE_THUMB32_BREAKPOINT_INSTRUCTION 0xf7f0a018
- void __kprobes arch_arm_kprobe(struct kprobe *p)
- {
- unsigned int brkp;
- void *addr;
- brkp = KPROBE_ARM_BREAKPOINT_INSTRUCTION;
- patch_text(addr, brkp);
- }
- void __kprobes __patch_text(void *addr, unsigned int insn)
- {
- int size;
- insn = __opcode_to_mem_arm(insn);
-
- *(u32 *)addr = insn;
- size = sizeof(u32);
-
- flush_icache_range((uintptr_t)(addr),
- (uintptr_t)(addr) + size);
- }
首先可以看下探测点检测到非法指令时候,产生中断的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)
-
- 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,
- };
-
-
- int __init arch_init_kprobes()
- {
- arm_kprobe_decode_init();
- register_undef_hook(&kprobes_arm_break_hook);
- return 0;
- }
-
- void register_undef_hook(struct undef_hook *hook)
- {
- unsigned long flags;
-
- raw_spin_lock_irqsave(&undef_lock, flags);
- list_add(&hook->node, &undef_hook);
- raw_spin_unlock_irqrestore(&undef_lock, flags);
- }
-
- void unregister_undef_hook(struct undef_hook *hook)
- {
- unsigned long flags;
-
- raw_spin_lock_irqsave(&undef_lock, flags);
- list_del(&hook->node);
- raw_spin_unlock_irqrestore(&undef_lock, flags);
- }
-
- asmlinkage void __exception do_undefinstr(struct pt_regs *regs)
- {
- unsigned int instr;
- void __user *pc;
-
- regs->ARM_pc -= correction;
- pc = (void __user *)instruction_pointer(regs);
- instr = *(u32 *) pc;
-
- if (call_undef_hook(regs, instr) == 0)
- goto do_undefinstr_exit;
- }
-
- static int call_undef_hook(struct pt_regs *regs, unsigned int instr)
- {
- struct undef_hook *hook;
- unsigned long flags;
- int (*fn)(struct pt_regs *regs, unsigned int instr) = NULL;
-
- list_for_each_entry(hook, &undef_hook, node)
-
-
- if ((instr & hook->instr_mask) == hook->instr_val &&
- (regs->ARM_cpsr & hook->cpsr_mask) == hook->cpsr_val)
-
- fn = hook->fn;
-
- return fn ? fn(regs, instr) : 1;
- }

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.
