Wiki
Clone wikichocos / ELF - Notes
- ELF (Executable and Linkable Format) - powszechnie stosowany zarówno do plików wykonywalnych jak i bibliotek
- Najpierw nagłówek z podstawowymi informacjami na temat zawartosci a potem juz kolejne sekcje pamieci
- W headerze jest zapisane gdzie są sekcje programu i jak powinny być załadowane
Relokacja
Relokacja - dzięki niej można dostosować adresy programu tak, aby uniezależnić go od tego w jakim miejscu umieści je system (teoretycznie) * Tabela-Relokacji - to część pliku w formacie ELF, która zawiera informacje potrzebne do procesu relokacji. Tabela ta zawiera wpisy dla każdego fragmentu kodu lub danych, które muszą być zmodyfikowane, gdy plik jest ładowany do pamięci w innym miejscu, niż został pierwotnie skompilowany.
Każdy wpis w tabeli relokacji wskazuje, gdzie w pliku znajduje się odniesienie do adresu, które musi być zmodyfikowane, oraz jakiego rodzaju modyfikacja jest wymagana. Dzięki temu, gdy system operacyjny ładuje plik do pamięci, może przejrzeć tabelę relokacji i odpowiednio zaktualizować wszystkie odniesienia do adresów, zapewniając, że program będzie działał poprawnie, niezależnie od tego, gdzie w pamięci zostanie umieszczony.
Wpis w tablicy relokacji:
//========================================================================================================================================== /** * @brief stores relocation entry (extended from RELA and not from REL) */ //========================================================================================================================================== typedef struct { oC_Elf_Addr_t Offset; //!< Location at which to apply the relocation action. oC_Elf_Word_t Info; //!< Symbol table index and type of the relocation oC_Elf_Sword_t Addend; //!< Constant addend used to compute the value to be stored into the relocatable field } oC_Elf_RelocationEntry_t;
Tablice relokacji są tworzone per sekcja, więc np tablica relokacji dla sekcji .text
będzie się nazywać .rel.text
albo .rela.text
Przykład tablicy relokacji:
Dla kodu:
#include <stdio.h> volatile int globalVar = 10; int main() { globalVar = 11; printf("globalVar: %d\n", globalVar); return 0; }
Skompilowanego a flagą -fPIC
Global Offset Table
Global Offset Table (got) zawiera wszystkie referencje do zmiennych i funkcji znajdujących się w programie. Ważne jest, aby zauważyć, że mogą to być zarówno odwołania do samego programu jak i bibliotek współdzielonych. Jako, że ChocoOS nie wspiera bibliotek współdzielonyc, to u nas są to tylko odwołania do symboli tego programu (to upraszcza nam trochę kod dynamicznego linkera). Sekcja got powstaje wtedy, kiedy użyjemy flagi fPIC podczas kompilacji.
Aby zaktualizować sekcję GOT ChocoOS wykorzystuje następujący kod:
// W pliku ELF jest sekcja o nazwie 'sections' (pochodzi ona z linkera) outFile->sections = oC_Elf_GetSectionHeaderByName(outFile,".sections"); ... // Odczytujemy zawartość `sections` oC_Program_Sections_t * sectionsHeader = File->Sections.sections.Address; // BinSize pochodzi z linkera: https://bitbucket.org/chocos/chocos/src/76e42f3b1e912c8679784636815879fcdd9e19bf/program.ld#lines-108 oC_MemorySize_t sectionsSize = sectionsHeader->BinSize; // Kopiujemy zawartość binarną programu do bufora `SectionsBuffer` memcpy( ProgramContext->SectionsBuffer, File->FileBuffer, sectionsSize ); // Odczytujemy adres i rozmiar sekcji GOT uint32_t * gotEntries = ProgramContext->Sections.got.Address; uint32_t leftSize = ProgramContext->Sections.got.Size; while(leftSize > 0) { // Aktualizujemy wszystkie wpisy w GOT tak, aby wskazywały na bufor w pamięci, który zarezerwowaliśmy i który zainicjalizowaliśmy zawartością sekcji `sections` // Możemy sobie pozwolić na takie uproszczenie, ponieważ nie wspieramy bibliotek współdzielonych, więc // wszystkie wpisy odnoszą się do tego bufora (*gotEntries) += (uint32_t)ProgramContext->SectionsBuffer; leftSize -= sizeof(uint32_t); gotEntries++; }
Ładowanie pliku i proces odczytywania poszczególnych sekcji
W nagłówku pliku mamy coś takiego:
oC_Elf_Half_t SectionHeaderTableEntrySize; //!< Holds the size in bytes of one entry in section header table oC_Elf_Half_t SectionHeaderTableNumberOfEntries; //!< Number of entries in section header table oC_Elf_Half_t SectionHeaderNameStringTableIndex; //!< Holds the section header table index of the entry associated with the section name string table
Czyli mamy section header table - to jest tablica, w której każdy element jest jednym headerem sekcji. Każdy header jest tego samego rozmiaru i znajduje się jeden po drugim. W FileHeaderze mamy też zapisane ile tych sekcji w pamięci jest, oraz który wpis w tej section header table zawiera informacje na temat sekcji, która przechowuje nazwy poszczególnych sekcji.
Poruszanie się po nagłówkach sekcji
Czyli jak chcemy odczytać jakąś sekcję, to:
- Używamy
SectionHeaderTableOffset
w nagłówku pliku, aby zlokalizować tablicę sekcji - Używamy
SectionHeaderTableEntrySize
z nagłówka pliku aby przeszukać kolejne wpisy w tablicy - Używamy
SectionHeaderNameStringTableIndex
aby zlokalizować nagłówek z nazwami wszystkich sekcji
Każdy section header wygląda tak:
//========================================================================================================================================== /** * @brief holds header of the ELF sections */ //========================================================================================================================================== typedef struct { oC_Elf_Word_t NameIndex; //!< Name of the section - its value is an index into the section header string table oC_Elf_Word_t Type; //!< This member categorizes the section’s contents and semantics. See #oC_Elf_SectionType_t for more oC_Elf_Word_t Flags; //!< Attributes flags. See #oC_Elf_SectionFlag_t for more oC_Elf_Addr_t Address; //!< If the section appear in the memory image of a process, this member gives the address at which the section's first byte should reside oC_Elf_Off_t Offset; //!< Byte offset from the beginning of the file to the first byte in the section oC_Elf_Word_t Size; //!< The section size in bytes oC_Elf_Word_t Link; //!< Section header table index link, interpretation depends on the section type oC_Elf_Word_t Info; //!< Extra information that depends on the section type oC_Elf_Word_t AddressAlign; //!< Alignment of the address of the section oC_Elf_Word_t EntrySize; //!< Some sections holds table of symbols, and this is size of the entry in the table. If it is 0, the section does not hold a table of fixed size entries } oC_Elf_SectionHeader_t;
Offset
wskazuje na miejsce w pliku, gdzie znajdują się dane danej sekcji. Size
informuje nas o tym jak duża jest ta sekcja. Jeśli jakaś sekcja przechowuje tablicę o stałym rozmiarze elementu, to EntrySize
informuje nas o tym jak duży jest wpis w tej tablicy. Warto zauważyć, że sekcja z nazwami sekcji nie jest tablicą, więc NameIndex
jest tak naprawdę offsetem w tej sekcji
Odszukanie sekcji po nazwie:
Iterujemy od pierwszej sekcji do ostatniej, używamy NameIndex
z section headera, aby odczytać nazwę sekcji z sekcji nazw. Jak znajdziemy to używamy Offset
z section headera, żeby odczytać dane sekcji
Updated