2009-07-28 6 views
13

Me estoy metiendo en el trabajo del kernel por un poco de mi investigación de verano. Estamos buscando hacer modificaciones al TCP, en cálculos específicos de RTT. Lo que me gustaría hacer es reemplazar la resolución de una de las funciones en tcp_input.c a una función proporcionada por un módulo kernel cargado dinámicamente. Creo que esto mejoraría el ritmo al que podemos desarrollar y distribuir la modificación.¿Puedo reemplazar una función del kernel de Linux con un módulo?

La función que me interesa se declaró estática, sin embargo, he recompilado el núcleo con la función no estática y exportado por EXPORT_SYMBOL. Esto significa que la función ahora está disponible para otros módulos/partes del kernel. Lo he verificado por "cat/proc/kallsyms".

Ahora me gustaría poder cargar un módulo que pueda reescribir la dirección de símbolo desde la inicial a mi función cargada dinámicamente. De manera similar, cuando el módulo se descargue, restaurará la dirección original. ¿Es este un enfoque factible? ¿Todos tienen sugerencias de cómo se podría implementar mejor?

Gracias!

Igual que Overriding functionality with modules in Linux kernel

Editar:
Esta fue mi enfoque final.
Dada la siguiente función (que quería anular, y no se exporta):

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

modificar así:

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

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

Este redefine el identificador de función esperada en cambio, como un puntero de función (que puede llamarse de manera similar) apuntando a la implementación original. EXPORT_SYMBOL() hace que la dirección sea accesible globalmente, por lo que podemos modificarla desde un módulo (u otra ubicación del kernel).

Ahora usted puede escribir un módulo del kernel con la siguiente 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; 
} 

Este módulo sustituye a la aplicación original con una versión de carga dinámica. Tras la descarga, se restaura la referencia original (y la implementación). En mi caso específico, proporcioné un nuevo estimador para el RTT en TCP. Al usar un módulo, puedo hacer ajustes pequeños y reiniciar las pruebas, todo sin tener que recompilar y reiniciar el kernel.

Respuesta

7

No estoy seguro de que funcione - Creo que la resolución del símbolo para las llamadas internas a la función que desea reemplazar ya estará hecha cuando se cargue su módulo.

En su lugar, puede cambiar el código cambiando el nombre de la función existente y luego creando un puntero de función global con el nombre original de la función. Inicialice el puntero a la dirección de la función interna, de modo que el código existente funcionará sin modificaciones. Exporte el símbolo del puntero de función global, luego su módulo puede cambiar su valor por asignación en la carga y tiempo de descarga del módulo.

+2

Terminé yendo por la ruta que sugirió al agregar un gancho global. Fue fácil de implementar y proporcionó exactamente lo que necesitaba. Gracias por la información con respecto a la resolución del símbolo. No había encontrado una fuente que explicara definitivamente cómo y cuándo se accedió a la tabla de símbolos (en cada llamada a función o solo en el enlace). Este fue un consejo útil. –

2

Creo que lo que quiere es Kprobe.

Otra forma que café ha mencionado es agregar un gancho a la rutina original, y registrar/anular el registro del gancho en el módulo.

+0

Kprobe parece una herramienta interesante y útil. Gracias por el enlace. Aunque tomé una ruta diferente, creo que esto podría haber sido un enfoque eficaz. –

3

Puede intentar usar ksplice - ni siquiera necesita hacerlo no estático.

+0

No es gratis. ¿Hay alguna alternativa de FOSS? –

3

Alguna vez hice una prueba de concepto de un módulo de secuestro que insertó su propia función en lugar de la función del kernel. Ocurre que la nueva arquitectura de kernel tacing usa un sistema muy similar.

Inyecté mi propia función en el kernel sobrescribiendo los primeros dos bytes de código con un salto que apuntaba a mi función personalizada. Tan pronto como se llama a la función real, salta a mi función que, después de haber hecho su trabajo, llamó a la función original.


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

Soy un aviso, cuando se va a experimentar con este tipo de cosas, cuidado con los errores de kernel y otros eventos desastrosos. Te aconsejaría que hicieras esto en un entorno virtualizado. Este es un código de prueba de concepto que escribí hace un tiempo, no estoy seguro de que todavía funcione.

Es un principio muy fácil, pero muy efectivo. Por supuesto, una solución real utilizaría bloqueos para asegurarse de que nadie llamará a la función mientras la sobrescribe.

¡Diviértete!

Cuestiones relacionadas