我可以用模块替换Linux内核函数吗?

时间:2009-07-28 21:47:27

标签: c linux module linux-kernel kernel-module

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

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

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

谢谢!

Overriding functionality with modules in Linux kernel

相同

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

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 个答案:

答案 0 :(得分:7)

我不确定它是否可行 - 我相信你想要替换的函数的内部调用的符号解析将在你的模块加载时完成。

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

答案 1 :(得分:3)

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

答案 2 :(得分:3)

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

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


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

我警告你,当你要尝试这些事情时,要注意内核恐慌和其他灾难性事件。我建议你在虚拟化环境中这样做。这是我前一段时间写的概念验证代码,我不确定它是否仍然有用。

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

玩得开心!

答案 3 :(得分:2)

我认为你想要的是Kprobe

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