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