Procesy
Algorytm Exec
Autor: Bartłomiej Kijanka
1.0 Interfejs programisty
Funkcje z rodziny exec* zmieniają kontekst danego procesu na
podstawie programu wykonywalnego umieszczonego w pliku dyskowym.
Kod, jakim jest program na dysku, staje się działającym w
pamięci operacyjnej procesem. Do rodziny exec należą funkcje:
int execl ( const char *path, const char *arg, ...);
int execlp( const char *file, const char *arg, ...);
int execle( const char *path, const char *arg , ..., char *const envp[]);
int execv ( const char *path, char *const argv[]);
int execvp( const char *file, char *const argv[]);
int execve( const char *filename, char *const argv [], char *const envp[]);
Różniące się składniowo, jednak wszystkie wywołują
ostatecznie funkcję sys_execve z pliku process.c, która
wywołuje do_execve z pliku exec.c.
2.0 Struktura linux_binprm
Przed usunięciem z pamięci kodu procesu wykonującego
do_execve oraz jego kontekst, trzeba zapamiętać argumenty i
zmienne środowiskowe. W tym celu korzystamy ze struktury
linux_binprm:
struct linux_binprm{
char buf[BINPRM_BUF_SIZE]; - bufor, do którego ładowany jest początkowy fragment pliku wykonywalnego
struct page *page[MAX_ARG_PAGES]; - tablica stron zawierających argumenty wywołania i środowisko
unsigned long p; - wskażnik do operacji na tablicy page
int sh_bang; - flaga zapewniająca, że interpreterem języka skryptowego nie będzie program napisany w języku skryptowym
struct file * file; - struktura zawierająca informacje o pliku
int e_uid, e_gid; - identyfikator użytkownika i grupy użytkowników dla nowego programu
kernel_cap_t cap_inheritable, cap_permitted, cap_effective; - dziedziczenie 'możliwości' (ang. capabilities) procesu, np. praw roota
int argc, envc; - liczba parametrów i argumentów środowiskowych
char * filename; - nazwa pliku (ze ścieżką)
unsigned long loader, exec; - pomocnicze zmienne używane podczas ładowania
};
3.0 Działanie
Wewnętrzna funkcja
int do_execve(char * filename, char ** argv, char ** envp, struct pt_regs * regs)
zarządza całym algorytmem exec; sama funkcja jest bardzo
krótka, jednak wywołuje dużą ilość funkcji pomocniczych.
Algorytm działania do_execve():
- Wywołanie funkcji open_exec(), która:
- sprawdza czy plik istnieje
- czy jest wykonywalny
- czy możemy go otworzyć
- W przypadku powodzenia tych operacji zwracany
jest wskaźnik do struktury file.
- Inicjalizacja niektórych pól struktury linux_binprm,
min. tablicy page i struktury file
- Wywołanie funkcji prepare_binprm; przeprowadza
ona sprawdzenie możliwości wykonania pliku i dalszą
inicjację struktury linux_binprm. Jej działanie jest
następujące:
- Sprawdza ,czy:
- plik jest wykonywalny - czy jest
przynajmniej jeden bit zezwalający na
wykonanie; chyba nie potrzebne w tym
miejscu (już było sprawdzenie
wykonywalności - na początku, w funkcji
open_exec). Komentarz do tego
sprawdzenia:
/* Huh? We had already checked for
MAY_EXEC, WTF do we check this? */
pozostawiam bez komentarza :D
- mamy prawo do wykonania pliku
- plik ma atrybut s, jeżeli tak to
sprawdzenie odpowiednich uprawnień
- Wczytuje pierwsze BINPRM_BUF_SIZE (128) bajtów z
pliku, w celu ustalenia formatu pliku
wykonywalnego
- Kopiowanie napisów: nazwy pliku, parametrów wywołania
i zmiennych środowiska do struktury linux_binprm;
zajmują się tym funkcje: copy_strings_kernel
(nazwa pliku) i copy_strings(reszta)
- Wywołanie funkcji search_binary_handler; wyszukuje ona
moduł ładujący dla danego formatu pliku wykonywalnego.
- Wywołuje są po kolei funkcje ładujące dla
wszystkich formatów, aż jakaś rozpozna dany
format
- Jeśli żaden nie rozpozna, to próbuje
załadować moduł któremu się uda (zajmuje
się tym proces kerneld - jeśli jest obecny w
systemie)
- Znowu wywołuje wszystkie funkcje ładujące,
żeby sprawdzić czy teraz któraś rozpozna nasz
plik.
- Jeśli któraś funkcja rozpozna format, to
ładuje plik do pamięci i wykonuje.
4.0 Formaty plików wykonywalnych
W wersji jądra 2.4 standardowo rozpoznawane są formaty:
script, a.out, ELF, java, em86.
Przykładowo, dla formatu ELF ładowaniem programu zajmuje się
funkcja:.
- static int load_elf_binary(struct linux_binprm *
bprm, struct pt_regs * regs) (plik fs/binfmt_elf.c)
Jej algorytm (w dużym skrócie) to:
- Sprawdzenie, czy to rzeczywiście jest format ELF
- Wczytanie nagłówków pliku
- Sprawdzenie, czy potrzebny jest interpreter; jeśli tak
to go ładujemy
- Wymazanie starego kontekstu funkcją flush_old_exec()
z pliku exec.c. Zwalnia ona segmenty danych, kodu i
stosu, (o ile nikt inny z tych rzeczy nie korzystał)
przywraca standardową obsługę sygnałów oraz zamyka
niektóre deskryptory plików - takie które miały
ustawioną flagę FD_CLOEXEC. Pliki otwarte poleceniem
open oraz pipe-y standardowo nie mają jej ustawionej -
więc 'przeżywają' execa.
- Przy pomocy funkcji start_thread() (plik
processor.h) uruchamiamy nowy kod.