Ten dokument zawiera przetłumaczony i w swobodny sposób pozmieniany tekst standardu ELF. Dokument ten pochodzi z strony Briana Raitera , i został przetłumaczony i użyty za jego zgodą. Tłumaczenie A.D.Danilecki adanilecki _malpa_ cs.put.poznan.pl. Wszelkie błedy są efektem mojej kiepskiej znajomości angielskiego i nie powinny obciążać oryginalnych autorów. Proszę także o uwagi na temat jakości tłumaczenia i propozycje lepszych tłumaczeń kierować na adres adanilecki _malpa_ cs.put.poznan.pl .
lepsze kulawe tłumaczenie niż żadne? Czy też lepsze żadne tłumaczenie niż
kiepskie?
Jeśli znasz dobrze angielski nie zawracaj sobie głowy lekturą tego
dokumentu i znajdź jego angielski oryginał.
W tej chwili dokument posaida wiele niespójności, błędów,
tylko częściowo przeniesiony jest na.php i większość linków nie działa.
Tłumacz (czyli ja :) ) będzie poprawiał dokument w wolnym czasie, jednakże
nie będzie uprzedzał o zamieszczaniu poprawek. Czekaj na wersję 0.7 lub
co jakiś czas (co dwa tygodnie co najmniej) sprawdzaj moją stronę www.
Format pliku wykonywalnego i łączonego (Executable and Linking Format) pierwotnie został wymyślony i opublikowany przez UNIX System Laboratories (USL) jako część interfejsu binarnego aplikacji (Application Binary Interface) czyli tzw. ABI. Komitet standardów interfejsów narzędzi (Tool Interface Standards committee, TIS) wybrał ewoluujący standard ELF jako format przenośnego pliku .o dla najprzeróżniejszych systemów operacyjnych.
Intencją standardu ELF jest zunifikowanie rozwoju oprogramowania poprzez dostarczenie programistom zbioru binarnych interfejsów które będą dostępne dla wielu środowisk programistycznych i wielu systemów operacyjnych. Powinno to zredukować liczbę różnych implementacji interfejsu, w ten sposób zmniejszając potrzebę przekodowywania i rekompilacji kodu.
Ten dokument jest przeznaczony dla programistów którzy tworzą pliki wykonywalne lub obiektowe dla różnych 32bitowych środowisk systemów operacyjnych.
Zauważ: Wszystkie odniesienia do architektury x86 zostały zmienione na odniesienia do architekury Intela.
Część 1 opisuje intelowski ABI dla plików obiektowych, nazywany ELF (Executable and Linking Format). Istnieją trzy główne typy takich plików:
Po materiale wprowadzającym, część pierwsza skupia się na formacie pliku i sposobie odniesienia go do budowy programów. Część druga opisuje również pliki obiektowe, koncentrując się na informacjach niezbędnych do uruchomienia programu.
do spisu treści tej specyfikacji
Pliki obiektowe biorą udział w łączeniu (linkowaniu) programu (budowaniu programu) oraz wykonywaniu programu. Dla wygody i efektywności, format pliku obiektowego zapewnia równoległe widoki zawartości pliku, odwzierciedlające różne potrzeby tych działań. Rysunek 1-1 pokazuje organizację pliku obiektowego.
+ Rysunek 1-1: Format pliku obiektowego.
Widok ze strony linkera | Widok od strony wykonywania |
Nagłówek ELF | Nagłówek ELF |
Tablica nagłowka programu (opcja) | Tablica nagłówka programu |
Sekcja 1 | Segment 1 |
... | Segment 2 |
Sekcja n | ... |
Tablica nagłówków sekcji | Tablica nagłówków sekcji (opcja) |
Nagłówek ELF znajduje się na początku i zawiera ,,mapę drogową'' opisującą sposób organizacji pliku. Sekcje zawierają większą część informacji pliku obiektowego dla widoku dla linkera: instrukcje, dane, tablice symboli, informacje o przemieszczeniach (relokacji, ang. relocations), itd. Opis sekcji specjalnych pojawi się później w części pierwszej. Część druga omawia segmenty i widok pliku od strony wykonywania programu.
Tablica nagłówku programu , jeśli obecna, mówi systemowi w jaki sposób stworzyć obraz procesu. Pliku używane do budowy obrazu procesu (wykonania programu) muszą mieć tablicę nagłowka programu; pliki relokacyjne nie. Tablica nagłówków sekcji zawiera informacje opisujące sekcje pliku. Każda sekcja posiada wpis w tablicy; każdy taki wpis podaje informacje takie jak nazwa sekcji, rozmiar itd. Pliki używane podczas łączenia muszą mieć tablicę nagłówków sekcji, inne pliki obiektowe mogą lecz nie muszą jej zawierać.
ZAUWAŻ: Chociaż rysunek pokazuje tablicę nagłówka programu natychmiast po nagłówku ELF, a tablicę nagłówków sekcji dopiero po wszystkich sekcjach, właściwa struktura plików może się różnić od podanej. Co więcej, sekcje i segmenty nie posiadają żadnego ściśle określonego porządku. Tylko nagłówek ELF posiada określoną pozycję w pliku.
do spisu treści tej specyfikacji
Tak jak to tutaj opisujemy, format pliku obiektowego wspiera rozmaite procesory o 8-bitowej i 32-bitowej architekturze. Jednakże jego intencją jest rozszerzalność również na inne więcej- lub mniej- bitowe architektury. Plik obiektowy reprezentuje więc pewne dane kontrolne z formatem niezależnym od sprzętu, czyniąc możliwym zidentyfikowanie plików obiektowych i interpretację ich zawartości w prosty sposób. Pozostała część danych zawiera rozkazy dla procesora docelowego, niezależnie od tego na jakim sprzęcie plik został utworzony.
+ Rysunek 1-2: 32-Bitowe typy danych
Nazwa | Rozmiar | Wyrównywanie | Cel |
Elf32_Addr | 4 | 4 | Adresy w programie bez znaku |
Elf32_Half | 2 | 2 | Liczby całkowite średnie bez znaku |
Elf32_Off | 4 | 4 | Offset w pliku bez znaku |
Elf32_Sword | 4 | 4 | Wielkie liczby całkowite ze znakiem |
Elf32_Word | 4 | 4 | Wielkie liczby całkowite bez znaku |
unsigned char | 1 | 1 | Małe liczby całkowite bez znaku |
Wszystkie struktury danych które definicuje format pliku podążają za ,,naturalnymi'' wskazówkami co do rozmiaru i wyrównywania odpowiednich klas. Jeśli jest to potrzebne, struktury danych zawierają z góry określone wypełnianie (ang. padding) dla zapewnienia odpowiedniego wyrównania (ang. alignment) dla 4-bajtowych obiektów, tak aby zapewniać wielkość struktur będącą wielokrotnościami czwórki, itd. Dane posiadają również odpowiednie wyrównanie od początku pliku, to znaczy że członek typu Elf32_Addr zostanie wyrównany w 4-bajtowych granicach wewnątrz pliku.
Dla zapewnienia przenośności, ELF nie używa pól bitowych.
do spisu treści tej specyfikacji
Niektóre struktury kontrolne w plikach obiektowych mogą rosnąć, ponieważ nagłówek ELF zawiera ich właściwe rozmiary. Jeżeli format pliku obiektowego się zmienia, program może odkryć że struktury kontrolne są mniejsze lub większe niż tego oczekiwał. Program może więc ignorować dodatkową informację. Traktowanie brakującej informacji zależy od kontekstu i zostanie wyspecyfikowane gdy/jeśli zostaną zdefiniowane dodatkowe rozszerzenia.
+ Rysunek 1-3: Nagłówek ELF
#define EI_NIDENT 16 | |
typedef struct { | |
unsigned char | e_ident[EI_NIDENT]; |
Elf32_Half | e_type; |
Elf32_Half | e_machine; |
Elf32_Word | e_version; |
Elf32_Addr | e_entry; |
Elf32_Off | e_phoff; |
Elf32_Off | e_shoff; |
Elf32_Word | e_flags; |
Elf32_Half | e_ehsize; |
Elf32_Half | e_phentsize; |
Elf32_Half | e_phnum; |
Elf32_Half | e_shentsize; |
Elf32_Half | e_shnum; |
Elf32_Half | e_shstrndx |
} Elf32_Ehdr; |
Początkowe bajty znaczą plik jako plik obiektowy i zapewniają dane niezależne od sprzętu na której przyjdzie dekodować i interpretować zawartość pliku. Dokładny opis pojawia się niżej, w ,,Identyfikacji ELF''.
To pole identyfikuje typ pliku obiektowego.
Nazwa | Wartość | Znaczenie |
ET_NONE | 0 | Plik bez określonego typu |
ET_REL | 1 | Plik relokacyjny(przemieszczalny) |
ET_EXEC | 2 | Plik wykonywalny |
ET_DYN | 3 | Współdzielony plik obiektowy |
ET_CORE | 4 | Plik Core |
ET_LOPROC | 0xff00 | Zależny od procesora |
ET_HIPROC | 0xffff | Zależny od procesora |
Chociaż zawartość plików ,,core'' jest niezdefiniowana, typ ET_CORE jest zarezerwowany do oznaczania takich plików. Wartości od ET_LOPROC do ET_HIPROC są zarezerwowane dla semantyki specyficznej dla procesora. Inne wartości są zarezerwowane i, gdy zajdzie taka potrzeba, będą przydzielane nowym typom plików.
Wartość tego pola podaje wymaganą architekturę dla danego pliku.
Nazwa | Wartość | Znaczenie |
EM_NONE | 0 | Brak specyfikacji architektury |
EM_M32 | 1 | AT&T WE 32100 |
EM_SPARC | 2 | SPARC |
EM_386 | 3 | Intel 80386 |
EM_68K | 4 | Motorola 68000 |
EM_88K | 5 | Motorola 88000 |
EM_860 | 7 | Intel 80860 |
EM_MIPS | 8 | MIPS RS3000 |
Inne wartości są zarezerwowane i będą przydzielane nowym architekturom gdy zajdzie taka potrzeba. Nazwy ELF specyficzne dla procesora używają nazw architektur w celu rozróżnienia. Na przykład, flagi wspomniane niżej używają przedrostka EF_; flaga nazwana WIDGET dla architektury EM_XYZ będzie się nazywać EM_XYZ_WIDGET.
To pole identyfikuje wersję pliku obiektowego.
Nazwa | Wartość | Znaczenie |
EV_NONE | 0 | Błędna wersja |
EV_CURRENT | 1 | Wersja bieżąca |
Wartość 1 ozancza oryginalny format pliku; gdy pojawią się rozszerzenia, powstaną nowe wersje z wyższymi numerami. Wartość EV_CURRENT, mimo że podana wyżej jako 1, będzie się zmieniać jeśli bedzie to niezbędne to odzwierciedlenia bieżącej wersji.
To pole podaje adres wirtualny pod który system przekaże kontrolę, startując w ten sposób proces. Jeżeli plik nie posiada takiego miejsca, to pole przechowuje zero.
To pole zawiera offset tablicy nagłówka programu w bajtach. Jeśli plik nie posiada tablicy nagłówka programu, to pole zawiera zero.
To pole zawiera offset tablicy nagłówków sekcji w bajtach. Jeżeli plik nie posiada takiej tablicy, to pole zawiera zero.
To pole zawiera flagi specyficzne dla procesora skojarzone z tym plikiem.
Nazwy flag posiadają formę EF_
To pole zawiera rozmiar nagłówka ELF w bajtach.
To pole zawiera rozmiar w bajtach jednej pozycji w
tablicy nagłówka
programu.
Wszystkie pola są tego samego rozmiaru.
To pole zawiera liczbę pól w tablicy nagłówka
programu. Mnożąc
e_phentsize
przez e_phnum otrzymujemy rozmiar tablicy w bajtach. Jeżeli plik nie posaida
tablicy nagłówka programu, e_phnum zawiera zero.
To pole przechowuje rozmiar nagłówka sekcji w bajtach. Nagłówek sekcji
jest jednym polem w tablicy nagłówków sekcji. Wszystkie pola są jednakowego
rozmiaru.
To pole przechowuje liczbę pól w tablicy nagłówków
sekcji. W ten sposób
mnożąc e_shnum przez e_shentsize otrzymujemy rozmiar w bajtach tablicy
nagłówków sekcji. Jeżeli plik nie posiada tablicy nagłówków sekcji, e_shnum
zawiera zero.
To pole zawiera index pól dla tablicy nagłówków sekcji skojarzony z
tablicą
nazw sekcji. Jeśli plik nie posiada takiej tablicy, to pole zawiera
wartość SHN_UNDEF. Zobacz ,,Sekcje'' i
,,Tablica Łańcuchów'' poniżej aby
uzyskać więcej informacji.
Identyfikacja ELF Jak już wspomniano wyżej, ELF zapewnia plikowi obiektowemu ramę do współpracy z wieloma procesorami, wieloma sposobami kodowania danych, i wieloma klasami architektur. Aby wpierać te rodziny plików obiektowych, początkowe bajty pliku mówią jak interpretować plik, niezależnie od procesora na którym się o to dowiadujemy i niezależnie od pozostałej zawartości pliku. Początkowe bajty nagłówka ELF (i pliku obiektowego) odpowiadają polu e_ident. + Rysunek 1-4: e_ident[] indeksy identyfikacji Nazwa Wartość Przeznaczenie ==== ===== ======= EI_MAG0 0 Identifikacja pliku EI_MAG1 1 Identifikacja pliku EI_MAG2 2 Identifikacja pliku EI_MAG3 3 Identifikacja pliku EI_CLASS 4 Klasa pliku EI_DATA 5 Kodowanie danych EI_VERSION 6 Wersja pliku EI_PAD 7 Początek dopełniających bajtów EI_NIDENT 16 rozmiar e_ident[] Te indekst dotyczą bajtów które przechowują następujące wartości. * od EI_MAG0 do EI_MAG3 Pierwsze 4 bajty pliku przechowują ,,magic number'' (liczbę magiczną :) , która identyfikuje plik obiektowy ELF. Nazwa Wartość Pozycja ==== ===== ======== ELFMAG0 0x7f e_ident[EI_MAG0] ELFMAG1 'E' e_ident[EI_MAG1] ELFMAG2 'L' e_ident[EI_MAG2] ELFMAG3 'F' e_ident[EI_MAG3] * EI_CLASS Kolejny bajt, e_ident[EI_CLASS], identyfikuje klasę pliku. Nazwa Wartość Znaczenie ==== ===== ======= ELFCLASSNONE 0 Błędna klasa ELFCLASS32 1 obiekty 32-bit ELFCLASS64 2 obiekty 64-bit Format pliku jest przeznaczony by być przenośnym pomiędzy architekturami o różnych rozmiarach (np 32-bitowe, 64-bitowe), bez wyznaczania sprzętu o rozmiarze najmniejszym lub największym. Klasa ELFCLASS32 wspiera architekturę o plikach i rozmiarze pamięci wirtualnej do 4Gb; używa podstawowych typów zdefiniowanych wyżej. Klasa ELFCLASS64 jest zarezerwowana dla architektur 64-bitowych. Jej pojawienie się tutaj pokazuja jak plik obiektowy może się zmienić, ale w innym przypadku format 64bitowy jest niewyspecyfikowany. Inne klasy będą definiowane w miarę potrzeby, z różnymi typami prostymi i rozmiarami dla danych plików obiektowych. * EI_DATA Bajt e_ident[EI_DATA] definiuje sposób kodowania danych specyficznych dla procesora w pliku obiektowym. Następujące kodowania są zdefiniowane w tej chwili Nazwa Wartość Znaczenie ==== ===== ======= ELFDATANONE 0 Błędny sposób kodowania ELFDATA2LSB 1 Patrz niżej ELFDATA2MSB 2 Patrz niżej Więcej sposobów na temat kodowania pojawia się poniżej. Inne wartości są zarezerwowane i będą przydzielane nowy sposobom kodowanie jeśli okaże się to potrzebne. * EI_VERSION Bajt e_ident[EI_VERSION] definiuje numer wersji nagłówka ELF. Obecnie ta wartość musi sie równać EV_CURRENT, tak jak to objaśniono wyżej dla e_version. * EI_PAD Ta wartość oznacza początek nieużywanych bajtów w e_ident. Te bajty są zarezerwowane i ustawione na zero; programy czytające pliki obiektowe powinny je ignorować. Wartość EI_PAD zmieni się w przyszłości gdy obecnie nieużywane bajty otrzymają jakieś znaczenie. Kodowanie danych pliku specyfikuje jak interpretować proste obiekty w pliku. Jak opisano wyżej, klasa ELFCLASS32 używa obiektów które zajmują 1, 2, i 4 bajty. Pod zdefiniowanym kodowaniem, obiekty są reprezentowane jak pokazano niżej. Numery bajtów występują w górnych lewych rogach. Kodowanie ELFDATA2LSB definiuje 2-komplementarne(dopełniane do 2) wartości, z najmniej znaczącym bajtem zajmującym najniższy adres . + Rysunek 1-5: Kodowanie danych ELFDATA2LSB 0------+ 0x0102 | 01 | +------+ 0------1------+ 0x010204 | 02 | 01 | +------+------+ 0------1------2------3------+ 0x01020304 | 04 | 03 | 02 | 01 | +------+------+------+------+ ELFDATA2MSB definiuje 2-komplementarne wartości, z najbardziej znaczącym bajtem zajmującym najniższy adres. + Rysunek 1-6: Kodowanie danych ELFDATA2MSB 0------+ 0x0102 | 01 | +------+ 0------1------+ 0x010204 | 01 | 02 | +------+------+ 0------1------2------3------+ 0x01020304 | 01 | 02 | 03 | 04 | +------+------+------+------+ Informacje o architekturze Dla identyfikacji pliku w e_ident, 32-bitowa architektura Intela wymaga następujących wartości. + Rysunek 1-7: 32-bitowa identyfikacja dla architektury Intela, e_ident Pozycja Wartość ======== ===== e_ident[EI_CLASS] ELFCLASS32 e_ident[EI_DATA] ELFDATA2LSB Identyfikacja procesora pozostaje w polu e_machine nagłówka ELF i musi posiadać wartość EM_386 Pole e_flags nagłówka ELF przechowuje flagi bitowe związane z plikiem. 32-bitowa architektura Intela nie definiuje zadnych flag, tak więc to pole zawiera zero. =========================== Sekcje =========================== Tablica nagłówków sekcji pliku obiektowego pozwala zlokalizować wszystkie sekcje pliku. Tablica nagłówków sekcji jest tablicą struktur Elf32_Shdr opisanych niżej. Indeks tablicy nagłówków sekcji jest wskaźnikiem do tej tablicy. Pole e_shoff nagłówka ELF podaje offset w bajtach od początku pliku do tablicy nagłówków sekcji; e_shnum mówi ile pozycji zawiera tablica; e_shentsize podaje rozmiar w bajtach każdej pozycji. Niektóre indeksy tablicy nagłówków sekcji są zarezerwowane; plik obiektowy nie może posiadać sekcji dla tych specjalnych indeksów. + Rysunek 1-8 : Specjalne wartości indeksów sekcji Nazwa Wartość ==== ===== SHN_UNDEF 0 SHN_LORESERVE 0xff00 SHN_LOPROC 0xff00 SHN_HIPROC 0xff1f SHN_ABS 0xfff1 SHN_COMMON 0xfff2 SHN_HIRESERVE 0xffff * SHN_UNDEF Ta wartość znaczy niezdefiniowane, brakujące lub w inny sposób pozbawione znaczenie odwołanie do sekcji. Na przykład symbol ,,defined'' w odniesieniu do sekcji numer SHN_UNDEF jest symbolem niezdefiniowanym. ZAUWAŻ: Chociaż indeks 0 jest zarezerwowany jako wartość niezdefiniowana, tablica nagłówków sekcji zawiera pozycję dla indeksu 0. To znaczy, jeśli pole nagłówka ELF e_shnum wynosi 6, czyli że plik ma 6 pozycji w tablicy nagłówków sekcji, to posiadają one numery od 0 do 5. Zawartość początkowej pozycji jest zdefiniowana później w tej sekcji. * SHN_LORESERVE Ta wartość definiuje dolną granicę zbioru zarezerwowanych indeksów. * od SHN_LOPROC do SHN_HIPROC Wartości w tym zbiorze są zarezerwowane dla semantyki specyficznej dla procesora. * SHN_ABS Ta wartość definiuje bezwzlędną wartość dla odpowiadającego odwołania. Na przykład symbole zdefiniowane jako względne dla sekcji numer SHN_ABS posiadają bezwzględne, absolutne wartości i nie dotyczy ich relokacja (ang. relocation). * SHN_COMMON Symbole zdefiniowane w tej sekcji są symbolami wspólnymi (common symbols), tak jak FORTRAN COMMON albo niezalokowane zewnętrzne zmienne C. * SHN_HIRESERVE Ta wartość definiuje górną granicę zbioru zarezerwowanych indeksów. System rezerwuje indeksy pomiędzy SHN_LORESERVE i SHN_HIRESERVE włącznie; wartości te nie odnoszą się do tablicy nagłówków sekcji, to jest tablica nagłówków sekcji nie zawiera pozycji dla zarezerwowanych indeksów. Sekcje zawierają wszystkie informacje w pliku obiektowym, poza nagłówkiem ELF, tablicą nagłówka programu i tablicą nagłówków sekcji. Co więcej, sekcje pliku obiektowego spełniają kilka warunków. * Każda sekcja w pliku obiektowym posiada dokładnie jeden nagłówek sekcji który ją opisuje. Mogą istnieć nagłówki sekcji nie posiadające sekcji. * Każda sekcja zajmuje jedną ciągłą (możliwe że pustą) sekwencję bajtów w pliku. * Sekcje w pliku nie mogą się nakładać. Żaden bajt nie może być w więcej niż jednej sekcji. * Plik obiektowy może posiadać przestrzeń nieaktywną. Różne nagłówki i sekcje mogą nie pokrywać każdego bajtu w pliku obiektowym. Zawartość takich danych nieaktywnych jest niezdefiniowana. Nagłówek sekcji posiada następującą strukturę. + Rysunek 1-9: Nagłówek sekcji typedef struct { Elf32_Word sh_name; Elf32_Word sh_type; Elf32_Word sh_flags; Elf32_Addr sh_addr; Elf32_Off sh_offset; Elf32_Word sh_size; Elf32_Word sh_link; Elf32_Word sh_info; Elf32_Word sh_addralign; Elf32_Word sh_entsize; } Elf32_Shdr; * sh_name To pole specyfikuje nazwę sekcji. Jego wartością jest wskaźnik do tablicy nazw nagłówków sekcji [zobacz ,,Tablica nazw'' poniżej], podając lokalizację łańcucha zakończonego znakiem pustym. * sh_type To pole szufladkuje zawartość i semantykę sekcji. Typy sekcji są opisane niżej. * sh_flags Sekcje wspierają jednobitowe flagi opisujące różne atrybuty . Definicje flag pojawiają się niżej. * sh_addr Jeśli sekcja pojawia się w obrazie pamięci procesu, to pole podaje pole w którym pierwszy bajt sekcji się pojawia. Wpw, to pole zawiera zero. * sh_offset Wartość tego pola podaje offset w bajtach od początku pliku do pierwszego bajtu sekcji. jeden typ sekcji, SHT_NOBITS opisany niżej, nie zajmuje miejsca w pliku, i jego sh_offset zajmuje koncepcyjne (ang conceptual??) umieszczenie w pliku. * sh_size To pole podaje rozmiar sekcji w bajtach. Jeżeli typ sekcji nie jest SHT_NOBITS, sekcja zajmuje sh_size bajtów w pliku. Sekcja typu SHT_NOBITS może posiadać rozmair niezerowy, ale i tak nie zajmuje miejsca w pliku. * sh_link To pole zawiera łącze do wskaźnika na tablicę nagłówków sekcji (ang.section header table index link), którego interpretacja zależy od typu sekcji. Tablica poniżej opisuje wartości. * sh_info To pole zawiera dodatkową informację, której interpretacja zależy od typu sekcji. Tablica poniżej opisuje wartości jakie może przybierać. * sh_addralign Niektóre sekcje posiadają ograniczenie na wyrównywanie (alignment) adresów. na przykład, jeśli sekcja zawiera podwójne słowo, system musi zapewnić wyrównanie do podwójnego słowa dla całej sekcji. Oznacza to że wartość pola sh_addr modulo wartość sh_addralign musi wynosić zero (ang. That is, the value of sh_addr must be congruent to 0, modulo the value of sh_addralign.) Obecnie tylko zero i dodatnie całkowite potęgi dwójki są dozwolone. Wartość 0 i 1 oznacza że sekcja nie posiada ograniczeń na wyrównywanie. * sh_entsize Niektóre sekcje przechowują tablice o stałych rozmiarach pozycji, takie jak tablice symboli. Dla takich sekcji to pole podaje rozmiar w bajtach każdej pozycji. Pole to zawiera zero jeśli sekcja nie posiada tablicy o stałych rozmiarach pozycji. Pole sh_type nagłówka sekcji podaje sematykę sekcji. + Rysunek 1-10: Typy sekcji, sh_type Name Value ==== ===== SHT_NULL 0 SHT_PROGBITS 1 SHT_SYMTAB 2 SHT_STRTAB 3 SHT_RELA 4 SHT_HASH 5 SHT_DYNAMIC 6 SHT_NOTE 7 SHT_NOBITS 8 SHT_REL 9 SHT_SHLIB 10 SHT_DYNSYM 11 SHT_LOPROC 0x70000000 SHT_HIPROC 0x7fffffff SHT_LOUSER 0x80000000 SHT_HIUSER 0xffffffff * SHT_NULL Ta wartość oznacza że nagłowek sekcji jest nieaktywny i nie odnosi się do żadnej istniejącej sekcji. Inne pola nagłówka sekcji posiadają niezdefiniowane, dowolne wartości. * SHT_PROGBITS Ta sekcja zawiera informacje zdefiniowane przez program, których format i znaczenie jest całkowicie zależne od programu. * SHT_SYMTAB and SHT_DYNSYM Ta sekcja zawiera tablicę symboli. Obecnie plik obiektowy może posiadać tylko jedną sekcję każdego typu, lecz to ograniczenie może zostać złagodzone w przyszłości. Zazwyczaj SHT_SYMTAB zapewnia symbole do edytowania łączy/linków (ang. link editing), chociaż może również zostać użyte do dynamicznego łączenia (ang. dynamic linking). Plik obiektowy może również posiadać sekcję SHT_DYNSYM, która zawiera minimalny zbiór symboli potrzebnych przy dynamicznym łączeniu. Zobacz ,,Tablice symboli'' by dowiedzieć się szczegółów. * SHT_STRTAB Ta sekcja zawiera tablicę nazw (ang. string table). Plik obiektowy może posiadać wiele sekcji tego typu. Po więcej szczegółów udaj się do rozdziału ,,Tablica Nazw''. * SHT_RELA (?) Sekcja zawiera pozycje relokacji z góry nadanymi dodatkami, takimi jak typ Elf32_Rela dla 32-bitowej klasy plików obiektowych. (???? ang. The section holds relocation entries with explicit addends, such as type Elf32_Rela for the 32-bit class of object files). Plik obiektowy może posiadać wiele relokacyjnych sekcji. Zobacz ,,Relokacja'' poniżej po więcej szczegółów. * SHT_HASH Sekcja zawiera haszową (mieszającą) tablicę symboli. Wszystkie obiekty biorące udział w dynamicznym łączeniu muszą zawierać taką tablicę. Obecnie plik obiektowy może posiadać tylko jedną haszową tablicę, ale to ograniczenie może zostać złagodzone w przyszłości. Zobacz ,,Tablica Haszowa'' w części drugiej po więcej szczegółów. * SHT_DYNAMIC Sekcja zawiera informacje dla dynamicznego łączenia. Obecnie plik obiektowy może posiadać tylko jedną dynamiczną sekcję, ale to ograniczenie może ulec złagodzeniu w przyszłości. Zobacz ,,Sekcja dynamiczna'' w części drugiej po więcej szczegółów. * SHT_NOTE Sekcja zawiera informacje które znaczą w pewien sposób plik. Zobacz ,,Sekcje z uwagami'' w części drugiej po więcej szczegółów. * SHT_NOBITS Sekcja tego typu nie zajmuje przestrzenie w pliku ale poza tym przypomina sekcję SHT_PROGBITS. Chociaż ta sekcja nie zawiera bajtów, pole sh_offset zawiera koncepcyjny (ang. conceptual) ofset pliku. * SHT_REL Sekcja zawiera pozycje relokacji bez z góry nadanych dodatków takich jak typ Elf32_Rel dla 32bitowych klas plików obiektowych (?). Plik obiektowy może posiadać wiele sekcji relokacyjnych. Zobacz ,,Relokacja'' poniżej po więcej szczegółów. * SHT_SHLIB Ta sekcja jest zarezerwowana ale nie posiada wyspecyfikowanej semantylo. Programy zawierające tą sekcję nie są zgodne z ABI (aplication binary interface). * od SHT_LOPROC do SHT_HIPROC Wartości w tym zbiorze są zarezerwowane dla semantyki zależnej od procesora. * SHT_LOUSER Ta wartość podaje dolne ograniczenie zbioru indeksów zarezerwowanych dla programów użytkowych. * SHT_HIUSER Ta wartość podaje górną granicę indeksów zarezerwowanych dla programów użytkowych. Typy sekcji pomiędzy SHT_LOUSER i SHT_HIUSER mogą być używane przez aplikacje, bez wchodzenia w konflikt z bieżącymi lub zdefiniowanymi w przyszłości systemowymi typami sekcji. Inne wartości określające typy sekcji są zarezerwowane. Tak jak wspomniano wcześniej, nagłówek sekcji dla indeksu 0 (SHN_UNDEF) istnieje, nawet pomimo tego że indeks ten znaczy niezdefiniowane odnośniki do sekcji. Ta pozycja zawiera : + Rysunek 1-11: Pozycja w tablicy nagłówków sekcji: Indeks 0 Nazwa Wartość Uwagi ==== ===== ==== sh_name 0 brak nazwy sh_type SHT_NULL Nieaktywna sh_flags 0 Brak flag sh_addr 0 Brak adresu sh_offset 0 Brak ofestu pliku sh_size 0 Brak rozmiaru sh_link SHN_UNDEF Brak informacji o łączeniu sh_info 0 Brak informacji pomocniczych sh_addralign 0 Brak dopełniania (alignment) sh_entsize 0 Brak pozycji Pole sh_flags nagłówka sekcji zawiera bitową flagę która opisuje atrybuty sekcji. Zdefiniowane wartości pojawiają się w tablicy niżej, inne wartości są zarezerwowane. + Rysunek 1-12: Flagi atrybutów sekcji, sh_flags Nazwa Wartość ==== ===== SHF_WRITE 0x1 SHF_ALLOC 0x2 SHF_EXECINSTR 0x4 SHF_MASKPROC 0xf0000000 Jeśli bit flagi jest ustawiony w sh_flagsm atrybut jest ,,włączony'' dla tej sekcji. W przeciwnym razie atrybut jest ,,wyłączony'' lub nie posiada zastosowania. Atrybuty niezdefiniowane są ustawione na zero. * SHF_WRITE Sekcja zawiera dane które powinny byc zapisywalne podczas wykonywania procesu. * SHF_ALLOC Sekcja zajmuje pamięć podczas wykonywania procesu. Pewne sekcje kontrolne nie znajdują się w obrazie pamięci pliku obiektowego; ten atrybut jest wyłączony dla tych sekcji. * SHF_EXECINSTR Sekcja zawiera wykonywalne kody instrukcji maszynowych. * SHF_MASKPROC Wszystkie bity włączone w tą masję są zarezerwowane dla semantyki specyficznej dla procesora. Dwa pola w nagłówku sekcji, sh_link i sh_info, zawierają specjalne informacje, zależące od typu sekcji. + Rysunek 1-13: Interpretacja sh_link i sh_info sh_type sh_link sh_info ======= ======= ======= SHT_DYNAMIC Indeks nagłówka sekcji w tablicy 0 nazw używanej przez pozycje w tej sekcji SHT_HASH Indeks nagłówka sekcji w tablicy 0 symboli do której odnosi się tablica haszowa. SHT_REL, Indeks nagłówka sekcji w Indeks nagłówka sekcji dla sekcji SHT_RELA związanej tablicy symboli. do której odnosi się relokacja SHT_SYMTAB, Indeks nagłówka sekcji w O jeden większe niż indeks tablicy SHT_DYNSYM związanej tablicy nazw. symboli dla ostatniego lokalnego symbolu (wiążącego STB_LOCAL). inne SHN_UNDEF 0 Sekcje specjalne Różne sekcje przechpwują program i informacje kontrolne. Sekcje wyszczególnione w liście poniżej są używane przez system i posiadają określone typy i atrybuty. + Rysunek 1-14: Sekcje specjalne Nazwa Typ Atrybuty ==== ==== ========== .bss SHT_NOBITS SHF_ALLOC+SHF_WRITE .comment SHT_PROGBITS brak .data SHT_PROGBITS SHF_ALLOC+SHF_WRITE .data1 SHT_PROGBITS SHF_ALLOC+SHF_WRITE .debug SHT_PROGBITS brak .dynamic SHT_DYNAMIC zobacz niżej .dynstr SHT_STRTAB SHF_ALLOC .dynsym SHT_DYNSYM SHF_ALLOC .fini SHT_PROGBITS SHF_ALLOC+SHF_EXECINSTR .got SHT_PROGBITS zobacz niżej .hash SHT_HASH SHF_ALLOC .init SHT_PROGBITS SHF_ALLOC+SHF_EXECINSTR .interp SHT_PROGBITS zobacz niżej .line SHT_PROGBITS brak .note SHT_NOTE brak .plt SHT_PROGBITS zobacz niżej .relSHT_REL zobacz niżej .rela SHT_RELA zobacz niżej .rodata SHT_PROGBITS SHF_ALLOC .rodata1 SHT_PROGBITS SHF_ALLOC .shstrtab SHT_STRTAB brak .strtab SHT_STRTAB zobacz niżej .symtab SHT_SYMTAB zobacz niżej .text SHT_PROGBITS SHF_ALLOC+SHF_EXECINSTR * .bss Ta sekcja zawiera niezainicjowane dane które odnoszą się do obrazu pamięci programu. Z definicji system inicjalizuje te dane zerami gdy program rozpoczyna działanie. Sekcja ta nie zajmuje przestrzeni w pliku, tak jak to wynika z typu sekcji, SHT_NOBITS * .comment Ta sekcja zawiera informacje kontrolne dotyczące wersji. * .data and .data1 Te sekcje zawierają zainicjalizowane dane które odnoszą się do obrazu pamięci programu. * .debug Ta sekcja zawiera informację dla debuggingu. Zawartość jest niezdefiniowana. * .dynamic Ta sekcja zawiera informacje dotyczące dynamicznego łączenia. Atrybuty sekcji zawierają SHF_ALLOC. To czy bit SHF_WRITE jest ustawiony zależy od procesora. Patrz część druga po więcej informacji. * .dynstr Ta sekcja zawiera łańcuchy potrzebne podczas dynamicznego łączenia, zazwyczaj łańcuchy reprezentujące nazwy związane z pozycjami tablicy symboli. Patrz część druga po więcej informacji. * .dynsym Ta sekcja zaiwera tablicę symboli dla dynamicznego łączenia, tak jak opisuje to ,,tablica symboli''. Patrz część druga po więcej informacji. * .fini Ta sekcja zawiera wykonywalne instrukcje które odnoszą się do kodu kończącego proces. To znaczy, gdy program kończy się normalnie, system zapewnia wykonanie kodu tej sekcji. * .got Ta sekcja zawiera tablicę globalnych ofsetów. Zobacz ,,Sekcje specjalne'' w w części 1 i ,,Tablica globalnych ofsetów'' w części 2 po więcej informacji. * .hash Ta tablica zawiera haszową tablicę symboli. Zobacz ,,Tablica Hashowa'' w części drugiej po więcej informacji. * .init Ta sekcja zawiera instrukcje które należą do kodu inicjalizacyjnego procesu. Oznacza to że kiedy proces startuje, system wykonuje kod w tej sekcji zanim wywoła główny punkt ,,wejścia'' programu (ang. entry point) (dla programów w C będzie to na przykład main). * .interp Ta sekcja zawiera nazwę scieżki interpretera programu. Jeśli plik posiada ładowalny segment który zawiera tą sekcję, atrybuty sekcji będą zawierać bit SHF_ALLOC. W przeciwnym wypadku ten bit będzie wyczyszczony. Zajrzyj do części drugiej po więcej informacji. * .line Ta sekcja zawiera informacje o numerach linii dla debuggingu, które opisują związek między liniami programu źródłowego i kodem maszynowym. Zawartość jest niezdefiniowana. * .note Ta sekcja zawiera informacje w formacie opisanym przez ,,Sekcje z uwagami'' w części drugiej. * .plt Ta sekcja zawiera tablicę procedury łączenia. Zobacz ,,Sekcje specjalne'' w części pierwszej i ,,tablica procedury łączenia'' w części drugiej po więcej informacji. * .rel and .rela Te sekcje zawierają informacje relokacyjne, tak jak poniżej opisuje to ,,Relokacja''. Jeśli plik zawiera ładowalny segment zawierający relokację, atrybuty sekcji będą zawierać bit SHF_ALLOC, wpw ten bit będzie wyczyszczony. odnosi się do sekcji do której odnosi się relokacja. W ten sposób sekcja relokacji dla .text normalnie będzie posiadać nazwę .rel.text lub .rela.text. * .rodata and .rodata1 Te sekcje zawierają dane tylko do odczytu które odnoszą się typowo do niezapisywalnego segmentu w obrazie procesu. Zobacz ,,Nagłówek programu'' w części drugiej po więcej szczegółów. * .shstrtab Ta sekcja zawiera nazwy sekcji. * .strtab Ta sekcja zawiera łańcuchy, zazwyczaj łańcuchy reprezentujące nazwy związane z pozycjami tablicy symboli. Jeśli plik posiada ładowalny segment który zawiera tablicę łańcuchów (nazw) symboli, atrybuty sekcji będą zawierać bit SHF_ALLOC. W przeciwnym wypadku ten bit zostanie wyczyszczony. * .symtab Ta skecja zawiera tablicę symboli tak jak to opisuje ,,Tablica symboli''. Jeśli plik zawiera ładowalny segment który zawiera tablicę symboli, atrybuty sekcji będą zawierać bit SHF_ALLOC. Wpw bit ten pozostanie wyczyszczony. * .text Ta sekcja zawiera ,,tekst'', kod, wykonywalne instrukcje programu. Nazwy sekcji zaczynające się od kropki są zarezerwowane dla systemu, aczkolwiek aplikacje mogą używać tych sekcji jeśli ich istniejące znaczenie jest wystarczające. Aplikacje mogą używać nazw bez kropki by uniknąć konfliktów z sekcjami systemowymi. Format pliku obiektowego pozwala definiować sekcje nie zawierające się w liście wyżej. Plik obiektowy może posiadać więcej jak jedną sekcję z tą samą nazwą. Nazwy sekcji zarezerwowane dla architektury procesora są konstruowane poprzez przyłączenia skrótu określającego architekturę z przodu nazwy sekcji. Nazwa powinna być wzięta z nazwy architektur użytych dla e_machine. Na przykład .BLA.psect oznacza sekcję psect zdefiniowaną dla architektury BLA. Istniejące rozszerzenia są nazywane ich historycznymi nazwami. Istniejące rozszerzenia ========================== .sdata .tdesc .sbss .lit4 .lit8 .reginfo .gptab .liblist .conflict ========================= Tablica nazw ========================= Sekcje z tablicami nazw (łańcuchów) zawierają zakończone znakiem pustym sekwencje znaków, powszechnie nazywane łańcuchami. Plik obiektowy używa tych łańcuchów do reprezentowania symboli i nazw sekcji. Można dostać się do łańcucha za pomocą indeksu w sekcji tablicy nazw. Pierwszy bajt, o indeksie zero, musi z definicji zawierać znak pusty. Tak samo ostatni bajt tablicy nazw również z definicji musi zawierać znak pusty, zapewniając że na pewno wszystkie łańcuchy zakończą się znakiem pustym. Łańcuch którego indeks wynosi zero nie definiuje żadnej nazwy lub inaczej mówiąc nazwę pustą, zależnie od kontekstu. Dozwolona jest pusta sekcja tablicy nazw; pole sh_size nagłówka sekcji w takim przypadku zawiera zero. Niezerowe indeksy są nieprawidłowe dla tablicy pustego łańcucha. Pole sh_name nagłówka sekcji zawiera indeks do nagłówka sekcji sekcji tablicy nazw, tak ja to jest wyznaczone przez pole e_shstrndx nagłówka ELF. Następujące rysunki pokazują tablicę nazw z 25 bajtami i łańcuchami połączonymi z różnymi indeksami. Indeks +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 ===== == == == == == == == == == == 0 \0 n a m e . \0 V a r 10 i a b l e \0 a b l e 20 \0 \0 x x \0 + Rysunek 1-15: Indeksy tablicy nazw Indeks Łańcuch ===== ====== 0 żaden 1 "name." 7 "Variable" 11 "able" 16 "able" 24 Łańcuch pusty Tak jak pokazuje to przykład, indeks tablicy nazw może odnosić się do jakiegolwiek bajtu w sekcji. Łańcuch może pojawiać się więcej razy niż razl mogą istnieć wskaźniki do podłańcuchów i może istnieć wiele wskaźników do pojedyńczego łańcucha. Dozwolone są również łańcuchy do których nie istnieje żaden wskaźnik. ========================= Tablica Symboli ========================= Tablica symboli pliku obiektowego przechowuje informacje potrzebne do zlokalizowania i relokacji symbolicznych definicji i referencji w programie. Indeks tablicy symboli jest wskaźnikiem do tej tablicy. Indeks zerowy zarówno wskazuje początkową pozycję w tablicy jak i służy za niezdefiniowany indeks symbolu. Zawartość początkowej pozycji są zdefiniowane później w tym punkcie. Nazwa Wartość ===== ======= STN_UNDEF 0 Pozycja tablicy symboli posiada następujący format. + Rysunek 1-16: Pozycja tablicy symboli typedef struct { Elf32_Word st_name; Elf32_Addr st_value; Elf32_Word st_size; unsigned char st_info; unsigned char st_other; Elf32_Half st_shndx; } Elf32_Sym; * st_name To pole zawiera indeks do tablicy nazw symboli pliku obiektowego, która zawiera znakową reprezentację nazw symboli. Jeśli wartość jest niezerowa, reprezentuje ono indeks tablicy nazw który podaje nazwę symbolu. Wpw pozycja tablicy symboli nie posiada nazwy. Zauważ: Zewnętrzne symbole C posiadają takie same nazwy w C i tablicach symboli plików obiektowych. * st_value To pole podaje wartość związanego symbolu. Zależnie od kontekstu może to być wartość absolutna, adres itd. Szczegóły pojawiają się niżej. * st_size Wiele symboli posiada rozmiary z nimi związane. Na przykład rozmiar obiektu danych jest to numer bajtów zawartych w tym obiekcie. To pole zawiera 0 jeśli symbol nie posiada rozmiaru lub jego rozmiar jest nieznany. * st_info To pole definiuje typ symbolu i związane z nim atrybuty. Lista wartości i ich znaczeń pojawia się poniżej. Następujący kod pokazuje jak operować tymi wartościami. #define ELF32_ST_BIND(i) ((i)>>4) #define ELF32_ST_TYPE(i) ((i)&0xf) #define ELF32_ST_INFO(b, t) (((b)<<4)+((t)&0xf)) * st_other To pole obecnie zawiera 0 i nie posiada zdefiniowanego znaczenia. * st_shndx Każda pozycja tablicy symboli jest ,,zdefiniowana'' w relacji do jakiejś sekcji. To pole zawiera odpowiedni indeks do tablicy nagłówków sekcji. Jak pokazuje to rysunek 1-8 {*} oraz opisuje związany tekst, niektóre indeksy sekcji maja specjalne znaczenie. Wiązanie symbolu determinuje jego widoczność i zachowanie podczas łączenia. + Rysunek 1-17: Wiązanie symbolu, ELF32_ST_BIND Nazwa Wartość ==== ===== STB_LOCAL 0 STB_GLOBAL 1 STB_WEAK 2 STB_LOPROC 13 STB_HIPROC 15 * STB_LOCAL Lokalne symbole nie są widoczne poza plikiem obiektowym zawierającym ich definicję. Lokalne symbole o tej samej nazwie mogą istnieć w wielu plikach współdziałając między sobą. * STB_GLOBAL Symbole globalne są widoczne w wszystkich plikach obiektowych które są łączone. Jedna definicja symbolu globalnego jest wystarczająca do referencji do tego samego symbolu globalnego w innym pliku. * STB_WEAK Słabe symbole przypominają symbole globalne, ale ich definicja posiada słabszy priorytet. * od STB_LOPROC do STB_HIPROC Wartości w tym domkniętym zbiorze są zarezerwowane dla semantyki specyficznej dla procesora. Globalne i słabe symbole różnią się na dwa główne sposoby. * Kiedy edytor łączenia (ang. link editor) łączy kilka relokacyjnych plików obiektowych nie zezwala na istnienie wielu definicji symboli STB_GLOBAL o tej samej nazwie. Z drugiej stronym jeśli zdefiniowany symbol globalny istnieje, istnienie symbolu słabego o identycznej nazwie nie powoduje wystąpienia błędu. Edytor łączenia uznaje globalną definicję i ignoruje słabe definicje. Podobnie, jeśli wspólny symbol istnieje (np. symbol dla którego st_shndx zawiera SHN_COMMON), pojawienie się słabego symbolu o tej samej nazwie nie powoduje błędu. Edytor łączenia honoruje wspólne (ang. common) definicje i ignoruje słabe. * Kiedy edytor łączenia przeszukuje biblioteki, rozpakowuje pola które zawierają definicje niezdefiniowanych symboli globalnych. Definicja pola może być symbolem słabym lub globalnym. Edytor łączenia nie rozpakowuj pól bibliotek (ang. extract archive members) by rozwiązać niezdefiniowane słabe symbole. Takie symbole posiadają wartość zero. W każdej tablicy symboli, wszystkie symbole z wiązaniem STB_LOCAL mają wyższy priorytet niż symbole słabe i globalne. Ja ,,Sekcje'' wyżej opisują, pole nagłówka sekcji sh_info dla sekcji tablicy symboli zawiera indeks tablicy symboli dla pierwszego nielokalnego symbolu. Typ symbolu zapewnia klasyfikację dla związanej jednostki (ang. entity). + Rysunek 1-18: Tablica Symboli, ELF32_ST_TYPE Nazwa Wartość ==== ===== STT_NOTYPE 0 STT_OBJECT 1 STT_FUNC 2 STT_SECTION 3 STT_FILE 4 STT_LOPROC 13 STT_HIPROC 15 * STT_NOTYPE Typ symbolu nie jest zdefiniowany. * STT_OBJECT Symbol jest związany z obiektem danych, takim jak zmienna, tablica itd. * STT_FUNC Symbol związany z funkcją lub innym fragmentem wykonywalnego kodu. * STT_SECTION Symbol jest związany z sekcją. Pozycja tablicy symboli tego typu istnieje przede wszystkim dla relokacji i normalnie posiada wiązanie STB_LOCAL. * STT_FILE Zgodnie z przyjętymi konwencjami nazwa tego symbolu podaje nazwę podaje nazwę pliku źródłowego związanego z plikiem obiektowym. Symbol pliku posiada wiązanie STB_LOCAL, jego indeks sekcji wynosi SHN_ABS i ma wyższy priorytet przed innymi symbolami STB_LOCAL w tym pliku, o ile one istnieją. * od STT_LOPROC do STT_HIPROC Wartości w tym domkniętym zbiorze są zarezerwowane dla semantyki specyficznej dla procesora. Symbole funkcji (te z typem STT_FUNC) w współdzielonym pliku obiektowym posiadają specjalne znaczenie. Kiedy inny plik obiektowy odnosi się do funkcji z współdzielonego obiektu, edytor łączenia automatycznie tworzy pozycję tablicy procedur łączenia dla symbolu do którego się odnosi. Do symboli dla obiektów współdzielonych z typami innymi niż STT_FUNC nie będzie można się odnosić automatycznie za pomocą tablicy procedur łączenia (ang. procedure linkage table). Jeśli wartość symbolu odnosi się do specyficznej lokalizacji w środku sekcji, jego pole indeksu sekcji - st_shndx - zawiera indeks do tablicy nagłówków sekcji. Gdy sekcja przemieszcza się podczas relokacji, wartość symbolu zmienia się również i odnośniki do symbolu wciąż wskazują do tej samej lokalizacji w programie. Pewne wartości indeksów sekcji specjalnych posiadają inną semantykę. * SHN_ABS Symbol posiada absolutną wartość która nie będzie się zmieniać z powodów relokacji. * SHN_COMMON Symbol oznacza wspólny blok który jeszcze nie został zaalokowany (przydzielony). Wartość symbolu podaje ograniczenia wypełniania, podobnie jak pole sh_addralign sekcji. Oznacza to że edytor łączenie przydzieli dla symbolu pamięć o adresie który jest wielokrotnością st_value. Rozmiar symbolu podaje ile bajtów będzie potrzebnych. * SHN_UNDEF Ten indeks tablicy sekcji oznacza że symbol jest niezdefiniowany. Kiedy edytor łączenia łączy ten plik obiektowy z innymi które definiują ten symbol, odnośniki w tym pliku zostaną połączone z właściwą definicją. Jak wspomniano wyżej, pozycja tablica symboli dla indeksu 0 (STN_UNDEF) jest zarezerwowana. Zawiera ona następujące wartości: + Rysunek 1-19: Pozycja tablicy symboli: Indeks 0 Nazwa Wartość Uwaga ==== ===== ==== st_name 0 Brak typu st_value 0 Wartość zerowa st_size 0 Brak rozmiaru st_info 0 Brak typu, wiązania lokalne st_other 0 st_shndx SHN_UNDEF Brak sekcji Wartości symboli Pozycja tablicy symboli dla różnych typów plików obiektowych ma nieco różną interpretację dla pola st_value. * W plikach relokacyjnych, st_value zawiera ograniczenia dopełniania (ang. alignment constraints) dla symbolu którego indeks sekcji wynosi SHN_COMMON. * W plikach relokacyjnych st_value zawiera ofset dla zdefiniowanego symbolu (błąd w dokumentacji czy jak? -przyp autora). Oznacza to że st_value jest ofsetem od początku sekcji identyfikowanej przez st_shndx. * W plikach wykonywalnych i współdzielonych, st_value zawiera adres wirtualny. Aby symbole tych plików były bardziej poręczne dla dynamicznego linkera, ofset sekcji (interpretacja pliku) podaje drogę do wirtualnego adresu (interpretacja pamięci) do którego nie ma odnosienia numer sekcji. Chociaż wartości tablicy symboli posiadają podobne znaczenie dla różnych plików obiektowych, dane pozwalają na efektywny dostęp dla odpowiednich programów. ========================== Relokacja ========================== Relokacja jest procesem łączenia odnośników do symboli z definicjami symboli. Na przykład kiedy prgram wywołuje funkcję, związana z tym instrukcja call musi przekazać kontrolę pod właściwy adres pod wykonanie. Onnymi słowy pliki relokacyjne muszą posiadać informacje o tym jak modyfikować zawartość ich sekcji, umożliwiając w ten sposób współdzielonym i wykonywalnym plikom obiektowym trzymać właściwe informacje dla obrazu procesu programu. Te dane to pozycje (ang. entries) , pola relokacji. + Rysunek 1-20: Pozycje relokacji typedef struct { Elf32_Addr r_offset; Elf32_Word r_info; } Elf32_Rel; typedef struct { Elf32_Addr r_offset; Elf32_Word r_info; Elf32_Sword r_addend; } Elf32_Rela; * r_offset To pole podaje lokalizację do której odnosi się akcja relokacji. Dla plików relokacyjnych, wartością tego pola jest ofset od początku sekcji do jednostki pamięci (storage unit) na której się odbije relokacja. Dla plików wykonywalnych lub obiektów współdzielonych, wartością jest adres wirtualny jednostki pamięci dotkniętej relokacją. * r_info To pole podaje zarówno indeks tablicy symboli z uznaniem relokacji która musi być dokonana, jak i typ relokacji mającej być zastosowanej. Na przykład pozycja relokacji instrukcji call zawierałaby indeks tablicy symboli dla wołanej funkcji. Jeśli indeks ten ma wartość STN_UNDEF, czyli indeks niezdefiniowanego symbolu, relokacja używa 0 jako ,,wartości symbolu''. Typy relokacji są specyficzne dla procesora. Kiedy tekst odnosi się do typu relokacji pozycji relokacji lub indeksu tablicy symboli, oznacza to rezultat zastosowania odpowiednio ELF32_R_TYPE lub ELF32_R_SYM do pola r_info pozycji. #define ELF32_R_SYM(i) ((i)>>8) #define ELF32_R_TYPE(i) ((unsigned char)(i)) #define ELF32_R_INFO(s, t) ((s)<<8+(unsigned char)(t)) * r_addend To pole definiuje stały dodatek (ang. addend) używany do obliczania wartości która będzie przechowywana w przemieszczalnym polu. Jak pokazano wyżej, tylko pozycje Elf32_Rela zawierają z góry określony dodatek (ang. explocit addend). Pozycje typu Elf32_Rel przechowują domnieny dodatek w lokalizacji która będzie modyfikowana. Zależnie od architektury procesora, jedna fomra lub druga będzie niezbędna lub bardziej wygodna. Również implementacja dla jednej maszyny może używać wyłącznie jednej formy albo wybierać formę zależnie od kontekstu. Sekcja relokacji odnosi się do dwóch innych sekcji: tablicy symboli i sekcji która będzie modyfikowana. Pola sh_info i sh_link nagłówka sekcji, opisane wyżej w ,,Sekcjach'', opisują te zależności. Pozycje dla różnych plików obiektowych posiadają nieco inną interpretację pola r_offset. * W plikach relokacyjnych, r_offset przechowuje ofset sekcji. Oznacza to że sekcja relokacji sama opisuje jak zmodyfikować inną sekcję w pliku; ofsety relokacji wyznaczają jednostki pamięci w środku innej sekcji. * W plikach wykonywalnych i współdzielonych, r_offset zawiera wirtualny adres. Aby pozycje relokacji w tych plikach bardziej poręcznych dla dynamicznego linkera, ofset sekcji (interpretacja pliku) podaje drogę do adresu wirtualnego (interpretacja pamięci). Chociaż interpretacja r_offset zmienia się dla różnych plików obiektowych aby zapewnić efektywny dostęp przez odpowiednie programy, znaczenie typów relokacji pozostaje takie samo. Typy Relokacji Pozycje relokacji opisują jak zmieniać następujące instrukcje i pola danych (numery bitów pojawiają się w dolnych rogach prostokąta). + Rysunek 1-21: Pola relokacyjne +---------------------------+ | word32 | 31---------------------------0 * word32 To definiuje 32bitowe pole zajmujące 4 bajty z dowolnym wypełnianiem. Te wartości używają tego samego porządku bajtów jak inne wartości słów w 32bitowej architektury Intela. 3------2------1------0------+ 0x01020304 | 01 | 02 | 03 | 04 | 31------+------+------+------0 Obliczenia poniżej przyjmują że akcje przekształcają plik relokacyjny w albo wykonywalny albo współdzielony plik obiektowy. Koncepcyjnie edytor łączenia łączy jeden lub więcej plik relokacyjny by stworzyć wyjście. Najpierw decyduje jak połączyć i zlokalizować pliki wejściowe, potem uaktualnia wartości symboli i wreszcie przeprowadza relokacje. Relokacja w odniesieniu do wykonywalnych lub współdzielonych plików obiektowych jest podobna i powoduje identyczny rezultat. Opisy poniżej używają następującej symboliki. * A Oznacza dodatki użyte do obliczenia wartości przemieszczalnych pól. * B Oznacza adres bazowy pod którym współdzielone obiekty zostały załadowane podczas wykonania do pamięci. Ogólnie współdzielony plik obiektowy jest zbudowany z 0 jako podstawą adresów wirtualnych, ale adresy podczas wykonywania będą inne. * G Oznacza ofset w globalnej tablicy ofsetów w której adres symbolu pozycji relokacji pozostaje podczas wykonywania. Zobacz ,,Tablica Globalnych Ofsetów'' w części drugiej po więcej informacji. * GOT Oznacza adres tablicy globalnych ofsetów. Zobacz ,,Tablica Globalnych Ofsetów'' w części drugiej po więcej informacji. * L Oznacza miejsce (ofset sekcji lub adres) pozycji w tablicy procedur łączenia dla symbolu.(ang. place (section offset or address) of the procedure linkage table entry for a symbol). Pozycja w tablicy procedur łączenia przekierowuje wywołanie funkcji do właściwego przeznaczenia. Edytor łączenia buduje początkową tablicę procedur łączenia a dynamiczny linker modyfikuje pozycje/wpisy/elementy (entries) podczas wykonywania. Zobacz ,,Tablica procedur łączenia'' w części drugiej po więcej informacji. * P Oznacz miejsce (ofset w sekcji lub adres) jednostki pamięci która jest przemieszczana (obliczane za pomocą r_offset). * S Oznacza wartość symbolu którego indeks pozostaje w pozycji/wpisie relokacji. Wartość r_offset pozycji relokacji wyznacza ofset lub wirtuany adres pierwszego bajtu jednostki pamięci dotkniętej przez relokację. Typ relokacji podaje które bity należy zmieniać i jak obliczać ich wartości. Architektura Systemu V używa tylko pozycji relokacji Elf32_Rel , pole które ma być przemieszczone zawiera dodatek (ang. field to be relocated holds the addend). W wszystkich przypadkach dodatek i obliczony rezultat używają tego samego porządku bajtów. + Rysunek 1-22: Typy Relokacji Nazwa Wartość Pole Obliczenian ==== ===== ===== =========== R_386_NONE 0 żadne żadne R_386_32 1 word32 S + A R_386_PC32 2 word32 S + A - P R_386_GOT32 3 word32 G + A - P R_386_PLT32 4 word32 L + A - P R_386_COPY 5 żadne żadne R_386_GLOB_DAT 6 word32 S R_386_JMP_SLOT 7 word32 S R_386_RELATIVE 8 word32 B + A R_386_GOTOFF 9 word32 S + A - GOT R_386_GOTPC 10 word32 GOT + A - P Niektóre typy relokacji posiadają semantykę wymykającą się prostym obliczeniom. * R_386_GOT32 Ten typ relokacji oblicza odległość od podstawy tablicy globalnych ofsetów do pozycji symbolu w tablicy globalnych ofsetów. Dodatkowo instruuje edytor łączenia by zbudował tablicę globalnych ofsetów. * R_386_PLT32 Ten typ relokacji oblicza adres pozycji w tablicy procedury łączenia dla symbolu i dodatkowo poucza edytor łączenia by zbudował tablicę procedur łączenia. * R_386_COPY Edytor łączenia tworzy ten typ relokacji dla dynamicznego łączenia. Jego pole ofset odnosi się do lokalizacji w zapisywalnym segmencie. Indeks tablicy symboli definiuje symbol który powinien istnieć zarówno w bieżącym pliku obiektowym i w współdzielonym obiekcie. Podczas wykonywania dynamiczny linker kopiuje dane związane z symbolami obiektu współdzielonego do lokalizacji wyspecyfikowanej przez ofset. * R_386_GLOB_DAT Ten typ relokacji jest używany do ustawienia wpisu tablicy globalnych ofsetów na adres wyspecyfikowanego symbolu. Specjalny typ relokacji pozwala zadecydować o stosunku między symbolami a pozycjami tablicy globalnych ofsetów. * R_386_JMP_SLOT {*} Edytor łączenia tworzy ten typ relokacji dla dynamicznego łączenia. Jego pole ofset podaje lokalizację wpisu d tablicy procedur łączenia. Dynamiczny linker modyfikuje wpis/pozycję w tablicy procedur łączenia by przekazać kontrolę adresowi wyznaczonego obiektu (zobacz też ,,Tablica Procedur Łączenia'' w części drugiej). * R_386_RELATIVE Edytor łączenia tworzy ten typ relokacji dla dynamicznego łączenia. Jego pole ofset podaje lokalizację w środku współdzielonego obiektu który zawiera wartość reprezentującą względny adres. Dynamiczny linker oblicza odpowiedni adres wirtualny poprzez dodanie wirtualnego adresu pod którym współdzielony obiekt był załadowany pod adres względny. Pozycje relokacji dla tego typu muszą zawierać zero dla indeksu tablicy symboli. * R_386_GOTOFF Ten typ relokacji oblicza różnicę między wartością symbolu i adresem tablicy globalnych ofsetów. Dodatkowo instruuje edytor łączenia by zbudował tablicę globalnych ofsetów. * R_386_GOTPC Ten typ relokacji przypomina R_386_PC32, poza tym że używa adresu tablicy globalnych ofsetów w obliczeniach. Symbol do którego odnosi się ta relokacja to zwykle _GLOBAL_OFFSET_TABLE_, co dodatkowo poucza edytor łączenia by zbudował tablicę globalnych ofsetów. ________________________________________________________________ 2. ŁADOWANIE PROGRAMU I DYNAMICZNE ŁĄCZENIE ________________________________________________________________ ========================= Wprowadzenie ========================= Część 2 opisuje informacje dotyczące plików obiektowych i akcje systemu które tworzą działające programy. Niektóre informacje tu podane odnoszą się do wszystkich systemów, inne są specyficzne dla konkretnego procesora. Wykonywalne i współdzielone pliki obiektowe statycznie reprezentują program. Aby wykonać taki program, system używa plików aby utworzyć dynamiczną reprezentację programu, albo inaczej obraz procesu. Obraz procesu posiada segmenty zawierające jego tekst (kod), dane, stos itd. Główne punkty w tej części omawiają następujące tematy: * Nagłówek programu. Ten punkt dopełnia część pierwszą, opisując struktury pliku obiektowego które odnoszą się bezpośrednio do wykonania programu. Główna struktura danych, tablica nagłówka programu, lokalizuje obrazy segmentu wewnątrz pliku i zawiera inne informacje niezbędne do stworzenia obrazu pamięci programu. * Ładowanie programu. System mając plik obiektowy musi załadować go do pamięci aby program mógł wystartować. * Dynamiczne łączenie. Gdy system załaduje program, musi skompletować obraz procesu poprzez rozwiązanie odniesień do symboli pomiędzy plikami obiektowymi składającymi się na proces. ZAUWAŻ: Istnieją konwencje nazywania dla stałych ELF które posiadają zdefiniowane zasięgi procesorów (ang have specified processor ranges). Nazwy takie jak DT_, PT_ dla rozszerzeń specyficznych dla procesora zawierają nazwę procesora: np. DT_M32_SPECIAL. Już istniejące rozszerzenia dla konkretnych procesorów niezgodne z tą konwencją będą jednak wspierane. Istniejące Rozszerzenia ======================= DT_JMP_REL ======================== Nagłówek programu ===================== Tablica nagłówka programu pliku obiektowego lub współdzielonego jest tablicą struktur z których każda opisuje segment lub inną informację której system potrzebuje aby przygotować program do odpalenia. Segment pliku obiektowego zawiera jedną lub więcej sekcji, tak jak niżej jest to opisane w punkcie ,,Zawartość Segmentu''. Nagłówki programu są znaczące tylko dla współdzielonych i wykonywalnych plików obiektowych. Plik definiuje rozmiar własnego nagłówka programu za pomocą pól nagłówka ELF : e_phnum i e_phentsize [Zobacz też ,,Nagłówek ELF'' w części pierwszej]. + Rysunek 2-1: Nagłówek Programu typedef struct { Elf32_Word p_type; Elf32_Off p_offset; Elf32_Addr p_vaddr; Elf32_Addr p_paddr; Elf32_Word p_filesz; Elf32_Word p_memsz; Elf32_Word p_flags; Elf32_Word p_align; } Elf32_Phdr; * p_type To pole opisuje jaki rodzaj segmentu opisuje ten element tablicy lub jak interpretować informache elementu tablicy. Wartości typów i ich znaczenie opisane są niżej. * p_offset To pole podaje ofset od początku pliku na którym znajduje się pierwszy bajt segmentu. * p_vaddr To pole podaje wirtualny adres pod którym pierwszy bajt segmentu znajduje się w pamięci. * p_paddr Na systemach dla których odpowiednie jest fizyczne adresowanie, to pole jest zarezerwowane dla fizycznego adresu segmentu. Ponieważ System V ignoruje fizyczne adresowanie dla programów użytkownika/użytkowych, to pole posiade niezdefiniowaną zawartość dla plików wykonywalnych i obiektów współdzielonych. * p_filesz To pole podaje liczbę bajtów w obrazie segmentu w pliku. Może wynosić zero. * p_memsz To pole podaje liczbę bajtów w obrazie segmentu w pamięci. Może wynosić zero. * p_flags To pole podaje flagi stosowane dla segmentu. Zdefiniowane wartości flag pojawiają się niżej. * p_align Tak jak to później w tej części opisuje punkt ,,Ładowanie Programu'', ładowalne segmenty procesu muszą posiadać (ang. congruent) wartości dla p_vaddr i p_offset, modulo rozmiar strony. To pole podaje wartość dla której segmenty są wyrównywane w pamięci i pliku. Wartości 0 i 1 oznaczają że nie jest potrzebne wyrównywanie. W innych przypadkach p_align powinna być dodatnią, całkowitą potęgą dwójki, a p_vaddr powinno się równać p_offset modulo p_align. Niektóre wpisy/pozycje opisują segmenty procesu; inne podają dodatkowe informacje i nie dotyczą obrazu procesu. Zdefiniowane pozycje mogą pojawić się w dowolnym porządku, za wyjątkiem przypadków opisanych poniżej. Poniżej opisane są wartości typów segmentów - inne wartości są zarezerwowane dla użycia w przyszłości. + Rysunek 2-2: Typy segmentów, p_type Nazwa Wartość ==== ===== PT_NULL 0 PT_LOAD 1 PT_DYNAMIC 2 PT_INTERP 3 PT_NOTE 4 PT_SHLIB 5 PT_PHDR 6 PT_LOPROC 0x70000000 PT_HIPROC 0x7fffffff * PT_NULL Element tablicy jest nieużywany; inne wartości pola są niezdefiniowane. ten typ pozwala tablicy nagłówku programu posiadać ignorowane wpisy. * PT_LOAD Element tablicy podaje ładowalny segment, opisany przez p_filesz i p_memsz. Bajty z początku pliku są mapowane do początku segmentu pamięci. Jeśli rozmiar pamięci segmentu (p_memsz jest większy niż rozmiar pliku (p_filesz), dodatkowe bajty z definicji zawierają wartość zero i następują po zainicjalizowanym obszarze segmentu. Rozmiar pliku nie może być większy niż rozmiar pamięci. Wpisy łądowalnych segmentów w tablicy nagłówku programu pojawiają się w porządku rosnącym, uporządkowane wg pola P_vaddr. * PT_DYNAMIC Element tablicy specyfikuje informacje tyczące dynamicznego łaczenia. Zobacz też punkt ,,Sekcje Dynamiczne'' poniżej jeśli chcesz uzyskać więcej informacji. * PT_INTERP Element tablicy specyfikuje lokalizację i rozmiar nazwy ścieżki do programu wywoływanego jako interpreter; mazwa ta zakończona jest zerem. Ten typ segmentu ma znaczenie tylko i wyłącznie dla plików wykonywalnych (chociaż może się pojawić również w obiektach współdzielonych). Może też pojawić się więcej niż raz w pliku. Jeśli jest obecny, musi poprzedzać jakąkolwiek pozycję/wpis ładowalnego segmentu. Zobacz punkt ,,Interpreter Programu'' poniżej w celu uzyskania więcej informacji. * PT_NOTE Element tablicy specyfikuje lokalizację i rozmiar pomocniczej informacji. Zobacz też punkt ,,Sekcje Uwag'' poniżej po więcej szczegółów. * PT_SHLIB Ten typ segmentu jest zarezerwowany ale nie posiada zdefiniowanej semantyki. Programy zawierające element tablicy tego typu nie są zgodne z ABI. * PT_PHDR Element tablicy, jeśli obecny, podaje lokalizację i rozmiar samej tablicy nagłówku programu (ang. location and size program header table itself), zarówno w pliku jak i w obrazie pamięci programu. ten typ segmentu nie może pojawić się więcej niż raz w pliku, co więcej, może pojawić tylko wtedy gdy tablica nagłówka programu jest częścią obrazu pamięci programu. Jeśli jest obecny, musi poprzedzać jakikolwiek wpis/pozycję (ang entry) łądowalnego segmentu. Zobacz też punkt ,,Interpreter Programu'' aby uzyskać więcej informacji. * PT_LOPROC through PT_HIPROC Wartości w tym domkniętym zbiorze są zarezerwowane dla semantyki specyficznej dla procesora. ZAUWAŻ: O ile nie jest to jawnie wymagane gdzie indziej, wszystkie typy segmentów nagłówka programu są opcjonalne. To znaczy tablica nagłówka programu może zawierać tylke te elementy stosowne do jej zawartości. Adres Bazowy Obiektowe pliki współdzielone i wykonywalne posiadają adres bazowy, który jest najniższym wirtualnym adresem związanym z obrazem pamięci pliku obiektowego programu. Używa się adresu bazowego do relokacji obrazu pamięci programu podczas dynamicznego łączenia. Adres bazowy pliku wykonywalnego i współdzielonego jest obliczany podczas wykonywania z trzech wartości: adresu załadowania pamięci, maksymalnego rozmiaru strony, i najniższego adresu wirtualnego segmentu ładowalnego programu. Tak jak to opisuje punkt ,,Ładowanie Programu'' w tym rozdziale, adresy wirtualne mogą nie reprezentować właściwych adresów wirtualnych obrazu pamięci programu. Aby obliczyć adres podstawowy, należy odnaleźć adres pamięci związany z najmniejszą wartością p_vaddr dla segmentu PT_LOAD. Następnie otrzymuje się adres bazowy obcinajać adres w pamięci do najbliższej wielokrotności maksymalnego rozmiaru strony pamięci. Zależnie od rodzaju pliku który jest ładowany do pamięci, adres pamięci może lub nie odpowiadać wartościom p_vaddr. Tak jak opisuje to w części 1 punkt ,,Sekcje'', sekcja .bss posiada typ SHT_NOBITS. Chociaż nie zajmuje przestrzeni w pliku, odnosi się do obrazu pamięci segmentu. Zwykle te niezainicjalizowane dane pozostają na końcu segmentu w ten sposób czyniąc p_memsz większy niż p_filesz w związanym elemencie nagłówka programu. Sekcja Uwag Czasami producent lub twórca systemu potrzebuje oznaczyć plik obiektowy za pomocą specjalnej informacji którą inne programy będą sprawdzać dla zgodności (ang for conformance and compatibility etc). Sekcje typu SHT_NOTE i elementy nagłówka programu typu PT_NOTE mogą być używane właśnie do tego celu. Informacja ta w sekcjach i elementach nagłówka programu zawiera jakąkolwiek liczbę pozycji, z których każda jest tablicą czterobajtowych słów w formacie procesora docelowego. Etykiety pojawiające się niżej służą do pomocy przy objaśnianiu organizacji tych informacji, ale nie są częścią specyfikacji. + Rysunek 2-3: Uwagi namesz descsz type name ... desc ... * namesz i name Pierwsze namesz bajtów w polu ,,name'' (nazwa) zawiera zakończoną zerem znakową reprezentację właściciela lub oryginalnego twórcy pozycji. Nie ma żadnego sformalizowanego mechanizmu który pozwalałby unikać konfliktów nazw. Zgodnie z konwencją producenci używają swoich własnych nazw, takich jak ,,Kompania komputerowa XYZ'' jako identyfikatorów. Jeżeli nie jest obecna żadna nazwa, namesz zawiera zero. Jeśli jest to konieczne, wykonuje się dopełnianie (ang. padding) aby zapewnić 4-bajtowe wyrównywanie (ang. alignment). Takie dopełnianie nie jest zawarte w namesz. * descsz i desc Pierwsze descsz bajtów i w polu ,,desc'' (opis) zawiera deskryptor uwagi. ABI nie zawiera żadnych ograniczeń na zawartość deskryptora. Jeśli nie istnieje taki deskryptor, descsz zwiera zero. Jeśli jest to konieczne, wykonuje się dopełnianie (ang. padding) aby zapewnić 4-bajtowe wyrównywanie (ang. alignment). Takie dopełnianie nie jest zawarte w descsz. * type To słowo podaje interpretację deskryptora. Każdy twórca kontroluje swoje własne typy. Mogą istnieć wielokrotne interpretacje wartości pojedyńczego typu. Dlatego też program musi rozpoznawać zarówno typ jak i nazwę aby zrozumieć deskryptor. Obecnie typy muszą być nieujemne. ABI ine definiuje co oznacza deskryptor. Aby to zilustrować, następujący segment uwag posiada dwie pozycje: + Rysunek 2-4: Przykład segmentu uwag +0 +1 +2 +3 ------------------- namesz 7 descsz 0 Brak deskryptora type 1 name X Y Z spc C o \0 pad namesz 7 descsz 8 type 3 name X Y Z spc C o \0 pad desc word0 word1 ZAUWAŻ: System rezerwuje uwagi bez nazw (namesz=0) i z zerową długością nazwy (name[0]=='\0') ale obecnie nie definiuje żadnych typów. Wszystkie inne nazwy muszą zawierać co najmniej jeden jeden znak różny od znaku pustego. ZAUWAŻ: Uwagi są opcjonalne. Obecność tych informacji nie odbija się na zgodności programu z ABI, o ile ta informacja nie zmienia zachowania programu. Jeśli tak się dzieje, program nie jest zgodny z ABI i posiada niezdefiniowane zachowanie. ======================= Ładowanie Programu ===================== Gdy system tworzy lub powiększa obraz procesu, logicznie kopiuje segment pliku do segmentu pamięci wirtualnej. Kiedy - i jeśli - system fizycznie przeczyta plik zależy od zachowania programu, obciążenia systemu (ang. system load) itd. Proves nie wymaga fizycznej strony dopóki odnosi się do stron logicznych podczas wykonywania i procesy powszechnie nie odnoszą się do wielu stron (ang. leave may pages unreferenced) Dlatego też opóźnienia fizycznego odczytu często ich nie obchodzi (ang obviates them) opprawiając wydajność systemu. Aby otrzymać efektywność w praktyce, pliki wykonywalne i współdzielone muszą posiadać obrazy segmentów których ofsety plików i adresy wirtualne są (ang. congruent) modulo rozmiar strony. Adresy wirtualne i ofsety plików dla segmentów architektury SYSTEMU V są (ang. congruent) modulo 4KB (0x1000) lub większej potęgi dwójki. Ponieważ 4 KB jest maksymalnym rozmiatem strony, pliku pozostaną wygodne i nadające się dla stronicowania niezależnie od rozmiaru fizycznej strony. + Rysunek 2-5: Plik wykonywalny Ofset pliku Plik Adres Wirtualny =========== ==== =============== 0 Nagłówek ELF Tablica nagłówka programu Inne informacje 0x100 Segment Kodu 0x8048100 ... 0x2be00 bajtów 0x8073eff 0x2bf00 Segment danych 0x8074f00 ... 0x4e00 bajtów 0x8079cff 0x30d00 Inne informacje ... + Rysunek 2-6: Segmenty nagłówka programu Pole Tekst(kod) Dane ====== ==== ==== p_type PT_LOAD PT_LOAD p_offset 0x100 0x2bf00 p_vaddr 0x8048100 0x8074f00 p_paddr unspecified unspecified p_filesz 0x2be00 0x4e00 p_memsz 0x2be00 0x5e24 p_flags PF_R+PF_X PF_R+PF_W+PF_X p_align 0x1000 0x1000 Chociaż ofsety przykładowego pliku i adresy wirtualne są (ang congruent) modulo 4 KB dla zarówno tekstu (kodu) jak i danych, do czterech stron plikowych zawiera nieczyste (? ang. impure ?) dane i tekst (zależnie od rozmiaru strony i rozmiaru bloku systemu). * Pierwsza strona kodu zawiera nagłówek ELF, nagłówek programu i inne informacje * Ostatnia strona kodu zawiera kopię początku danych. * Pierwsza strona danych zawiera kopię początku tekstu. * Ostatnia strona danych może zawierać informację o pliku nie mające zastosowania dla działającego procesu. Logicznie, system wymusza pozwolenia na dostęp do pamięci (ang, memory permissions) tak jak gdyby każdy segment był kompletny i osobny. Adresy segmentów są dostosowywane by zapewnić dla każdej logicznej strony w przestrzeni adresowej istnieje osobny zbiór zezwoleń. W przykładzie powyżej obszar pliku zawierający koniec tekstu i początek danych byłby zamapowany dwukrotnie; na jednym wirtualnym adresie dla kodu i osobnym adresie wirtualnym dla danych. Koniec segmentu danych wymaga specjalnego traktowania dla niezainicjalizowanych danych, które system definiuje jako startujące z zerową zawartością. Stąd jeśli ostania strona danych pliku zawiera informacje nie w logicznej stronie pamięci dodatkowe dane muszą być ustawione na zeroom a nie na nieznane zawartości pliku wykonywalnego. ,,Nieczystości'' w pozostałych trzech stronach nie są logiczną częścią obrazu procesu. Nie jest zdefiniowane czy system je kasuje. Obraz dla tego programu wyglądałby następująco, przyjmując 4 KB strony. + Rysunek 2-7: Segmenty obrazu procesu Adresy wirtualne Zawartość Segment =============== ======== ======= 0x8048000 Dopełnienie nagłówka Tekst 0x100 bajtów 0x8048100 Segment kodu ... 0x2be00 bajtów 0x8073f00 Dopełnienie danych 0x100 bajtów 0x8074000 Dopełnienie kodu Dane 0xf00 bajtów 0x8074f00 Segment danych ... 0x4e00 bajtów 0x8079d00 Niezainicjalizowane dane 0x1024 zerowych bajtów 0x807ad24 Dopełnienie strony 0x2dc zerowych bajtów Jeden z aspektów ładowania segmentów różni się pomiędzy plikami wykonywalnymi i obiektami współdzielonymi. Pliku wykonywalne zazwyczaj zawierają absolutny kod. Aby pozwolić procesowi na poprawne wykonywanie się, segmenty muszą przebywać pod wirtualnymi adresami użytymi do zbudowania pliku wykonywalnego. Stąd system używa wartości p_vaddr niezmienionych jako adresów wirtualnych Z drugiej strony, segmenty obiektów współdzielonych zazwyczaj zawierają kod niezależny od pozycji. Pozwala to wirtualnym adresom segmentów zmieniać się między jednym procesem a drugim, bez zakłócania zachowania wykonywania. Chociaż system wybiera adresy wirtualne dla indywidualnych procesów zarządza względnymi pozycjami segmentów. Ponieważ kod niezależny od pozycji używa względnego adresowania pomiędzy segmentami, różnica pomiędzy wirtualnymi adresami w pamięci musi się zgadzać z różnicą pomiędzy wirtualnymi adresami w pliku. Następująca tablica pokazuje możliwe przydziały wirtualnych adresów dla obiektów współdzielonych, ilustrując stałe względne pozycje. Tablica ilustruje również obliczenia bazowych adresów. + Rysunek 2-8: Przykład adresów segmentów współdzielonych obiektów. Źródło Kod Data Adres bazowy ===== ==== ==== ============ Plik 0x200 0x2a400 0x0 Proces 1 0x80000200 0x8002a400 0x80000000 Proces 2 0x80081200 0x800ab400 0x80081000 Proces 3 0x900c0200 0x900ea400 0x900c0000 Proces 4 0x900c6200 0x900f0400 0x900c6000 ======================= Dynamiczne łączenie ==================== Interpreter Programu Plik wykonywalny może mieć jeden element nagłówka programu typu PT_INTERP. Podczas exec(BA_OS), system otrzymuje ścieżkę z segmentu PT_INTERP i tworzy obraz procesu inicjującego z segmentu pliku interpretera: to znaczy, zamiast używać obrazu segmentu oryginalnego pliku wykonywalnego, system tworzy obraz pamięci z interpretera. Potem na interpreterze spoczywa odpowiedzialność za przejęcie kontroli od systemu i zapewnienie środowiska dla programu użytkowego. Interpreter otrzymuje kontrole w jeden z dwóch sposobów. Po pierwsze, może otrzymać deskryptor pliku do odczytania pliku wykonywalnego, zaczynając od początku. Może używać tego deskryptora pliku by odczytywać i/lub mapować segmenty pliku wykonywalnego do pamięci. Po drugie, zależnie od formatu pliku wykonywalnego, system może załadować plik wykonywalny do pamięci zamiast podania interpretorowi deskryptora pliku. Z możliwym możliwym wyłączeniem deskryptora pliku, stan początkowy procesu interpretera dopasowuje to co otrzymałby pliku wykonywalny. Sam interpreter nie może wymagać innego interpretera. Interpreter może być plikiem wykonywalnym lub obiektem współdzielonym. * obiekt współdzielony (w normalnym przypadku) jest ładowany jako niezależny od pozycji, z adresami które mogą się różnić między jednym procesem a drugim; system tworzy segment w obszarze dynamicznego segmentu używanym przez mmap(KE_OS) i jemu podobne usługi. Konsekwetnie (w efekcie? ang consequently) również interpreter współdzielonego obiektu nie spowoduje konfliktu z oryginalnym adresem segmentu pliku wykonywalnego. * Plik wykonywalny jest ładowany pod dopasowane adresy; system tworzy jego segmenty używając wirtualnych adresów z tablicy nagłówka programu. W efekcie adresy wirtualne interpretera pliku wykonywalnego mogą kolidować z pierwszym plikiem wykonywalnyml interpreter jest odpowiedzialny za rozwiązywanie takich konfliktów. Dynamiczny Linker Podczas budowania pliku wykonywalnego który używa dynamicznego łączenia, edytor łączenia dodaje element nagłówka programu typu PT_INTERP do pliku wykonywalnego, w ten sposób powiadamiając system by wywoływał dynamicznego linkera jak interpreter programu. ZAUWAŻ: lokalizacja dynamicznych linkerów dostarczanych przez system jest specyficzna dla procesora. Exec(BA_OS) oraz dynamiczny linker współpracują aby stworzyć obraz procesu dla programu, co wymaga następujących akcji: * Dodania segmentu pamięci pliku wykonywalnego do obrazu procesu; * Dodanie segmentu pamięci wspołdzielonego obiektu do obrazu procesu; * Wykonania relokacji dla pliku wykonywalnego i jego współdzielonych obiektów; * Zamknięcia deskryptora pliku użytego do przeczytania pliku wykonywalnego, o ile jakiś został dostarczony dynamicznemu linkerowi; * Przekazania kontroli programowi, czyniąc to tak jak gdyby program otrzymał kontrolę bezpośrednio od exec(BA_OS). Edytor łączenia również konstruuje różne dane które są potrzebne dynamicznemu linkerowi przy wykonywalnych i współdzielonych plikach obiektowych. Tak jak to wyżej było pokazane w ,,Nagłówku programu'', ten dane pozostają w segmencie wykonywalnym, czyniąc je dostępnymi podczas wykonywania. (Jeszcze raz, przypomnienie (ang. recall) dokładnej zawartości segmentu jest specyficzne dla procesora. Zobacz dodatek o procesorach dla pełnej informacji). * Sekcja .dynamic o typie SHT_DYNAMIC zawiera rozmaite dane. Struktura przebywająca na początku sekcji zawiera adresy innych informacji dla dynamicznego łączenia. * Sekcja .hash o typie SHT_HASH zawiera tablicę haszową symboli. * Sekcje .got i .plt o typie SHT_PROGBITS zawierają dwie oddzielne tablice: tablice globalnego ofsetu i tablicę procedur łączenia. Sekcje poniżej wyjaśniają jak dynamiczny linker używa ich i zmienia tablice aby stworzyć obraz pamięci dla plików obiektowych. Ponieważ każdy zgodny z ABI program importuje podstawowe usługi systemowe z biblioteki współdzielonych, dynamiczny linker bierze udział w każdym wykonaniu programu zgodnego z ABI. Tak jak to wyjaśnia punkt ,,Ładowanie programu w dodatku o procesorach, obiekty mogą zajmować wirtualne adresy które różnią się od adresów zapisanych w tablicy nagłówka programu w pliku. Dynamiczny linker relokuje obraz pamięci, uaktualniając bezwzględne adresy zanim aplikacja otrzyma kontrolę. Chociaż wartości bezwzględne adresów byłyby poprawne gdyby biblioteki były ładowane pod adresem zdefiniowanym w tablicy nagłówka programu, tak normalnie się nie robi. Jeżeli środowisko procesu [zobacz exec(BA_OS)] zawiera zmienną nazwaną LD_BIND_NOW z niepustą wartością, dynamiczny linker podejmuje wszelkie relokacje zanim przekaże kontrolę programowi. Na przykład wszystkie ustawienia zmiennej środowiskowej poniżej spowodowałyby to zachowanie. * LD_BIND_NOW=1 * LD_BIND_NOW=on * LD_BIND_NOW=off W przeciwnym razie LD_BIND_NOW albo nie pojawia się w środowisku albo posiada pustą wartość. Dynamiczny linker może ,,leniwie'' wartościować pozycje tablicy procedur łączenia, w ten sposób unikając rozwiązywania symboli i nakładu związanego z relokacją dla funkcji, które nie są wywoływane. Zobacz też ,,Tablica łączenia procedur'' w tej części po więcej informacji. Sekcje dynamiczne Jeśli plik obiektowy bierze udział w dynamicznym łączeniu, jego tablica nagłówka programu będzie zawierał element typu PT_DYNAMIC. Ten ,,segment'' zawiera sekcję .dynamic. Specjalny symbol, _DYNAMIC, zaznacza sekcję, który zawiera (tlumacz: sekcja czy symbol??) tablicę następujących struktur: + Rysunek 2-9: Struktury Dynamiczne typedef struct { Elf32_Sword d_tag; union { Elf32_Sword d_val; Elf32_Addr d_ptr; } d_un; } Elf32_Dyn; extern Elf32_Dyn _DYNAMIC[]; Dla każdego obiektu tego typu, d_tag kontroluje interpretację d_un. * d_val Te obiekty Elf32_Word reprezentują wartości całkowite z różnymi interpretacjami. * d_ptr Te obiekty Elf32_Addr reprezentują wirtualne adresy programu. Jak poprzednio wspomniano, adresy wirtualne pliku mogą być różne od adresów wirtaulnych w pamięci podczas wykonywania. Podczas interpretowania adresów zawartych w sekcji dynamicznej, dynamiczny linker oblicza właściwe adresy, oparte na oryginalnych wartościach w pliku i adresu bazowego w pamięci. Dla spójności, pliku nie zawierając pozycji relokacji do ,,poprawiania'' adresów w strukturze dynamicznej. Następująca tablica wymienia (ang. tag) wymagania dla obiektowych plików wykonywalnych i współdzielonych. Jeśli tag jest zaznaczony jako ,,obowiązkowy'', wtedy tablica dynamicznego łączenia dla każdego pliku zgodnego z ABI musi posiadać wpis tego typu. Tak samo ,,opcjonalnie'' oznacza że wpis dla taga może się pojawić ale nie jest wymagany. + Rysunek 2-10: Tagi dynamicznej tablicy, d_tag Nazwa Wartość d_un Wykonywalny Współdzielony ==== ===== ==== ========== ============= DT_NULL 0 ignorownany obowiązkowy obowiązkowy DT_NEEDED 1 d_val opcjonalny opcjonalny DT_PLTRELSZ 2 d_val opcjonalny opcjonalny DT_PLTGOT 3 d_ptr opcjonalny opcjonalny DT_HASH 4 d_ptr obowiązkowy obowiązkowy DT_STRTAB 5 d_ptr obowiązkowy obowiązkowy DT_SYMTAB 6 d_ptr obowiązkowy obowiązkowy DT_RELA 7 d_ptr obowiązkowy opcjonalny DT_RELASZ 8 d_val obowiązkowy opcjonalny DT_RELAENT 9 d_val obowiązkowy opcjonalny DT_STRSZ 10 d_val obowiązkowy obowiązkowy DT_SYMENT 11 d_val obowiązkowy obowiązkowy DT_INIT 12 d_ptr opcjonalny opcjonalny DT_FINI 13 d_ptr opcjonalny opcjonalny DT_SONAME 14 d_val ignorowany opcjonalny DT_RPATH 15 d_val opcjonalny ignorowany DT_SYMBOLIC 16 ignored ignorowany opcjonalny DT_REL 17 d_ptr obowiązkowy opcjonalny DT_RELSZ 18 d_val obowiązkowy opcjonalny DT_RELENT 19 d_val obowiązkowy opcjonalny DT_PLTREL 20 d_val opcjonalny opcjonalny DT_DEBUG 21 d_ptr opcjonalny ignorowany DT_TEXTREL 22 ignored opcjonalny opcjonalny DT_JMPREL 23 d_ptr opcjonalny opcjonalny DT_LOPROC 0x70000000 niezdefiniowany --------------------- DT_HIPROC 0x7fffffff niezdefiniowany --------------------- * DT_NULL Wpis z tagiem DT_NULL oznacza koniec tablicy _DYNAMIC * DT_NEEDED Ten element zawiera ofset tablicy łańcuchów zakończonych znakiem pustym łańcuchów, podających nazwy potrzebnych bibliotek. Ofset jest indeksem do tablicy zawartej w pozycji DT_STRTAB. Zobacz ,,Zależności obiektów współdzielonych'' dla więcej informacji o tych nazwach. Tablica dynamiczna może zawierać wielokrotne wpisy z tymi typami. Porządek tych wpisów jest znaczący, chociaż ich pozycja względem innych typów nie jest. * DT_PLTRELSZ Ten element zawiera totalny rozmiar w bajtach pozycji relokacji związanych z tablicą procedur łączenia. Jeśli wpis o typir DT_JMPREL jest obecny, musi mu towarzyszyć DT_PLTRELSZ. * DT_PLTGOT Ten element zawiera adres związany z tablicą procedur łączenia i/lun tablicą globalnych ofsetów. Zobacz punkt dodatku o procesorach po więcej szczegóły. * DT_HASH Ten element zawiera adres haszowej tablicy symboli, opisanej w ,,Tablicy Haszowej''. Ta tablica haszowa odnosi sie do tablicy symboli wskazywanej/do której się odnosi element DT_SYMTAB. * DT_STRTAB Ten element zawiera adresy tablicy nazw, opisanej w części pierwszej. Nazwy symboli, nazwy bibliotek i inne łańcuchy znajdują się w tej tablicy. * DT_SYMTAB Ten element zawiera adresy tablicy symboli, opisanej w części pierwszej z wpisami Elf32_Sym dla 32-bitowej klasy plików. * DT_RELA Ten element zawiera adres tablicy relokacji, opisanej w części 1. Wpisy w tej tablicy posiadają z góry znane (ang explicit addends) dodatki, takie jak Elf32_Rela dla 32-bitowej klasy plików. Plik obiektowy może posiadać wiele sekcji relokacji. Podczas budowania tablicy relokacji dla współdzielonego lub wykonywalnego pliku obiektowego, edytor łączenia (ang. catenates... może concatenates?) formuje z tych sekcji pojedyncza tablicę. Chociaż sekcje pozostają niezależne w pliku obiektowym, dynamiczny linker widzi pojedyńczą tablicę. Kiedy dynamiczny linker tworzy obraz procesu dla pliku wykonywalnego albo dodaje współdzielony obiekt do obrazu procesu, czyta tablicę relokacji i podejmuje związane akcje. Jeśli ten element jest obecnym struktura dynamiczna musi również posiadać elementy DT_RELASZ i DT_RELAENT. Kiedy relokacja jest ,,obowiązkowa'' dla pliku, albo DT_RELA albo DT_REL mogą się pojawić (obydwa są dozwolone, lcze nie wymagane). * DT_RELASZ Ten element zawiera totalny rozmiar w bajtach tablicy relokacji DT_RELA. * DT_RELAENT Ten element zawiera rozmiar w bajtach wpisu DT_RELA relokacji * DT_STRSZ Ten element zawiera rozmiar w bajtach tablicy nazw. * DT_SYMENT Ten element zawiera rozmiar w bajtach wpisu tablicy symboli. * DT_INIT Ten element zawiera adres funkcji inicjalizacyjnej, omówionej w punkcie ,,Funkcje Inicjalizacyjne i Kończące'' poniżej. * DT_FINI Ten element zawiera adres funkcji kończącej omówionej w punkcie ,,Funkcje Inicjalizujące i konczące''. * DT_SONAME Ten element zawiera ofset tablicy nazw/łańcuchów zawierającej łańcuchy zakończone znakiem pustym, podające nazwę współdzielonego obiektu. Ofset jest indeksem do tablicy zapisanej w wpisie DT_STRTAB. Zobacz punkt ,,Zależności w Plikach Obiektowych'' poniżej aby uzyskać więcej informacji na ten temat. * DT_RPATH Ten element zawiera ofset tablicy nazw/łańcuchów zawierającej łańcuchy zakończone znakiem pustym, podające nazwę ścieżkę przeszukiwań bibliotek, omówioną w ,,Zależnościach w Plikach Obiektowych''. Ofset jest indeksem do tablicy zapisanej w wpisie DT_STRTAB. * DT_SYMBOLIC Obecność tego elementu w bibliotece współdzielonych obiektów zmienia algorytm dynamicznego linkera rozwiązywania symbolów dla odniesień w środku biblioteki. Zamiast zaczynać przeszukiwanie w środku pliku wykonywalnego, dynamiczny linker zaczyna od samego obiektu współdzielonego. Jeśli symbol do którego nastąpiło odniesienie nie zawiera się w obiekcie współdzielonym, dynamiczny linker zaczyna przeszukiwać pliki wykonywalne i inne obiekty współdzielone w zwykły sposób. * DT_REL Ten element jest podobny do DT_RELA, poza tym że jego tablica posiada dodatki nie wiadome z góry (ang. implicit), takie jak Elf32_Rel dla klasy plików 32-bitowych. Jeśli ten element jest obecny, struktura dynamiczna musi posiadać również elementy DT_RELSZ i DT_RELENT. * DT_RELSZ Ten element zawiera rozmiar w bajtach tablicy relokacji DT_REL. * DT_RELENT Ten element zawiera rozmiar w bajtach wpisu relokacji DT_REL. * DT_PLTREL To pole definiuje typ wpisu relokacji do którego odnosi się tablica procedur łączenia. Wartość d_val zawiera DT_REL lub DT_RELA, zależnie od kontekstu. Wszystkie relokacje w tablicy procedur łączenia muszą używać tej samej relokacji (od tłumacza: typu relokacji jak sądzę). * DT_DEBUG To pole jest używana do debuggingu. Jego zawartość nie jest zdefiniowana przez ABI. Programy które dostają się do tego wpisu nie są zgodne z ABI. * DT_TEXTREL Nieobecność tego pola oznacza że żaden wpis relokacji nie powinien powodować modyfikacji niezapisywalnego segmentu, tak jak wyspecyfikowano w zezwoleniach do segmentu w tablicy nagłówka programu. Jeśli pole to jest obecne, jeden lub więcej wpis relokacji może zażądać modyfikacje do niezapisywalnego segmentu, i odpowiednio może przygotowywać dynamiczny linker. * DT_JMPREL Jeśli obecne, to pole pozycji d_prt przechowuje adres pozycji relokacji związane wyłącznie z tablicą procedur łączenia. Separowanie tych pozycji relokacji pozwala dynamicznemu linkerowi ignorować je podczas inicjalizacji procesu, o ile jest włączone ,,leniwe'' wiązanie. Jeśli to pole jest obecne, odnośne pozycje typu DT_PLTRELSZ i DT_PLTREL muszą również być obecne. * od DT_LOPROC do DT_HIPROC Wartości w tym domkniętym zbiorze są zarezerwowane dla semantyki specyficznej dla procesora. Za wyjątkiem elementu DT_NULL na końcu tablicy i względnego porządku elementów DT_NEEDED, wpisy mogą pojawiać się w dowolnym porządku. Wartości tagów nie pojawiające się w tablicy są zarezerwowane. Zależności w obiektach współdzielonych Kiedy edytor łączenia przetwarza biblitekę (ang. archive library), ekstrahuje pola biblioteki i kopiuje je do wynikowego pliku obiektowego. Te statycznie wkompilowane usługi są dostępne podczas wykonywania bez potrzeby korzystania z usług dynamicznego linkera. Obiekty współdzielone dostarczają również usług i dynamiczny linker musi dołączyc odpowiedni współdzielony plik obiektowy to obrazu pliku do wykonania. Stąd wspołdzielone i wykonywalne pliki obiektowe opisują ich spsecyficzne zależności. Kiedy dynamiczny linker tworzy segmenty pamięci dla pliku obiektowego, zależności (zapisane w wpisie DT_NEEDED struktury dynamicznej) mowią jakie obiekty współdzielone są potrzebne do wsparcia usług procesu (?). Poprzez powtarzające łączenie zależności i współdzielonych obiektów do których się one odnoszą, dynamiczny linker buduje kompletny obraz procesu. Podczas rozwiązywania symbolicznych odnośników, dynamiczny linker przegląda tablicę symboli za pomocą przeszukiwania wszerz. To znaczy najpierw patrzy na tablicę symboli samego programu, potem na tablice symboli wpisów w DT_NEEDED (zgodnie z ich uporządkowaniem), potem na wpisy drugiego poziomu DT_NEEDED (od tłumacza tj. wymagane przez te z DT_NEEDED pierwszego poziomu) itd. Współdzielone pliki obiektowe muszą być odczytywalne przez proces - inne zezwolenia nie są wymagane. ZAUWAŻ: Nawet kiedy do wpółdzielonego obiektu istnieje wiele odwołań w liście zależności, dynamiczny linker dołączy obiekt do procesu tylko raz. Nazwy w liście zależności są kopiami albo łańcuchów DT_SONAME albo nazwami ścieżek współdzielonych obiektów użytych di zbudowania pliku obiektowego. Na przykład, jeśli edytor łączenia zbuduje plik wykonywalny używając jednego obiektu współdzielonego z wpisem DT_SONAME jako lib1 i innej biblioteki obiektów współdzielonych w nazwą ścieżki /usr/lib/lib2, plik wykonywalny zawierać będzie lib1 i /usr/lib/lib2 w swojej liście zależności. Jeśli nazwa obiektu współdzielonego zawiera jeden lub więcej znaków "/" (szleszy jak to mawiał doktor Stefanowski :) ) - gdziekolwiek w nazwie, tak jak /usr/lib/lib2 jak powyżej albo katalog/plik, dynamiczny linker używa tej nazwy bezpośrednio jako nazwy ścieżki. Jeśli nazwa nie ma znaków "/", tak jak wyżej lib1, trzy udogodnienia definiują przeszukiwania ścieżki obiektów współdzielonych, wg następującej procedury: * Po pierwsze, tag dynamicznej tablicy DT_RPATH może podać łańcuch który zawiera listę katalogów, rozdzielanych przez dwukropek (:). Na przykład, łańcuch /home/dir/lib:/home/dir2/lib: powiadamia dynamicznego linkera by przeszukiwął najpierw katalog /home/dir/lib a następnie /home/dir2/lib, i wreszcie katalog domowy aby odnaleźć zależności. * Po drugie, zmienna w środowisku procesu LD_LIBRARY_PATH [zobacz exec(BA_OS)] może zawierać listę katalogów jak wyżej, opcjonalnie zakończonych średnikiem i kolejną listę katalogów. Następujące wartości byłyby równoważne do poprzedniego przykładu: LD_LIBRARY_PATH=/home/dir/lib:/home/dir2/lib: LD_LIBRARY_PATH=/home/dir/lib;/home/dir2/lib: LD_LIBRARY_PATH=/home/dir/lib:/home/dir2/lib:; Wszystkie katalogi LD_LIBRARY_PATH są przeszukiwane po tych z DT_RPATH. Chociaż niektóre programy (takie jak edytor łączenia) traktują listy przed i po średniku różnie, dynamiczny linker tak nie czyni. Dynamiczny linker akceptuje notację z średnikiem z semantyką opisaną powyżej. *Wreszcie, jeśli pozostałe dwie grupy katalogów nie będą zawierać pożądanej biblioteki, dynamiczny linker przeszukuje /usr/lib. ZAUWAŻ: Dla bezpieczeństwa, dynamiczny linker ignoruje środowiskowe ustawienia przeszukiwania, takie jak LD_LIBRABRY_PATH dla programów typu set-user i set-group ID. Jednakże przeszukuje katalogi wyszczególnione w DT_RPATH i /usr/lib. Tablica globalnych ofsetów Kod zależny od pozycji w ogólności nie może zawierać absolutnych wirtualnych adresów. Tablica globalnych ofsetów zawiera absolutne adresy w prywatnych danych, w ten sposób czyniąc adresy dostępnymi bez niweczenia niezależności od pozycji i współdzielenia kodu programu. Program odwołuje się do swojej tablicy globalnych ofsetów używając adresowania niezależnego od pozycji i wyciągając absolutne wartości, w ten sposób zmieniając odwołania/odnośniki niezależne od pozycji do bezwzględnych/absolutnych lokalizacji. Początkowo, tablica globalnych ofsetów zawiera informacje wymagane przez swoje wpisy relokacji [zobacz ,,Relokacja'' w części pierwszej]. Gdy system stworzy segmenty pamięci dla ładowalnego pliku obiektowego, dynamiczny linker przetwarza wpisy relokacji, z których niektóre będą typu R_386_GLOB_DAT odnoszącego się do tablicy globalnych ofsetów. Dynamiczny linker znajduje związane wartości symboli, oblicza ich absolutne wartości i ustawia odpowiednie wpisy w tablicy pamięci na właściwe wartości. Chociaż absolutne adresy są nieznane kiedy edytor łączenia buduje plik obiektowy, dynamiczny linker wie adresy wszystkich segmentów pamięci i stąd umie obliczyć absolutną wartość zawartych tam symboli. Jeśli program wymaga bezpośredniego dostępu do absolutnych adresów symbolu, ten symbol będzie posiadał wpis w tablicy globalnych ofsetów (Zaraz, zaraz.. a może to jest globalna tablica ofsetów?). Ponieważ pliki wykonywalne i współdzielone obiekty posiadają oddzielne tablice globalnych ofsetów adresy symboli mogą pojawić się w kilku tablicach. Dynamiczny linker przetwarza wszystkie relokacje w tych tablicach globalnych ofsetów zanim przekazuje kontrolę do jakiegokolwiek kodu w obrazie procesu, w ten sposób zapewniając dostępność absolutnych adresów podczas wykonywania. Wpis w tablicy jest zarezerwowany do trzymania adresów dynamicznej struktury, do której odwołuje się za pomocą symbolu _DYNAMIC. To pozwala programowi - takiemu jak dynamiczny linker - odnajdywać swoje własne struktury dynamiczne jeszcze nie przetwarzając własnch wpisów/pozycji relokacji. Jest to specjalnie ważne dla dynamicznego linkera, ponieważ musi on najpierw zainicjować się bez polegania na tym, że inne programy zrelokują jego obraz pamięci (?). W 32bitowej architekturze intela, wpisy pierwszy i drugi w tablicy globalnych ofsetów (globalnej tablicy ofsetów? ang. global offset table) są również zarezerwawane i opisuje je punkt ,,Tablica Procedur Łączenia'' poniżej. System może wybrać różne adresy segmentów pamięci dla tych samych obiektów współdzielonych w różnych programach; może nawet wybrać różne adresy bibliotek dla różnych wykonań tego samego programu. Jednakże segmenty pamięci nie zmieniają adresów od czasu stworzenia obrazu procesu. Tak długo jak długo istnieje proces, jego segmenty pamięci pozostają pod raz dopasowanymi adresami wirtualnymi. Format i interpretacja tablicy globalnych ofsetów są zależne od procesora. Dla 32-bitowej architektury Intela, do dostępu do tablicy może zostać użyty symbol _GLOBAL_OFFSET_TABLE_. + Rysunek 2-11: Tablica globalnych ofsetów extern Elf32_Addr _GLOBAL_OFFSET_TABLE_[]; Symbol _GLOBAL_OFFSET_TABLE_ może przebywać w środku sekcji .got, pozwalając zarówno na ujemne i dodatnie ,,zapisy'' do tablicy adresów. Tablica procedur łączenia Tak samo jak tablica globalnych ofsetów przekształca adresy niezależne od pozycji w bezwzględne lokalizacje, tak samo tablica procedur łączenia przekształca niezależne od pozycji w absolutne lokalizacje. (zmienić w tablice łączenia procedur!). Edytor łączenia nie może rozwiązać przekazywania wykonywania (takie jak wołania funkcji) pomiędzy jednym plikiem wykonywalnym lub współdzielonym a innym. Na skutek tego edytor łączenia aranżuje sytuację w której program przekazuje kontrolę do wpisów w tablicy łączenia procedur (ang. the link editor arranges to have the program transfer control to procedure linkage table). W architekturze Systemu V, tablice łączenia procedur przebywają w współdzielonym segmencie kodu, ale używają adresów z prywatnej globalnej tablicy ofsetów. Dynamiczny linker determinuje absolutne adresy przeznaczenia i odpowiednio modyfikuje obraz pamięci globalnej tablicy ofsetów. W ten sposób dynamiczny linker może przekształcać wpisy bez niweczenia niezależności od pozycji i współdzielenia kodu/tekstu programu. Pliki wykonywalne i współdzielone pliki obiektowe posiadają rozdzielne tablice łączenia procedur. + Rysunek 2-12: Bezwględna tablica procedur łączenia {*} .PLT0:pushl got_plus_4 jmp *got_plus_8 nop; nop nop; nop .PLT1:jmp *name1_in_GOT pushl $offset jmp .PLT0@PC .PLT2:jmp *name2_in_GOT pushl $offset jmp .PLT0@PC ... + Rysunek 2-13: Tablica procedur łączenia niezależna od pozycji. .PLT0:pushl 4(%ebx) jmp *8(%ebx) nop; nop nop; nop .PLT1:jmp *name1@GOT(%ebx) pushl $offset jmp .PLT0@PC .PLT2:jmp *name2@GOT(%ebx) pushl $offset jmp .PLT0@PC ... ZAUWAŻ: Tak jak to pokazuje rysunek, instrukcje tablicy procedur łączenia używają innego trybu adresowania operandów dla kodu absolutnego i zależnego od pozycji. Jest to jednak bez znaczenia, gdyz ich interfejs do linkera pozostaje taki sam. Postępując według kroków poniżej, dynamiczny linker i program ,,współpracują'' aby rozwiązać symboliczne odniesienia poprzez tablicę procedur łączenia i tablicę globalnych ofsetów. 1. Podczas pierwszego tworzenia obrazu pamięci programu, dynamiczny linker ustawia drugi i trzeci wpis w globalnej tablicy ofsetów na specjalne wartości. Kroki niżej wyjaśniają więcej na temat tych wartości. 2. Jeśli tablica łączenia procedur jest niezależna od pozycji, adres globalnej tablicy ofsetów musi przebywać w %ebx. Każdy współdzielony plik obiektowy w obrazie procesu posiada swoją własną tablicę łączenia procedur i kontrola przechodzi do wpisu w tablicy łączenia procedur tylko w środku tego samego pliku obiektowego. Stąd funkcja dokonująca wywołania (ang. calling function) jest odpowiedzialna za ustawienie bazowego rejestru globalnej tablicy ofsetów zanim odwoła się do wpisu w tablicy łączenia procedur. 3. Przyjmijmy dla przykładu że program wywołuje name1, co przekazuje kontrolę do nazwy/etykiety/symbolu (ang. label) .PLT1. 4. Pierwsza instrukcja powoduje skok do adresu w pozycji w globalnej tablicy ofsetów dla name1. Początkowo, globalna tanlica ofsetów przetrzymuje adres następnej instrukcji pushl a nie właściwy adres name1. 5. Program odkłada ofset relokacji (dalej po prostu ofset) na stos. Ofset relokacji jest 32-bitowym nieujemnym ofsetem w tablicy relokacji. Wyznaczona pozycja/wpis relokacji będzie posiadać typ R_386_JMP_SLOT, a jej ofset będzie specyfikować wpis w globalnej tablicy ofsetów użyty w poprzedniej instrukcji jmp. Wpis relokacji będzie również zawierać indeks tablicy symboli, w ten sposób zawiadamiając dynamicznego linkera do jakiego symbolu się odwołujemy, w tym przypadku name1. 6. Po odłożeniu ofsetu relokacji, program wykonuje skok do .PLT0, pierwszego wpisu w tablicy łączenia procedur. Instrukcja pushl odkłada wartość drugiego wpisu w globalnej tablicy ofsetów (got_plus_4 albo 4(%ebx) na stosie, w ten sposób podając dynamicznemu linkerowi słowo identyfikującej informacji. Następnie program wykonuje skok do adresu w trzecim wpisie globalnej tablicy ofsetów (got_plus_8 albo 8(%ebx), co przekazuje kontrolę dynamicznemu linkerowi. 7. Kiedy dynamiczny linker otrzymuje kontrolę, czyści (ang. unwinds) stos, patrzy na wyznaczoną pozycję relokacji, odnajduje wartość symbolu, zachowuje ,,prawdziwy'' adres dla name1 w globalnej tablicy ofsetów i przekazuje kontrolę do pożądanego przeznaczenia. 8. Następne wykonywanie wpisów w tablicy łączenia procedur będą już odwoływać się bezpośrednio do name1, bez wołania dynamicznego linkera po raz drugi. Oznacza to, że instrukcja jmp w .PLT1 przekaże kontrolę do name1, zamiast ,,przelatwywania przez'' (ang falling through) instrukcję pushl. Zmienna środowiskowa LD_BIND_NOW może zmienić zachowanie dynamicznego linkera. Jeśli jej wartość nie jest pusta, dynamiczny linkera wartościuje wpisy tablicy procedur łączenia zanim przekaże programowi kontrolę. Oznacza to że dynamiczny linker przetwarza pozycje relokacji o typie R_386_JMP_SLOT podczas inicjalizacji procesu. W innym przypadku dynamiczny linker wartościuje wpisy w tablicy łączenia procedur w ,,leniwy'' sposób, opóźniając rozwiązywanie symbolów i relokację do pierwszego wykonania wpisu tablicy. ZAUWAŻ: ,,Leniwe'' wiązanie zazwyczaj poprawia całkowitą wydajność aplikacji, ponieważ nieużywane symbole nie powodują niepotrzebnego nadmiaru (ang overhead) przy dynamicznym łączeniu. Jednakże mogą pojawić się dwie sytuacje w których ,,leniwe'' wiązanie może być niepożądane. Po pierwsze, pierwsze odwołanie do funkcji współdzielonego obiektu może zabrać więcej czasu niż następne wołania, ponieważ dynamiczny linker przechwytuje wołanie aby rozwiązać symbol. Niektóre aplikacje nie mogą tolerować takiego nieprzywidywalnego zachowania. Po drugie, jeśli pojawi się błąd i dynamiczny linker nie będzie mógł rozwiązać symbolu, dynamiczny linker zakończy program. Podczas ,,leinwego'' wiązania może to zdarzyć się w dowololnym czasie. I znowu, niektóre aplikacje nie mogą takiego zachowanie tolerować. Poprzez wyłączenie ,,leniwego'' wiązania, dynamiczny linker narzuca pojawianie się wszystkich błędów podczas inicjalizacji procesu, zanim aplikacja otrzyma kontrolę. Tablica Haszująca Tablica haszująca obiektów Elf32_Word wspiera dostęp do tablicy symboli. Nazwy/oznaczenia (ang.labels) pojawiają się niżej aby wyjaśnić organizację tablicy haszowej, ale nie są one częścią specyfikacji. + Rysunek 2-14: Haszująca tablica symboli nbucket nchain bucket[0] ... bucket[nbucket - 1] chain[0] ... chain[nchain - 1] Tablica ,,koszyk'' (ang bucket array) zawiera nbucket wpisów, a tablica łańcuchowa (chain array) zawiera nchain wpisów. Indeksy zaczynają się od zera. Zarówno ,,koszyk'' jak i ,,łańcuch'' zawierają indeksy tablicy symboli Tablica ,,łańcuch'' zawiera wpisy równoległe do tablicy symboli. Liczba wpisów w tablicy symboli powinna byc równa nchain; tak więc indeksy tablicy symboli akceptują również wpisów w tablicy ,,łańcuch''. Funkcja haszująca, pokazana niżej, na wejściu otrzymuje nazwę symbolu i zwraca wartość która może zostać użyta do obliczenia indeksu ,,koszyka''. Tak więc jeśli funkcja haszująca zwróci wartość x dla nazwy, bucket[x%nbucket] podaje indeks, y, do zarówno tablicy symboli jak i tablicy ,,łańcuch''. Jeżeli wpis w tablicy symboli nie odpowiada temu który chcieliśmy znaleźć, chain[y] podaje następny wpis w tablicy symboli z taką samą wartością haszową. Można w ten sposób podążać za łączami z tablicy ,,łańcuch'' (chain) dopóki albo wpis w tablicy symboli zawiera pożądaną nazwę albo wpis w tablicy ,,łańcuch'' zawiera wartość STN_UNDEF. + Rysunek 2-15: Funkcja haszująca unsigned long elf_hash(const unsigned char *name) { unsigned long h = 0, g; while (*name) { h = (h << 4) + *name++; if (g = h & 0xf0000000) h ^= g >> 24; h &= ~g; } return h; } Funkcje Inicjalizujące i Kończące Gdy dynamiczny linker zbuduje obraz procesu i przeprowadzi relokację, każdy obiekt współdzielony otrzymuje możliwość wykonania pewnego kodu inicjującego. Te funkcje inicjalizujące są wywoływane bez żadnego określonego porządku, ale wszystkie inicjalizacyjne funkcje obiektów współdzielonych wykonują się zanim plik wykonywalny otrzyma kontrolę. Podobnie, obiekty współdzielone mogą mieć funkcje kończące, które są wykonywane za pomocą mechanizmu atexit(BA_OS) gdy proces podstawowy zacznie sekwencję zakończenia. I znowu porządek w jakim dynamiczny linker wywoła te funkcje jest niezdefiniowany. Obiekty współdzielone wyznaczają swoje funkcje inicjalizujące i kończące za pomocą wpisów w DT_INIT i DT_FINI w strukturze dynamicznej opisanej w punkcie ,,Sekcje Dynamiczne'' powyżej. Zazwyczaj kod dla tych funkcji znajduje się w sekcjach .init i .fini, wspomnianych w ,,Sekcjach'' w części pierwszej. ZAUWAŻ: Chociaż atexit(BA_OS) przetwarzanie na zakończenie zwykle zostanie wykonane, nie ma żadnej gwarancji że zostanie wykonane po śmierci procesu. W szczególności proces nie wykona przetwarzania na zakończenie (kończącego, terminującego) jeśli wywoła _exit [zobacz exit(BA_OS)] lub jeśli proces umrze ponieważ otrzyma sygnał którego nie przechwycił lub nie zignorował. ________________________________________________________________ 3. Biblioteka C ________________________________________________________________ ========================== Biblioteka C ======================== Biblioteka C, libc, zawiera wszystkie symbole zawarte w libsys i na dodatek zawiera funkcje wypisane w następnych dwóch tablicach. Pierwsz tablica wypisuje funkcje z standardu ANSI C. + Rysunek 3-1: Zawartość libc, Nazwy bez synonimów abort fputc isprint putc strncmp abs fputs ispunct putchar strncpy asctime fread isspace puts strpbrk atof freopen isupper qsort strrchr atoi frexp isxdigit raise strspn atol fscanf labs rand strstr bsearch fseek ldexp rewind strtod clearerr fsetpos ldiv scanf strtok clock ftell localtime setbuf strtol ctime fwrite longjmp setjmp strtoul difftime getc mblen setvbuf tmpfile div getchar mbstowcs sprintf tmpnam fclose getenv mbtowc srand tolower feof gets memchr sscanf toupper ferror gmtime memcmp strcat ungetc fflush isalnum memcpy strchr vfprintf fgetc isalpha memmove strcmp vprintf fgetpos iscntrl memset strcpy vsprintf fgets isdigit mktime strcspn wcstombs fopen isgraph perror strlen wctomb fprintf islower printf strncat Dodatkowo, libc zawiera następujące usługi: + Rysunek 3-2: Zawartość libc, nazwy z synonimami. __assert getdate lockf ** sleep tell ** cfgetispeed getopt lsearch strdup tempnam cfgetospeed getpass memccpy swab tfind cfsetispeed getsubopt mkfifo tcdrain toascii cfsetospeed getw mktemp tcflow _tolower ctermid hcreate monitor tcflush tsearch cuserid hdestroy nftw tcgetattr _toupper dup2 hsearch nl_langinfo tcgetpgrp twalk fdopen isascii pclose tcgetsid tzset __filbuf isatty popen tcsendbreak _xftw fileno isnan putenv tcsetattr __flsbuf isnand ** putw tcsetpgrp fmtmsg ** lfind setlabel tdelete ** = Funkcja jest na poziomie 2 w SVID wydaniu 3 i stąd również na poziomie 2 w ABI. Poza symbolami wymienionymi w tablicy ,,Z synonimami'' powyżej, synonimy w formie _ istnieją dla pozycji które nie są wymienione z podkreśleniem poprzedzającym ich nazwę. Stąd libc zawiera na przykład zarówno getopt jak i _getopt. Z funkcji powyżej wymienonych, następujące nie są gdzie indziej zdefiniowane: int __filbuf(FILE *f); Ta funkcja zwraca następny znak w wejściu dla f, zapełniają jego (swój? ang. its) bufor jeśli potrzeba. Zwraca EOF jeśli pojawił się błąd. int __flsbuf(int x, FILE *f); Ta funkcja opróżnia znaki dla wejścia dla f tak jakby było wywołane putc(x,f) i następnie dołącza wartość x do wynikowego strumienia wyjściowego. Zwraca EOF jeśli pojawił się błąd i x wpw. int _xftw(int, char *, int (*)(char *, struct stat *, int), int); Odwołania do funkcji ftw(BA_LIB) są podmapowane pod tą funkcję gdy aplikacje są kompilowane. Ta funkcja jest identyczna jak ftw(BA_LIB) za wyjątkiem tego że _xftw() bierze wstawiony pierwszy argument, który musi posiadać wartość 2. Zobacz inne punkty w tym rozdziale po więcej udogodnień SVID, ANSI C i POSIXa. Zobacz ,,Interfes Danych systemowych'' później w tym rozdziale po więcej informacji. (EEE? od tłumacza - ale rozdział właśnie się urywa!) Globalne symbole danych Biblioteka libc wymaga aby były zdefiniowane pewne zewnętrzne symbole danych aby jej funkcje działały poprawnie. Wszystkie symbole danych wymagane dla biblioteki libsys muszą zostać zapewnione przez libc, tak samo jak symbole wymienione w tablicy poniżej. Dla formalnej deklaracji obiektów danych reprezentowanych przez te symbole zobacz w Definicji Interfejsu Systemu V, Wydanie Trzecie lub w punkcie ,,Definicje danych'' rodziału szóstego dla odpowiedniego dodatku o procesorze do ABI Systemu V. Dla wpisów w następujących tabelach które są w formie -_ , obydwa symbole w każdej parze reprezentują te same dane. Synonimy z podkreśleniami są dostarczane dla zgodności z standardem ANSI C. + Rysunek 3-3: Zawartość libc, Globalne Zewnętrzne symbole danych getdate_err optarg _getdate_err opterr __iob optind optopt