获取kprobes嗅探的函数参数
疑问:
我已经用kprobes对一个函数进行嗅探,我需要在pre_handler处理函数里获取被嗅探函数的参数值。
被嗅探函数如下:
void foobar(int arg, int arg2, int arg3,int arg4, int arg5, int arg6, int arg7, int arg8)
{
printk("foobarcalled\n");
}
设置嗅探:
...
kp.addr = (kprobe_opcode_t *) foobar;
register_kprobe(&kp);
foobar(0xdead1, 0xdead2, 0xdead3, 0xdead4, 0xdead5, 0xdead6, 0xdead7, 0xdead8);
pre_handler处理函数如下:
static int inst_generic_make_request(struct kprobe *p, struct pt_regs *regs)
{
printk(KERN_INFO "eax: %08lx ebx: %08lx ecx: %08lx edx: %08lx\n",
regs->ax, regs->bx, regs->cx, regs->dx);
printk(KERN_INFO "esi: %08lx edi: %08lx ebp: %08lx esp: %08lx\n",
regs->si, regs->di, regs->bp, regs->sp);
regs++;
//...
}
运行后,pre_handler处理函数输出如下:
22:58:07 kernel: [ 402.640994] eax: 000dead1 ebx: f7d80086 ecx: 000dead3 edx: 000dead2
May 10 22:58:07 kernel: [ 402.640996] esi: 00000000 edi: b77c8040 ebp: 00000000 esp: f7d8006c
May 10 22:58:07 kernel: [ 402.641006] eax: f7d8032c ebx: 000dead5 ecx: 000dead6 edx: 000dead7
May 10 22:58:07 kernel: [ 402.641007] esi: 000dead8 edi: f7d800e0 ebp: f7d80330 esp: 08049674
May 10 22:58:07 kernel: [ 402.641014] eax: 00000080 ebx: 0992b018 ecx: 0000108e edx: 0992b008
May 10 22:58:07 kernel: [ 402.641015] esi: 08049674 edi: b77c8040 ebp: bfe23fb8 esp: bfe23f50
现在我可以在各个寄存器看到foobar函数的参数(但0xdead4在哪里?),它们不是应该在栈里吗?我怎样才能在pre_handler处理函数里访问这些栈?又或者说我怎样才能获取任何不知道参数类型和个数的函数的参数?我知道这不是一件容易的事(甚至不可能获取全部参数),即使获取约值也行,我已经计算了两个函数间的参数,所以不需要太精确。如果我有参数在栈里的被调用函数的汇编代码,这是否有帮助?
回答
至少有两种方法。
1、方法一:Jprobes
估计是最简单的一种方法:如果你的任务可以用Jprobes,你可以试一下。Jprobes是基于kprobes实现的(详细描述和使用例子可以看:http://www.mjmwired.net/kernel/Documentation/kprobes.txt)。
Jprobes允许在被嗅探函数入口处调用与被嗅探函数原型一致的处理函数,你自然就可以通过这种方式获取它的所有参数了。
2、方法二:寄存器和栈
另一种方法就如你已经做得,稍微复杂一点。从你的输出日志看,我猜你是在32位X86系统上做的。
2.1、 32位X86
正如我们所见,在X86的linux内核(详见http://www.agner.org/optimize/calling_conventions.pdf)传递参数时有两个最常见的规律。需要注意的是系统调用可能遵循其他规律(详见man手册http://www.agner.org/optimize/calling_conventions.pdf),但我想你感兴趣的是分析“普通”函数而非系统调用。
规律1:
对于asmlinkage
标志和有变量参数列表的函数,全部参数压入栈内。函数的返回地址在入口函数的栈顶,第一个参数位于它的后面,第二个参数跟在第一个的后面,以此类推。
比如,在这种规律下如果你保存了esp的值,你会发现:*(esp+4)就是第一个参数,*(esp+8)就是第二个参数,以此类推。
规律2:
包括你所列举的例子在内的大多数函数都遵循这种规律。
内核编译时是带参数-mregparm=3的,所以前3个参数保存在eax、ebx和ecx,按照这种顺序,其他参数压入栈内,*(esp+4) 就是第4个参数,*(esp+8) 就是第5个参数。
2.2、64位X86
在X86-64上会简单一些。大部分内核函数(包括有变量参数列表的函数),可以通过寄存器rdi
,、rsi
,、rdx
,、rcx
,、r8
,、r9
获取前6个参数,按照这种顺序,其他参数压入栈内,*(esp+8)
就是第7个参数, *(esp+16)
就是第8个参数。
2.3、注意
值得注意的是:在X86-32系统上,并没有为内核模式陷阱(包括kprobes依赖的断点)把esp的值保存到pt_regs上。 <asm/ptrace.h> 提供kernel_stack_pointer()函数来获取当前esp的值,它在X86-32和X86-64上均有效。详见其头文件。
另外,regs_get_kernel_stack_nth()(也是定义在 <asm/ptrace.h>)能方便地获取处理器堆栈的内容。
英语原文:http://stackoverflow.com/questions/10563635/getting-function-arguments-using-kprobes