我已经在一个函数上放置了一个 kprobe,现在我需要在 kprobe 的预处理器函数中获取它的参数值。
这是我的功能:
void foobar(int arg, int arg2, int arg3, int arg4, int arg5, int arg6, int arg7, int arg8)
{
printk("foobar called\n");
}
将kprobe放在上面并调用函数:
...
kp.addr = (kprobe_opcode_t *) foobar;
register_kprobe(&kp);
foobar(0xdead1, 0xdead2, 0xdead3, 0xdead4, 0xdead5, 0xdead6, 0xdead7, 0xdead8);
最后是预处理器函数(取自 here ):
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++;
//...
}
预处理器函数的输出如下所示(我将 regs
指针递增了 3 次)
May 10 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
在哪里?),它们不应该在堆栈中吗?如何从预处理器函数访问堆栈?或者我如何在不知道它们的类型和计数的情况下获取任何函数的参数?我知道这可能不是一件容易的事(甚至不可能获得所有值),但只有近似值就足够了。我正在计算两个函数的参数之间的相关性,我真的不需要确切的值。如果我有将参数压入堆栈的调用函数的汇编代码,会有帮助吗?
最佳答案
至少有两种方法。
方法一:Jprobes
可能是最简单的一个:如果 Jprobes 适合您的任务,您可以尝试一下。它们是 kprobes 的近亲(请参阅它们的详细描述和指向 kernel docs 中示例的链接)。
Jprobes 允许在进入后者时使用与被探测函数相同的签名调用您的函数。您可以通过这种方式自动获得所有参数。
方法 2:寄存器和堆栈
另一种方法可能是扩展您已经做的事情并从寄存器和堆栈中检索参数。根据您问题的输出日志,我假设您正在使用 32 位 x86 系统。
x86,32 位
据我所知,在 x86 上的 Linux 内核中有两种最常见的参数传递约定(详细信息可在 manual by Agner Fog 中找到)。请注意,系统调用遵循其他约定(有关详细信息,请参阅 manual),但我假设您对分析“普通”函数而不是系统调用感兴趣。
约定 1
对于标有asmlinkage
的函数以及具有可变参数列表的函数,所有参数都在堆栈上传递。函数的返回地址应该位于函数入口处的堆栈顶部,第一个参数位于它的正下方。第二个参数在第一个参数之下,以此类推。
因为你有 esp
的保存值,你可以找到它指向的内容。如果使用此约定,*(esp+4)
应该是第一个参数,*(esp+8)
- 第二个等等。
约定 2
它似乎用于大多数内核函数,包括您在问题中提到的那个。
内核使用 -mregparm=3
编译,因此前 3 个参数在 eax
、edx
和 ecx 中传递
,按照这个顺序,其余的进入堆栈。 *(esp+4)
应该是第 4 个参数,*(esp+8)
- 第 5 个,依此类推。
x86, 64 位
在 x86-64 上似乎事情要简单一些。大多数内核函数(包括那些具有可变参数列表的函数)在 rdi
、rsi
、rdx
、rcx 中获取前 6 个参数
, r8
, r9
,按照这个顺序,剩下的入栈。 *(esp+8)
应该是第 7 个参数,*(esp+16)
- 第 8 个等等。
编辑:
请注意,在 x86-32 上,esp
的值不会保存在 pt_regs
中以用于内核模式陷阱(包括 KProbes 所依赖的软件断点)。 <asm/ptrace.h>提供 kernel_stack_pointer()
函数来检索 esp
的正确值,它在 x86-32 和 x86-64 上都有效。有关详细信息,请参阅该头文件中对 kernel_stack_pointer()
的描述。
此外,regs_get_kernel_stack_nth()
(也在该 header 中定义)提供了一种在处理程序中获取堆栈内容的便捷方法。
关于linux - 使用 kprobes 获取函数参数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10563635/