2009-07-28 4 views
13

Sto lavorando nel kernel per un po 'della mia ricerca estiva. Stiamo cercando di apportare modifiche al TCP, in calcoli RTT specifici. Quello che mi piacerebbe fare è sostituire la risoluzione di una delle funzioni in tcp_input.c con una funzione fornita da un modulo del kernel caricato in modo dinamico. Penso che ciò migliorerebbe il ritmo con cui possiamo sviluppare e distribuire la modifica.Posso sostituire una funzione del kernel Linux con un modulo?

La funzione a cui sono interessato è stata dichiarata come statica, tuttavia ho ricompilato il kernel con la funzione non statica ed esportato da EXPORT_SYMBOL. Ciò significa che la funzione è ora accessibile ad altri moduli/parti del kernel. Ho verificato questo da "cat/proc/kallsyms".

Ora mi piacerebbe essere in grado di caricare un modulo che può riscrivere l'indirizzo del simbolo dalla funzione iniziale a quella caricata dinamicamente. Allo stesso modo, quando il modulo deve essere scaricato, ripristinerebbe l'indirizzo originale. È un approccio fattibile? Avete tutti dei suggerimenti su come questo potrebbe essere meglio implementato?

Grazie!

Uguale Overriding functionality with modules in Linux kernel

Edit:
Questo era il mio approccio eventuale.
Data la seguente funzione (che ho voluto ignorare, e non viene esportato):

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

modificare in questo modo:

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

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

Questo ridefinisce l'identificatore funzione prevista, invece, come un puntatore a funzione (che può essere chiamato in modo simile) indicando l'implementazione originale. EXPORT_SYMBOL() rende l'indirizzo globalmente accessibile, quindi possiamo modificarlo da un modulo (o da un'altra posizione del kernel).

ora è possibile scrivere un modulo del kernel con la seguente forma:

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

Questo modulo sostituisce l'implementazione originale con una versione caricata dinamicamente. Al momento dello scaricamento, il riferimento originale (e l'implementazione) viene ripristinato. Nel mio caso specifico, ho fornito un nuovo stimatore per RTT in TCP. Usando un modulo, sono in grado di apportare piccole modifiche e riavviare i test, il tutto senza dover ricompilare e riavviare il kernel.

risposta

7

Non sono sicuro che funzionerà, credo che la risoluzione dei simboli per le chiamate interne alla funzione che si desidera sostituire sarà già stata effettuata al momento del caricamento del modulo.

Invece, è possibile modificare il codice rinominando la funzione esistente, quindi creando un puntatore a funzione globale con il nome originale della funzione. Inizializza il puntatore alla funzione dell'indirizzo della funzione interna, in modo che il codice esistente funzioni senza modifiche. Esporta il simbolo del puntatore della funzione globale, quindi il tuo modulo può semplicemente cambiarne il valore assegnandolo al carico del modulo e al tempo di scaricamento.

+2

Ho finito per seguire il percorso suggerito nell'aggiunta di un hook globale. È stato facile da implementare e ha fornito esattamente ciò di cui avevo bisogno. Grazie per le informazioni sulla risoluzione dei simboli. Non avevo trovato una fonte che spiegasse in modo definitivo come e quando è stata aperta la tabella dei simboli (ad ogni chiamata di funzione o solo al collegamento). Questo è stato un utile consiglio. –

2

Penso che quello che vuoi sia Kprobe.

Un altro modo accennato da caf è aggiungere un hook alla routine originale e registrare/annullare la registrazione del hook nel modulo.

+0

Kprobe sembra uno strumento interessante e utile. Grazie per il link. Anche se ho preso una strada diversa, penso che questo avrebbe potuto essere un approccio efficace. –

3

Puoi provare a utilizzare ksplice - non hai nemmeno bisogno di renderlo non statico.

+0

Non è gratuito. C'è un'alternativa FOSS –

3

Una volta ho fatto una dimostrazione del concetto di un modulo hijack che ha inserito la propria funzione al posto della funzione del kernel. Accade solo che la nuova architettura di taster del kernel usi un sistema molto simile.

Ho immesso la mia funzione nel kernel sovrascrivendo la prima coppia di byte di codice con un salto che punta alla mia funzione personalizzata. Non appena viene richiamata la funzione reale, salta invece alla mia funzione che dopo aver eseguito il suo lavoro ha chiamato la funzione originale.


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

Ti avverto, se si sta andando a sperimentare con questo genere di cose, guardare fuori per kernel panic e altri eventi disastrosi. Ti consiglierei di farlo in un ambiente virtualizzato. Questo è un codice proof-of-concept che ho scritto qualche tempo fa, non sono sicuro che funzioni ancora.

È un principio davvero semplice, ma molto efficace. Ovviamente, una soluzione reale userebbe le serrature per assicurarsi che nessuno chiamasse la funzione mentre la sovrascrivevi.

Buon divertimento!