2009-07-28 8 views
13

Ich komme für ein bisschen meiner Sommerforschung in Kernel-Arbeit. Wir versuchen, in bestimmten RTT-Berechnungen Änderungen am TCP vorzunehmen. Was ich tun möchte, ist die Ersetzung der Auflösung einer der Funktionen in tcp_input.c zu einer Funktion von einem dynamisch geladenen Kernel-Modul. Ich denke, dies würde das Tempo verbessern, in dem wir die Modifikation entwickeln und verteilen können.Kann ich eine Linux-Kernel-Funktion durch ein Modul ersetzen?

Die Funktion, an der ich interessiert bin, wurde als statisch deklariert, allerdings habe ich den Kernel mit der Funktion nonstatic neu kompiliert und von EXPORT_SYMBOL exportiert. Dies bedeutet, dass die Funktion nun für andere Module/Teile des Kernels zugänglich ist. Ich habe dies durch "cat/proc/kallsyms" verifiziert.

Jetzt möchte ich in der Lage sein, ein Modul zu laden, das die Symboladresse von der ursprünglichen zu meiner dynamisch geladenen Funktion neu schreiben kann. Wenn das Modul entladen werden soll, würde es in ähnlicher Weise die ursprüngliche Adresse wiederherstellen. Ist das ein praktikabler Ansatz? Haben Sie alle Vorschläge, wie dies besser umgesetzt werden könnte?

Danke!

Gleiche wie Overriding functionality with modules in Linux kernel

Edit:
Dies war meine eventuelle Annäherung.
Da die folgende Funktion (die ich außer Kraft setzen wollte, und nicht exportiert wird):

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

ändern wie folgt:

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

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

Dies definiert die erwartete Funktionskennung stattdessen als einen Funktionszeiger (die kann auf ähnliche Weise aufgerufen werden), wobei auf die ursprüngliche Implementierung verwiesen wird. EXPORT_SYMBOL() macht die Adresse global zugänglich, so dass wir sie von einem Modul (oder einem anderen Kernel-Speicherort) aus modifizieren können.

Jetzt können Sie einen Kernel-Modul mit dem folgende Formular schreiben:

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

Dieses Modul ersetzt die ursprüngliche Implementierung mit einer dynamisch geladenen Version. Nach dem Entladen wird die ursprüngliche Referenz (und Implementierung) wiederhergestellt. In meinem speziellen Fall habe ich einen neuen Schätzer für die RTT in TCP zur Verfügung gestellt. Mit einem Modul kann ich kleine Verbesserungen vornehmen und den Test neu starten, ohne den Kernel neu kompilieren und neu starten zu müssen.

Antwort

7

Ich bin mir nicht sicher, ob das funktioniert - ich glaube, dass die Symbolauflösung für die internen Aufrufe der Funktion, die Sie ersetzen möchten, bereits beim Laden des Moduls erfolgt ist.

Stattdessen können Sie den Code ändern, indem Sie die vorhandene Funktion umbenennen und dann einen globalen Funktionszeiger mit dem ursprünglichen Namen der Funktion erstellen. Initialisieren Sie den Funktionszeiger auf die Adresse der internen Funktion, damit der vorhandene Code unverändert funktioniert. Exportieren Sie das Symbol des globalen Funktionszeigers, dann kann Ihr Modul einfach seinen Wert durch Zuordnung bei Modullade- und Entladezeit ändern.

+2

Ich ging die Route, die Sie vorgeschlagen, beim Hinzufügen eines globalen Hooks. Es war einfach zu implementieren und lieferte genau das, was ich brauchte. Danke für die Information bezüglich der Symbolauflösung. Ich hatte keine Quelle gefunden, die eindeutig erklärte, wie und wann auf die Symboltabelle zugegriffen wurde (bei jedem Funktionsaufruf oder nur bei der Verknüpfung). Dies war ein nützlicher Ratschlag. –

2

Ich denke, was Sie wollen, ist Kprobe.

Eine weitere Möglichkeit, die caf erwähnt hat, besteht darin, der ursprünglichen Routine einen Hook hinzuzufügen und den Hook im Modul zu registrieren bzw. die Registrierung aufzuheben.

+0

Kprobe sieht wie ein interessantes und nützliches Werkzeug aus. Danke für den Link. Obwohl ich einen anderen Weg genommen habe, denke ich, dass dies ein effektiver Ansatz sein könnte. –

3

Sie können versuchen, ksplice zu verwenden - Sie müssen es nicht sogar nicht statisch machen.

+0

Es ist nicht kostenlos. Gibt es eine FOSS-Alternative? –

3

Ich habe einmal einen Beweis für das Konzept eines Hijack-Moduls, das seine eigene Funktion anstelle der Kernel-Funktion eingefügt. Ich zufällig gerade, dass die neue Kernel-Tacing-Architektur ein sehr ähnliches System verwendet.

Ich habe meine eigene Funktion in den Kernel injiziert, indem ich die ersten paar Byte Code mit einem Sprung überschrieben habe, der auf meine benutzerdefinierte Funktion zeigt. Sobald die reale Funktion aufgerufen wird, springt sie stattdessen zu meiner Funktion, die nach ihrer Arbeit die ursprüngliche Funktion aufgerufen hat.


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

Ich warne Sie, wenn Sie mit dieser Art von Dingen zu experimentieren gehen, achten Sie auf Kernel-Panik und andere katastrophale Ereignisse aus. Ich würde Ihnen raten, dies in einer virtualisierten Umgebung zu tun. Dies ist ein Proof-of-Concept-Code, den ich vor einiger Zeit geschrieben habe. Ich bin mir nicht sicher, ob er noch funktioniert.

Es ist ein wirklich einfaches Prinzip, aber sehr effektiv. Natürlich würde eine echte Lösung Sperren verwenden, um sicherzustellen, dass niemand die Funktion aufruft, während Sie sie überschreiben.

Viel Spaß!

Verwandte Themen