9

我在一个函数上放了一个 kprobe,现在我需要在 kprobe 的 prehandler 函数中获取它的参数值。

这是我的功能:

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);

最后是预处理函数(取自这里):

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++;
    //...
}

prehandler 函数的输出如下所示(我将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?),它们不应该在堆栈中吗?如何从 prehandler 函数访问堆栈?或者如何在不知道它们的类型和计数的情况下获取任何函数的参数?我知道这可能不是一件容易的事(甚至不可能获得所有值),但只有近似值就足够了。我正在计算两个函数的参数之间的相关性,我真的不需要确切的值。如果我有调用函数的汇编代码,其中参数被推到堆栈上会有所帮助吗)?

4

2 回答 2

14

至少有两种方法。

方法一:Jprobes

可能是最简单的一个:如果Jprobes适合您的任务,您可以尝试一下。它们是 kprobes 的近亲(请参阅它们的详细描述和内核文档中的示例链接)。

Jprobes 允许在进入后者时使用与被探测函数相同的签名调用您的函数。您可以通过这种方式自动获得所有参数。

方法 2:寄存器和堆栈

另一种方法可能是扩展您已经做的事情并从寄存器和堆栈中检索参数。从您问题的输出日志中,我假设您正在使用 32 位 x86 系统。

x86, 32 位

据我所知,在 x86 上的 Linux 内核中传递参数有两种最常见的约定(详细信息可在Agner Fog 的手册中找到)。请注意,系统调用遵循其他约定(有关详细信息,请参阅手册),但我假设您对分析“普通”函数而不是系统调用感兴趣。

公约 1

对于标记asmlinkage为 的函数以及具有可变参数列表的函数,所有参数都在堆栈上传递。函数的返回地址应该在函数入口的栈顶,第一个参数位于它的正下方。第二个参数低于第一个参数,依此类推。

由于您保存了 的值esp,因此您可以找到它所指向的内容。*(esp+4)应该是第一个参数,*(esp+8)- 如果使用此约定,则为第二个等。

公约 2

它似乎用于大多数内核功能,包括您在问题中提到的那个。

内核是用 编译的-mregparm=3,因此前 3 个参数被传入eaxedx并且ecx,按照这个顺序,其余的进入堆栈。*(esp+4)应该是第四个参数,*(esp+8)-第五个,依此类推。

x86, 64位

似乎 x86-64 上的事情要简单一些。大多数内核函数(包括具有可变参数列表的那些)按顺序获取rdi, rsi, rdx, rcx, r8,r9中的前 6 个参数,其余的进入堆栈。*(esp+8)应该是第 7 个参数,*(esp+16)- 第 8 个,依此类推。

编辑:

请注意,在 x86-32 上,esp不会pt_regs为内核模式陷阱(包括 KProbe 所依赖的软件断点)保存 的值。<asm/ptrace.h>提供 kernel_stack_pointer()了检索正确值的函数esp,它适用于 x86-32 和 x86-64。有关详细信息,请参阅该kernel_stack_pointer()头文件中的描述。

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

于 2012-05-13T19:32:52.927 回答
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 之后,您会发现其他一些带有函数参数名称的 dwarf 条目。在此条目中,您将找到调用函数时这些函数参数的位置。例如,它说第一个参数“arg”将在 ebx 寄存器中,第二个参数将在 esp+8 中,第三个参数将在 ecx 寄存器中,依此类推。

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

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

于 2012-05-12T16:22:28.643 回答