LKMPG: Fasi preliminari
Moduli vs Programmi
Come i moduli iniziano e finiscono
Un programma di solito inizia con la funzione main(), esegue una serie di istruzioni e termina al completamento di queste istruzioni. I moduli del kernel lavorano in modo leggermente diverso. Un modulo inizia sempre o con la funzione init_module
o con la funzione che specifichi nella chiamata di module_init
. Questa è la funzione d'ingresso per i moduli: dice al kernel che funzionalità il modulo fornisce e imposta il kernel in modo da eseguire le funzioni del modulo quando richieste. Una volta fatto tutto questo, la funzione d'ingresso termina ed il modulo non fa nulla finchè il kernel vuole fare qualcosa con il codice che il modulo fornisce.
Tutti i moduli terminano chiamando o la funzione cleanup_module
o la funzione specificata nella chiamata di module_exit
. Questa è la funzione d'uscita dei moduli: questa rimuove qualsiasi cosa la funzione d'ingresso ha fatto. Elimina le funzionalità registrate dal modulo all'interno del kernel dalla funzione d'ingresso.
Ogni modulo deve avere una funzione d'ingresso ed una d'uscita. Poichè c'è piu di un modo di specificare le funzioni d'ingresso ed uscita, cercherò di fare del mio meglio usando i termini `funzione d'ingresso' e `funzione d'uscita', ma se dovessi confondermi, riferendomi a loro come init_module
e cleanup_module
, penso che capirai cosa cerco di dire.
Funzioni disponibili per i moduli
I programmatori usano funzioni che non definiscono tutte le volte. Un primo esempio è printf()
. Utilizzi queste funzioni messe a disposizione dalla libreria standard del C, libc. Le definizioni di queste funzioni non vengono inserite nel tuo programma fino al momento del linking, che assicura che il codice (per la printf()
ad esempio) è disponibile, e aggiusta le chiamate alla funzione in modo da puntare al codice.
I moduli del kernel sono differenti in questo. Nell'esempio "Ciao Mondo", potresti aver notato che abbiamo usato una funzione, printk()
, ma non abbiamo incluso una libreria di I/O. Questo perchè i moduli sono file oggetti i cui simboli sono risolti quando si carica il modulo con insmod
. Le definizioni dei simboli arrivano dal kernel stesso; le uniche funzioni esterne che puoi usare sono quelle messe a disposizione dal kernel. Se sei curioso sui simboli che sono esportati dal tuo kernel, dai uno sguardo a /proc/kallsyms
.
Un punto da tenere a mente è la differenza tra funzioni di libreria e chiamate di sistema. Le funzioni di libreria sono di alto livello, vengono eseguite ad user space e mettono a disposizione una comoda interfaccia, per il programmatore, alle funzioni che fanno il vero lavoro---le chiamate di sistema (syscall). Le syscall vengono eseguite in kernel mode per conto dell'utente e sono messe a disposizione dal kernel stesso. La funzione di libreria printf()
può sembrare una funzione di stampa generica, ma tutto quello che fa è formattare i dati in stringhe e scriverle usando una syscall di basso livello, write()
, che poi invia i dati allo standard output.
Vuoi vedere da quali syscall è fatta printf()
? E' facile! Compila il seguente programma:
#include <stdio.h> int main(void) { printf("hello"); return 0; }
con gcc -Wall -o hello hello.c. Esegui il programma con strace ./hello. Sei impressionato? Ogni linea che vedi corrisponde ad una syscall. strace[4] è un comodo programma che ti dà i dettagli su quali syscall il programma chiama, come una chiamata è fatta, quali sono gli argomenti e cosa ritorna. E' uno strumento inestimabile per capire cose come a quali file un programma sta provando ad accedere. Verso la fine, vedrai una linea come write(1, "hello", 5hello)
. Questo è. La faccia dietro la maschera printf()
. Potresti non essere familiare con write()
poichè molte persone usano le funzioni di libreria per le operazioni di I/O su file (come fopen
,fputs
, fclose
). Se questo è il caso, prova a dare uno sguardo a man 2 write. La seconda sezione del man è dedicata alle chiamate di sistema (come kill()
e read()
). La terza sezione del manuale è dedicata alle chiamate di libreria, alle quali probabilmente siete molto più abituati (come cosh()
e random()
).
Puoi anche scrivere moduli per rimpiazzare le syscall, come faremo a breve. I crackers spesso fanno uso di questo tipo di cose per eseguire backdoors o trojan, ma puoi scrivere il tuo modulo per fare come benigne, come stampare Tee hee, fa il solletico! ogni volta che qualcuno prova a cancellare un file sul tuo sistema.