Executable and Linking Format
Das Executable and Linking Format (kurz ELF) ist ein Dateiformat für ausführbare Dateien. Es gibt 3 verschiedene Arten von ELF-Dateien:
- executable
- relocatable
- shared object
Inhaltsverzeichnis
Aufbau
ELF-Header
Der ELF-Header ist wie folgt aufgebaut.
| Name | Offset | Größe | Beschreibung | 
|---|---|---|---|
| e_ident | 0x00 | 16 | Enthält Daten zur Identifikation | 
| e_type | 0x10 | 2 | Enthält den ELF-Typ | 
| e_machine | 0x12 | 2 | Beschreibt die benötigte Architektur | 
| e_version | 0x14 | 4 | Version | 
| e_entry | 0x18 | 4 | Entrypoint | 
| e_phoff | 0x1C | 4 | Das Offset des Programm-Headers | 
| e_shoff | 0x20 | 4 | Das Offset des Section-Headers | 
| e_flags | 0x24 | 4 | Verschiedene Flags | 
| e_ehsize | 0x28 | 2 | Größe des ELF-Headers | 
| e_phentsize | 0x2A | 2 | Größe eines Eintrags im Programm-Header | 
| e_phnum | 0x2C | 2 | Anzahl an Einträgen im Programm-Header | 
| e_shentsize | 0x2E | 2 | Größe eines Eintrags im Section-Header | 
| e_shnum | 0x30 | 2 | Anzahl an Einträgen im Section-Header | 
| e_shstrndex | 0x32 | 2 | Assoziiert einen Eintrag im Section-Header mit der String-Tabelle | 
e_ident
Das Feld e_ident ist 16 Byte groß und enthält Werte zur Identifikation der ELF-Datei. Dies sind die Indizes:
| Name | Wert | Beschreibung | 
|---|---|---|
| EI_MAG0 | 0 | Datei-Identifikation | 
| EI_MAG1 | 1 | Datei-Identifikation | 
| EI_MAG2 | 2 | Datei-Identifikation | 
| EI_MAG3 | 3 | Datei-Identifikation | 
| EI_CLASS | 4 | Datei-Klasse | 
| EI_DATA | 5 | Prozessorspezifische Datenkodierung | 
| EI_VERSION | 6 | Datei-Version | 
| EI_PAD | 7 | Letztes genutztes Byte in e_ident | 
| EI_NIDENT | 16 | Anzahl an Bytes in e_ident | 
EI_MAG0 bis EI_MAG3
Diese Bytes werden zur Datei-Identifikation benutzt.
| Name | Wert | Position | 
|---|---|---|
| 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
Die Klasse gibt unter anderen an, wie viel Bits zur Adressierung verwendet werden. Also eine 32-Bit-Maschine sollte überprüfen, ob der Code auch wirklich auf 32-Bit-Maschinen geht.
| Name | Wert | Beschreibung | 
|---|---|---|
| ELFCLASSNONE | 0 | Ungültige Klasse | 
| ELFCLASS32 | 1 | 32-Bit-Objekte | 
| ELFCLASS64 | 2 | 64-Bit-Objekte | 
EI_DATA
Das Byte e_ident[EI_DATA] gibt an, in welchen Format Daten gespeichert werden, Little-Endian oder Big-Endian.
| Name | Wert | Beschreibung | 
|---|---|---|
| ELFDATANONE | 0 | Ungültig | 
| ELFDATA2LSB | 1 | Little-Endian | 
| ELFDATA2MSB | 2 | Big-Endian | 
EI_VERSION
Hier sollte das gleiche wie in e_version drinstehen, also EV_CURRENT.
EI_PAD
Alle Bytes in e_ident ab EI_PAD (inklusive) werden nicht benutzt und sollten auf Null gesetzt sein.
Architektur-Identifikation
Für einen x86-Prozessor sollten folgende Felder folgende Werte haben:
| Feld | Wert | 
|---|---|
| e_ident[EI_CLASS] | ELFCLASS32 | 
| e_ident[EI_DATA] | ELFDATA2LSB | 
| e_machine | EM_386 | 
e_type
Dieses Feld beschreibt, welches ELF-Format benutzt wird:
| Name | Wert | Beschreibung | 
|---|---|---|
| ET_NONE | 0 | Kein Typ | 
| ET_REL | 1 | Relocatable Datei | 
| ET_EXEC | 2 | Ausführbare Datei | 
| ET_DYN | 3 | Shared-Object-Datei | 
| ET_CORE | 4 | Kerndatei | 
| ET_LOPROC | 0xff00 | Prozessorspezifisch | 
| ET_HIPROC | 0xffff | Prozessorspezifisch | 
Für eine ausführbare ELF-Datei müsste hier ET_EXEC stehen.
e_machine
Wie oben bereits erwähnt, enthält dieses Element den Prozessor-Typ.
| Name | Wert | Beschreibung | 
|---|---|---|
| EM_NONE | 0 | Kein Typ | 
| 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 | 
| EM_X86_64 | 62 | AMD64 | 
e_version
Beschreibt die ELF-Version der Datei.
| Name | Wert | Beschreibung | 
|---|---|---|
| EV_NONE | 0 | Ungültige Version | 
| EV_CURRENT | 1 | Aktuelle Version | 
e_entry
Virtuelle Adresse des Entrypoints
e_phoff
Offset in der Datei, an dem die Programm-Header steht. Wenn die Datei keinen Programm-Header enthält, steht hier 0.
e_shoff
Offset in der Datei, an dem der Section-Header steht. Wenn die Datei keinen Section-Header enthält, steht hier 0.
e_flags
Dieses Feld enthält prozessorspezifische Flags.
e_ehsize
Größe des ELF-Headers.
e_phentsize
Dieses Feld gibt an, wie groß ein Eintrag im Programm-Header ist.
e_phnum
Gibt an, wie viele Einträge im Programm-Header enthalten sind. Das Produkt von e_phentsize und e_phnum ergibt die insgesamte Größe des Programm-Headers. Wenn die Datei keinen Programm-Header hat, steht hier 0.
e_shentsize
Dieses Feld gibt an, wie groß ein Eintrag im Section-Header ist.
e_shnum
Gibt an, wie viele Einträge im Section-Header enthalten sind. Das Produkt von e_shentsize und e_shnum ergibt die insgesamte Größe des Section-Headers. Wenn die Datei keinen Section-Header hat, steht hier 0.
e_shstrndx
Gibt an, welcher Eintrag im Section-Header mit der String-Tabelle verknüpft wird.
Programm-Header
Der Programm-Header besteht aus e_phnum Einträgen, wobei jeder e_phentsize groß ist. Der Programm-Header ist also insgesamt e_phnum*e_phentsize Bytes groß. Im Programm-Header stehen z. B. der Text- oder Daten-Teil eines Programms. Ein Eintrag im Programm-Header ist wie folgt aufgebaut:
| Name | Offset | Größe | Beschreibung | 
|---|---|---|---|
| p_type | 0x00 | 4 | Typ des Segments | 
| p_offset | 0x04 | 4 | Dateioffset, an dem das Segment steht | 
| p_vaddr | 0x08 | 4 | Virtuelle Adresse, an die das Segment kopiert werden soll | 
| p_paddr | 0x0C | 4 | Physische Adresse (meist irrelevant) | 
| p_filesz | 0x10 | 4 | Größe des Segments in der Datei | 
| p_memsz | 0x14 | 4 | Größe des Segments, das es im Speicher haben soll | 
| p_flags | 0x18 | 4 | Flags | 
| p_align | 0x1C | 4 | Alignment | 
p_type
Dieses Feld gibt den Typ des Segments an, für eine ausführbare ELF-Datei ist vor allem PT_LOAD relevant. Die folgenden Beschreibungen sind auf PT_LOAD bezogen (es kann sein, dass sie auch auf andere Typen zutreffen).
| Name | Wert | Beschreibung | 
|---|---|---|
| PT_NULL | 0 | Ungültiges Segment | 
| PT_LOAD | 1 | Ladbares Segment | 
| PT_DYNAMIC | 2 | Dynamisches Segment | 
| PT_INTERP | 3 | Position eines 0-terminierten Strings, der den Interpreter angibt. | 
| PT_NOTE | 4 | Universelles Segment | 
| PT_SHLIB | 5 | Shared Lib | 
| PT_PHDIR | 6 | Gibt Position und Größe des Programm-Headers an. | 
| PT_TLS | 7 | Thread-Local Storage | 
| PT_LOOS | 0x60000000 | Reserviert für betriebssystemspezifische Erweiterungen | 
| PT_HIOS | 0x6fffffff | |
| PT_LOPROC | 0x70000000 | Reserviert für prozessorspezifische Erweiterungen | 
| PT_HIPROC | 0x7fffffff | 
Außerdem habe ich noch diese (betriebssystemspezifischen) Werte in Linux-Header-Dateien (http://www.cs.fsu.edu/~baker/devices/lxr/http/source/linux/include/linux/elf.h#L35) gefunden.
| Name | Wert | 
|---|---|
| PT_GNU_EH_FRAME | PT_LOOS+0x474e550 | 
| PT_GNU_STACK | PT_LOOS+0x474e551 | 
p_offset
Offset in der Datei, an dem das Segment beginnt.
p_vaddr
Virtuelle Adresse, an die das Segment kopiert werden soll.
p_paddr
Gibt die physische Zieladresse für Systeme an, für welche dies relevant ist.
p_filesz
Enthält die Größe des Segments in der Datei. Kann eventuell auch 0 sein, z. B. für die BSS-Sektion.
p_memsz
Gibt die Größe des Segments im RAM an. Kann 0 sein.
p_flags
Beschreibt Flags, z. B. Zugriffsrechte.
| Name | Wert | Beschreibung | 
|---|---|---|
| PF_X | 1 | Ausführbares Segment | 
| PF_W | 2 | Beschreibbares Segment | 
| PF_R | 4 | Lesbares Segment | 
Damit kann man den Wert berechnen: <c>
flags = (read?PF_R:0)|(write?PF_W:0)|(exe?PF_X:0);
</c>
p_align
Alignment des Segmentes. Wenn 0 oder 1 angegeben wird, ist kein Alignment erforderlich. Ansonsten muss es eine 2er-Potenz sein (2, 4, 8, 16, ...).
Überprüfen
Überprüfen kann man die ELF-Datei mit den folgenden Schritten:
- MAGIC-String überprüfen
- Architektur überpüfen
- Klasse (32bit/64bit) überprüfen
- Daten-Format (Little-/Big-Endian) überprüfen
- Version überprüfen
Siehe auch
Links
- ELF für x86
- Wikipedia: ELF (sehr gute Sammlung von Links)

