2009-07-28 100 views
13

進入我的暑期研究的核心工作。我們希望在特定的RTT計算中對TCP進行修改。我想要做的是將tcp_input.c中的一個函數的分辨率替換爲由動態加載的內核模塊提供的函數。我認爲這會提高我們開發和分發修改的速度。我可以使用模塊替換Linux內核函數嗎?

我感興趣的函數被聲明爲靜態的,但是我已經用非靜態函數重新編譯了內核,並通過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提供了一個新的估計器。通過使用模塊,我可以進行小小的調整並重新開始測試,而無需重新編譯和重新引導內核。

回答

7

我不確定這會起作用 - 我相信對於要更換的函數的內部調用的符號解析將在您的模塊加載完成時完成。

相反,您可以通過重命名現有函數來更改代碼,然後使用該函數的原始名稱創建一個全局函數指針。將函數指針初始化爲內部函數的地址,因此現有代碼將不加修改地工作。導出全局函數指針的符號,然後您的模塊可以通過在模塊加載和卸載時分配來更改其值。

+2

我最終選擇了添加全局鉤子的路線。這很容易實現,並提供了我所需要的。 感謝有關符號解析的信息。我沒有找到明確解釋符號表訪問的方式和時間的來源(在每個函數調用或僅在連接時)。這是一條有用的建議。 – 2009-07-29 03:00:42

2

我想你想要的是Kprobe

caf提到的另一種方法是在原始程序中添加一個鉤子,並在模塊中註冊/取消註冊鉤子。

+0

Kprobe看起來像一個有趣而有用的工具。感謝您的鏈接。 雖然我採取了不同的路線,但我認爲這可能是一種有效的方法。 – 2009-07-29 02:55:57

3

你可以嘗試使用ksplice - 你甚至不需要使它不是靜態的。

+0

這不是免費的。有沒有FOSS的選擇? – 2016-05-27 02:18:27

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

我警告你,當你要試驗這些事情時,要小心內核恐慌和其他災難性事件。我建議你在虛擬化環境中做到這一點。這是我剛纔寫的概念驗證代碼,我不確定它仍然有效。

這是一個非常簡單的原則,但非常有效。當然,真正的解決方案會使用鎖來確保沒有人在覆蓋它時調用該函數。

玩得開心!