14

我进入内核工作是为了我的一些暑期研究。我们希望在特定的 RTT 计算中对 TCP 进行修改。我想做的是将 tcp_input.c 中的一个函数的分辨率替换为动态加载的内核模块提供的函数。我认为这将提高我们开发和分发修改的速度。

我感兴趣的函数被声明为静态,但是我已经用非静态函数重新编译了内核并由 EXPORT_SYMBOL 导出。这意味着该函数现在可供内核的其他模块/部分访问。我已经通过“cat /proc/kallsyms”验证了这一点。

现在我希望能够加载一个模块,该模块可以将符号地址从初始地址重写为动态加载的函数。同样,当模块要被卸载时,它会恢复原来的地址。这是一种可行的方法吗?大家有没有建议如何更好地实施?

谢谢!

与Linux 内核中的模块覆盖功能相同

编辑:
这是我最终的方法。
给定以下函数(我想覆盖它,而不是导出):

static void internal_function(void) 
{
  // do something interesting
  return;
}

像这样修改:

static void internal_function_original(void)
{
  // do something interesting
  return;
}

static void (*internal_function)(void) = &internal_function_original;
EXPORT_SYMBOL(internal_function);

这将预期的函数标识符重新定义为指向原始实现的函数指针(可以以类似方式调用)。EXPORT_SYMBOL() 使地址全局可访问,因此我们可以从模块(或其他内核位置)修改它。

现在您可以使用以下形式编写内核模块:

static void (*original_function_reference)(void);
extern void (*internal_function)(void);

static void new_function_implementation(void)
{
  // do something new and interesting
  // return
}

int init_module(void)
{
  original_function_reference = internal_function;
  internal_function           = &new_function_implementation;
  return 0;
}

void cleanup_module(void)
{
  internal_function = original_function_reference;
}

此模块用动态加载的版本替换原始实现。卸载后,将恢复原始引用(和实现)。在我的具体情况下,我为 TCP 中的 RTT 提供了一个新的估计器。通过使用一个模块,我可以进行小的调整并重新启动测试,而无需重新编译和重新启动内核。

4

4 回答 4

7

我不确定这是否可行 - 我相信在您的模块加载时,对您要替换的函数的内部调用的符号解析已经完成。

相反,您可以通过重命名现有函数来更改代码,然后使用函数的原始名称创建一个全局函数指针。将函数指针初始化为内部函数的地址,因此现有代码将不加修改地工作。导出全局函数指针的符号,然后你的模块可以在模块加载和卸载时通过赋值来改变它的值。

于 2009-07-28T23:22:51.233 回答
4

我曾经做过一个劫持模块的概念证明,它插入了它自己的函数来代替内核函数。我只是碰巧新的内核跟踪架构使用了一个非常相似的系统。

我通过使用指向我的自定义函数的跳转覆盖代码的前几个字节,在内核中注入了我自己的函数。一旦调用了真正的函数,它就会跳转到我的函数,在它完成后它的工作称为原始函数。


#include <linux/module.h>
#include <linux/kernel.h>

#define CODESIZE 12

static unsigned char original_code[CODESIZE];
static unsigned char jump_code[CODESIZE] =
    "\x48\xb8\x00\x00\x00\x00\x00\x00\x00\x00" /* movq $0, %rax */
    "\xff\xe0"                                          /* jump *%rax */
        ;
/* FILL THIS IN YOURSELF */
int (*real_printk)( char * fmt, ... ) = (int (*)(char *,...) )0xffffffff805e5f6e;

int hijack_start(void);
void hijack_stop(void);
void intercept_init(void);
void intercept_start(void);
void intercept_stop(void);
int fake_printk(char *, ... );


int hijack_start()
{
    real_printk(KERN_INFO "I can haz hijack?\n" );
    intercept_init();
    intercept_start();

    return 0;
}

void hijack_stop()
{
    intercept_stop();
    return;
}

void intercept_init()
{
    *(long *)&jump_code[2] = (long)fake_printk;
    memcpy( original_code, real_printk, CODESIZE );

    return;
}

void intercept_start()
{
    memcpy( real_printk, jump_code, CODESIZE );
}

void intercept_stop()
{
    memcpy( real_printk, original_code, CODESIZE );
}

int fake_printk( char *fmt, ... )
{
    int ret;
    intercept_stop();
    ret = real_printk(KERN_INFO "Someone called printk\n");
    intercept_start();
    return ret;
}

module_init( hijack_start );
module_exit( hijack_stop );

我警告你,当你要试验这些东西时,要注意内核恐慌和其他灾难性事件。我建议您在虚拟化环境中执行此操作。这是我不久前编写的概念验证代码,我不确定它是否仍然有效。

这是一个非常简单的原则,但非常有效。当然,真正的解决方案是使用锁来确保在您覆盖它时没有人会调用该函数。

玩得开心!

于 2009-08-07T00:15:47.220 回答
3

您可以尝试使用ksplice - 您甚至不需要将其设为非静态。

于 2009-07-29T01:55:36.853 回答
2

我认为你想要的是Kprobe

caf 提到的另一种方法是在原始例程中添加一个钩子,并在模块中注册/取消注册钩子。

于 2009-07-29T00:58:46.123 回答