Kod i dane w pliku binarnym ELF są logicznie podzielone na ciągłe, nienakładające się na siebie fragmenty zwane sekcjami. Sekcje nie mają żadnej z góry określonej struktury; zamiast tego struktura każdej sekcji zmienia się w zależności od zawartości. W rzeczywistości sekcja może w ogóle nie mieć żadnej konkretnej struktury; często sekcja jest niczym więcej niż niestrukturyzowanym blobem kodu lub danych. Każda sekcja jest opisana nagłówkiem sekcji, który oznacza właściwości sekcji i pozwala zlokalizować bajty należące do sekcji. Nagłówki sekcji dla wszystkich sekcji w pliku binarnym znajdują się w tabeli nagłówków sekcji. Ściśle rzecz biorąc, podział na sekcje ma na celu zapewnienie wygodnej organizacji do wykorzystania przez linker (oczywiście sekcje mogą być również analizowane przez inne narzędzia, takie jak narzędzia do statycznej analizy binarnej). Oznacza to, że nie każda sekcja jest faktycznie potrzebna podczas konfigurowania procesu i pamięci wirtualnej w celu wykonania pliku binarnego. Niektóre sekcje zawierają dane, które w ogóle nie są potrzebne do wykonania, takie jak informacje symboliczne lub dotyczące relokacji. Ponieważ sekcje mają na celu zapewnienie widoku tylko dla linkera, tabela nagłówków sekcji jest opcjonalną częścią formatu ELF. Pliki ELF, które nie wymagają linkowania, nie muszą mieć tabeli nagłówków sekcji. Jeśli nie ma tabeli nagłówków sekcji, pole e_shoff w nagłówku pliku wykonywalnego jest ustawiane na zero. Aby załadować i wykonać plik binarny w procesie, potrzebna jest inna organizacja kodu i danych w pliku binarnym. Z tego powodu pliki wykonywalne ELF określają inną organizację logiczną, zwaną segmentami, która jest używana w czasie wykonywania (w przeciwieństwie do sekcji, które są używane w czasie linkowania). Omówię segmenty później w tym rozdziale, gdy będę mówił o nagłówkach programów. Na razie skupmy się na sekcjach, ale pamiętaj, że organizacja logiczna, którą tutaj omawiam, istnieje tylko w czasie linkowania (lub gdy jest używana przez narzędzie do analizy statycznej), a nie w czasie wykonywania. Zacznijmy od omówienia formatu nagłówków sekcji. Następnie przyjrzymy się zawartości sekcji.Listing Wypis 2-4 pokazuje format nagłówka sekcji ELF określony w pliku /usr/include/elf.h.
Listing 2-4
typedef struct {
uint32_t sh_name; /* Section name (string tbl index) */
uint32_t sh_type; /* Section type */
uint64_t sh_flags; /* Section flags */
uint64_t sh_addr; /* Section virtual addr at execution */
uint64_t sh_offset; /* Section file offset */
uint64_t sh_size; /* Section size in bytes */
uint32_t sh_link; /* Link to another section */
uint32_t sh_info; /* Additional section information */
uint64_t sh_addralign; /* Section alignment */
uint64_t sh_entsize; /* Entry size if section holds table */
} Elf64_Shdr;
Pole sh_name
Jak widać w Listing 2-4, pierwsze pole w nagłówku sekcji nazywa się sh_name. Jeśli jest ustawione, zawiera indeks do tabeli ciągów. Jeśli indeks jest zerowy, oznacza to, że sekcja nie ma nazwy. W Sekcji 2.1 omówiłem specjalną sekcję o nazwie .shstrtab, która zawiera tablicę ciągów zakończonych zerem, po jednym dla każdej nazwy sekcji. Indeks nagłówka sekcji opisującego tabelę ciągów jest podany w polu e_shstrndx nagłówka wykonywalnego. Dzięki temu narzędzia takie jak readelf mogą łatwo znaleźć sekcję .shstrtab, a następnie zindeksować ją za pomocą pola sh_name każdego nagłówka sekcji (w tym nagłówka .shstrtab), aby znaleźć ciąg opisujący nazwę danej sekcji. Dzięki temu analityk może łatwo zidentyfikować cel każdej sekcji.