使用kprobes获取函数参数

时间:2012-05-12 12:15:32

标签: linux debugging assembly kernel

我已经在函数上放了一个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?),它们不应该在堆栈中吗?如何从预处理程序功能访问堆栈?或者如何在不知道其类型和数量的情况下获取任何函数的参数?我知道这可能不是一件容易的事(甚至不可能获得所有值),但只有大约值才足够。我正在计算两个函数的参数之间的相关性,我真的不需要精确的值。如果我有调用函数的汇编代码将参数压入堆栈会有帮助吗?

2 个答案:

答案 0 :(得分:10)

至少有两种方法。

方法1: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个参数按顺序在eaxedxecx中传递,其余的依赖于堆栈。 *(esp+4)应该是第四个参数,*(esp+8) - 第五个参数,依此类推。

x86,64位

似乎x86-64上的内容有点简单。大多数内核函数(包括具有可变参数列表的函数)获得rdirsirdxrcxr8,{中的前6个参数{1}},按顺序,其余的继续堆栈。 r9应该是第7个参数,*(esp+8) - 第8个,依此类推。

编辑:

请注意,在x86-32上,*(esp+16)的值未保存在esp中,用于内核模式陷阱(包括KProbes所依赖的软件断点)。 <asm/ptrace.h>提供pt_regs函数来检索kernel_stack_pointer()的正确值,它在x86-32和x86-64上都有效。有关详细信息,请参阅该标头文件中esp的说明。

此外,kernel_stack_pointer()(也在该标题中定义)提供了一种在处理程序中获取堆栈内容的便捷方法。

答案 1 :(得分:0)

据我所知,分析汇编代码和查找函数参数将是一项艰巨的任务,尤其是在Linux内核代码的情况下。找出函数参数的方法之一是使用调试信息。让我一步一步地说明这一点。

1)使用调试信息(-g选项)构建内核或模块,举个例子,我假设我已经用调试信息构建了一个名为'test.ko'的模块。

2)使用readelf命令解码调试信息。像这样:

   $readelf debug-dump=info test.ko > log.info

这里我将readelf输出重定向到log.info文件。

3)现在打开log.info并搜索要查找函数参数的函数,在我们的例子中,我们说'foobar()'。对于函数foobar(),将有一个带有TAG DW_TAG_subprogram的Dwarf条目。在此TAG之后,您将找到其他一些带有函数参数名称的矮人条目。在此条目中,您将在调用函数时找到这些函数参数的位置。例如,它表示第一个参数'arg'将位于ebx寄存器中,第二个参数将位于esp + 8中,第三个参数将位于ecx寄存器中,依此类推。

4)获得这些信息后,在kprobe预处理程序中打印所有寄存器。并且还打印堆栈数据,您可以打印预先处理程序中的esp寄存器。

5)根据您在第3步中获得的信息搜索参数值。