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 下面有不少的例子
当运行到监控的某个点的时候,会调用此函数
初始话,注册一个kprobe,可以传入函数名或者在内存的绝对地址(system.map)
注销kprobe探测点
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 ,具体模样如下:
此函数,很方便调试模拟,随便调用个shell 命令都会调用此函数
5. ARM架构kprobe应用及实现分析(5.0打印寄存器的值)
kp.pre_handler = handler_pre;
在此函数中打印寄存器的值,才能对我们分析当时的情况有帮助。(如查看调用函数的参数值等)
下面是生产一个寄存器对应的索引及寄存器的名字(用途),具体可以参考ARM的相关资料
一共18个寄存器
struct pt_regs {
long uregs[18];
};
下面很多地方也用到了此数组的生成方法(perror等),我也班门弄斧下:)
dump出所有寄存器的值:
最后handler_pre回调的时候,调用dump_arm_regs 到处所有寄存器的值,以便分析之用
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为中心要打印周围栈的数据
实际调用(regs->uregs[13] 即为stack pointer):
当探测到实际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 被探测函数说明)
导出参数的函数:
使用情形:
kernel log 输出如下:
func paras maybe : (0x00000011,0x00000022,0x00000033,0xc077c670,0x00000044,0x00000055)
与我们实际传入的一致
8. ARM架构kprobe应用及实现分析(8.0 register_kprobe实现)
1.
参考: kernel/Documentation/kprobes.txt 有如下一段话:
3. Specify either the kprobe "symbol_name" OR the "addr". If both are
specified, kprobe registration will fail with -EINVAL.
大概查找形式如下图:(mm slab 等地方都用到了此算法)
唯一的缺点就是当HASH值比较大的时候,会占用比较多的内存
太小的话,算法的时间复杂度又退化成list
9. ARM架构kprobe应用及实现分析(9.0 arch_prepare_kprobe平台相关注册)
1.
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)
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.