Celem zadania będzie implementacja dynamicznego linkowania w jądrze Linuxa:
Chodzi o to by można było raz dokonać drobnej zmiany w kodzie jądra a 
podmieniać funkcje realizujące pewne zmiany już bez modyfikacji kodu jądra 
i bez jego rekompilacji. Ważne jest to, że w momencie wołania funkcji nie 
musi ona być znana.
Przykład: 

asmlinkage ssize_t sys_read(unsigned int fd, char * buf, size_t count)
{
	.
	.
	.
	if (!file->f_op || !(read = file->f_op->read))
		goto out;

	/* odtąd  zaczynają się zmiany */
	{
	    struct marg_t argumenty, wynik;

	    argumenty.len = 5;
	    argumenty.arg = malloc(sizeof(struct arg_t) * argumenty.len);	    
	    argumenty.arg[0].data = (void*) file;
	    argumenty.arg[1].data = (void*) buf;
	    argumenty.arg[2].data = (void*) &count;
	    argumenty.arg[3].data = (void*) &file->f_pos;
	    argumenty.arg[4].data = (void*) read;
	    
	    wynik.len = 1;
	    wynik.arg = malloc(sizeof(struct arg_t) * wynik.len);
	    wynik.arg[0].data = (void*) &ret;
	    if (call_dynfunc("sec_read", &arg, &wynik) == CALL_OK) {
	     /* ewentualne prace administracyjne */
	    }
	    else 
	    /* a to jest oryginalny fragment kodu jądra */
		ret = read(file, buf, count, &file->f_pos);
	}
	.
	.
	.
}

aby zaimplementować ten mechanizm należy zaimplementować w jądrze trzy
funkcje:
int reg_dynfun(static char* "name",func funkcja);
int ureg_dynfun(static char* "name");

wartości zwracane:
#define REG_OK	0
#define REG_ERR -1

oznaczają odpowiednio sukces bądź porażkę w czasie rejestrowania 
(odrejestrowywania) funkcji.

By wykonać jakąś funkcję wywołujemy:
int call_dynfun(static char* "name", marg_t* arg, marg_t* wynik);

wartości zwracane 
#define CALL_OK		0	-- wołanie funkcji powiodło się wynik tej 
				    funkcji jest na zmiennej wynik
#define CALL_NOF	-1	-- funkcja o podanej nazwie nie istnieje
#define CALL_ERR	-2	-- błąd wykonania funkcji call_dynfun()


struct arg_t{
	char * data;
};

struct marg_t{
    int len;
    struct arg_t * arg;
};

typedef void (*func) (struct marg_t*,struct marg_t*);

Uwagi:
    * Wykonujemy ostatnią zarejestrowaną funkcję.
    * Zakładamy, że korzystający z funkcji, którą identyfikujemy jakimś 
      napisem zna jej interfejs!
    * wszystkie konwersje powinny być wykonywane w sposób jawny!


Po zaimplementowaniu tego mechanizmu należy za jego pomocą zrealizować
proste szyfrowanie plików. Każdy użytkownik może określić hasło, wówczas
wszystkie operacje zapisu/odczytu plików, których jest właścicielem, będą
korzystać z operacji szyfrowania/deszyfrowania; jeśli hasła nie określi,
operacje te będą działać jak zwykle. Jeśli użytkownik chce korzystać z
operacji szyfrowania, to na początku użytkownik musi wpisać hasło do
ustalonego pliku w /proc (nazwijmy go haslo); moduł zapamiętuje to hasło dla
danego użytkownika (każdy użytkownik ma zatem osobne hasło, a moduł
przechowuje tablicę indeksowaną użytkownikami); jeśli hasło nie zostało
ustawione lub zostało ustawione na puste, to operacje zapisu i odczytu
działają jak zwykle.

A zatem każda operacja dostępu do pliku należącego do danego użytkownika
będzie działać jak następuje:
    dla każdego bajtu "b" o numerze "n" z pliku obliczamy
    b:=b xor haslo[n mod strlen(haslo)];
    przykładowa zmiana jaka powinna nastąpić w pliku fs/read_write.c została 
    podana powyżej.

Podane rozwiązanie problemu szyfrowania/deszyfrowania plików nie jest
oczywiście wygodne w użyciu ani praktyczne, jednak ilustruje ogólny
mechanizm. Zaprojektowanie przeróbki, która uczyni go wygodniejszym, nie
powinno być trudne, acz oczywiście skomplikowałoby implementację.

Zmiany w jądrze powinny być nadesłane w postaci łat.
Do rozwiązania należy dołączyć odpowiednie skrypty, które zainstalują odpowiednie 
zmiany w kodzie jądra i makefile do programów i modułów z nich korzystających.

Autor: Piotr Kozieradzki