Die Binärformate a.out und ELF unter Linux

So ein Mist!

Fast jeder, der sich schon länger als etwa ein Jahr mit Linux beschäftigt, ist in dem letzten Jahr auf solche Stolperstellen getroffen:

Ich habe mir dieses ganz tolle Paket besorgt, und wenn ich das Programm starten will erhalte ich nur dies. Die Datei doch aber vorhanden und das x-Bit gesetzt!?

$ ./cool
bash: ./cool: No such file or directory
$ ls -l cool
-rwxr-xr-x   1 roro     user       192460 Mar 27 22:18 cool
$

Oder das Programm meckert, daß eine Bibliothek nicht gefunden wird:

$ ./cool
./cool: can't load library 'libXt.so.6'
$
Ich wollte PGP (Pretty Good Privacy) aus den Quellen selbst übersetzen. Obwohl es darauf vorbereitet war unter Linux kompiliert zu werden, steigt der Make-Lauf aus:
$ make linux
...
_zmatch.o(.text+0x77): undefined reference to `_window'
_zmatch.o(.text+0x94): undefined reference to `_window'
_zmatch.o(.text+0xa4): undefined reference to `_match_start'
make[1]: *** [pgp] Error 1
make[1]: Leaving directory `/home/roro/zz7/src'
make: *** [linux] Error 2
$

Diese Probleme sind auf eine wichtige Umstellung innerhalb des Linux-Systems zurückzuführen - die Umstellung des vorrangigen Binärformates. Dieses Format ist im Grunde nur eine Beschreibung über den Aufbau von Dateien und die Interpretation dieses Bytestromes um daraus letztlich einen Prozess zu erzeugen, der sich im Arbeitsspeicher des Recheners befindet. Es sollte aber einen gewichtigen Grund geben, warum die Entwickler des Linux-Kernels, der Bibliotheken und der Tools zur Programmierung solch grundlegende Änderung vornehmen und dabei sehr viele Menschen vor den Kopf stossen.

Alternativen für ein Binärformat

Viele Formate für ausführbare Dateien möglich, u.a.:

Besonderheiten von ELF

Typen von ELF-Files

Aufbau von Executables. Dies ist ähnlich in allen Formaten (Header, Sektionen)

Linking View            Execution View
--------------------    --------------------
ELF header              ELF header
--------------------    --------------------
Program header table    Program header table
optional
--------------------    --------------------
Section 1
--------------------    Segment 1
. . .
--------------------    --------------------
Section n
--------------------    Segment 2
. . .
--------------------    --------------------
. . .                   . . .
--------------------    --------------------
Section header table    Section header table
                        optional
--------------------    --------------------

Der ELF-Header ist so aufgebaut:

#define EI_NIDENT       16

typedef struct {
        unsigned char   e_ident[EI_NIDENT]; /* 0xF, "ELF", ...                  */
        Elf32_Half      e_type;             /* No Type, Relocatable, Executable,
                                               Shared Object, Core, ...         */
        Elf32_Half      e_machine;          /* No machine, AT&T WE 32100, SPARC
                                               Intel 80386, Motorola 68000, 88000,
                                               Intel 80860, MIPS RS3000, ...    */
        Elf32_Word      e_version;          
        Elf32_Addr      e_entry;            /* Einsprungspunkt                  */
        Elf32_Off       e_phoff;            /* Offset Programm-Header           */
        Elf32_Off       e_shoff;            /* Offset Section-Header            */
        Elf32_Word      e_flags;
        Elf32_Half      e_ehsize;           /* Groesse Object-File-Header       */
        Elf32_Half      e_phentsize;        /* Groesse Programm-Header-Tabellen */
        Elf32_Half      e_phnum;            /* Anzahl Programm-Header           */
        Elf32_Half      e_shentsize;        /* Groesse Section-Header           */
        Elf32_Half      e_shnum;            /* Anzahl Section-Header            */
        Elf32_Half      e_shstrndx;         /* Index auf Strings-Tabelle        */
} Elf32_Ehdr;
Das Format ist also auf verschiedenste Maschinentypen, Byteorder, 32/64-bit vorbereitet. Das hat aber wie gesagt nichts mit Austauschbarkeit oder Ausführbarkeit von Programmen zu tun. Für Linux war insbesondere die durch das ELF-Format vorgegebene Technik des dynamischen Bindens und der Shared Objects interessant, das in a.out sehr umständlich und aufgesetzt zu realisieren war.

Fast alle Formate sind in Sektionen aufgebaut. Es ist zweckmäßig bestimmte Informationen zu gruppieren.

Sektionen:

Das .COM-Format unter MS-DOS hat z.B. keine Sektionen. Dort werden alle Informationen in 64KB zusammengemischt und als Datei abgelegt. Diese wird zur Abarbeitung in den Speicher geladen und dann einfach in diesem 64KB Segment an Offset 100h gesprungen.

Während a.out nur die drei obigen Sektionen besitzt, hat ELF eine ganze Menge mehr. Zusätzlich können weitere selbst definiert und benutzt werden. Hier nur eine kurze Aufzählung von Sektionen die eine vorgegebene Bedeutung haben.

.bss
.comment
.data
.data1
.debug
.dynamic
.dynstr
.dynsym
.fini
.got
.hash
.init
.interp
.line
.note
.plt
.relname
.relaname
.rodata
.rodata1
.shstrtab
.strtab
.symtab
.text

Wie schon angedeutet: Shared Libraries sind durch das a.out Format nicht vorgesehen. Nun ist es trotzdem möglich eine Implementierung zu entwickeln. SunOS hat dies gemacht und kommt dem späten ELF sehr nahe. Auch unter Linux gab es eine Lösung. Diese hatte jedoch eine Nachteil. Von den 4GB virtuellem Speicher wurde innerhalb eines GB der Bereich an Shared Libraries vergeben. Somit mussten sich Entwickler abstimmen, wer für welche Bibliothek einen bestimmten Adressbereich benutzt. Ein Vorteil dabei ist die genaue Kenntniss des Adresse einer Funktion zur Startzeit eines Programmes.

Entwickung der Shared Objects:

Performance

Insgesamt wird ELF etwas langsamer sein als a.out (0% bis 5%, 3%). Es gab dazu mehrere Untersuchungen. Direkt aus der ELF Technologie ableitbare Verluste entstehen durch:

Weitere Performance-Einbußen sind für eine Übergangszeit durch das eventuelle Nebenherbestehen von a.out und ELF zu erwarten (Shared Objects sind u.U. doppelt im Arbeitsspeicher).

Voraussetzungen um ELF zu nutzen:

Zusätzlich für Entwicklung:

Motivation

Zitat Daniel Barlow (Februar '96 auf linux-kernel)

Re: a.out vs. ELF speed (WAS Re: Will a.out disappear?)

Everybody. Please. Stop it. We've been over this `elf is slower' `not detectably so' `I can detect it' `it's probably a setup difference' argument a billion times already since September 1994 (yes, 1994) and NOTHING HAS EVER COME OF IT ON _ANY_ PREVIOUS OCCASION.

If you're anti-elf on speed or aesthetics or backward compatibility or hassle grounds, you have my sympathies. If you're pro-elf, shut up, stop evangelising, they'll all have to come round anyway if they want to play Linux Quake or run the Java port. They'll learn ;-)

Thank you for your attention.

Daniel

Zusammenfassung

Quellen


Rolf Rossius