Zum Inhalt springen

Common Object File Format

aus Wikipedia, der freien Enzyklopädie
Dies ist eine alte Version dieser Seite, zuletzt bearbeitet am 17. März 2014 um 16:52 Uhr durch Horst Gräbner (Diskussion | Beiträge) (R). Sie kann sich erheblich von der aktuellen Version unterscheiden.

Das Common Object File Format (COFF; deutsch allgemeines Objektdateiformat) ist ein Binärformat für Programme und Objektdateien. Es wurde von AT&T für Unix System V-Systeme eingeführt[1] und findet heutzutage vor allem für Windows Verwendung. Für Dateiendungen wird es gerne als COF, OBJ oder .LIB abgekürzt. Auch die GNU Compiler Collection beherrscht dieses Format und nutzt es insbesondere für Cygwin-Targets.

Geschichte

Ursprünglich wurde das a.out Format für ausführbare Dateien unter Unix verwendet. Dieses unterstützte jedoch moderne Entwicklungen wie eingebettete Debugging-Informationen oder dynamische Bibliotheken nicht mehr. Deshalb fügte AT&T dem Release 3 vom Unix System V Unterstützung für das Common Object File Format hinzu. Dadurch wurden unter System V erstmals dynamische Bibliotheken ermöglicht. [2] Da das Originale COFF designtechnich beschränkt war, entwickelten sich unterschiedliche Dialekte unter den Unix Herstellern (z.B. XCOFF in AIX von IBM[3], ECOFF von SGI und anderen). Mit dem Release 4 von System V im Jahre 1989 ersetzte AT&T COFF durch ELF[4]. Moderne Unix und Linux Systeme unterstützen COFF nicht mehr, allerdings wird es für Eingebettete Systeme noch verwendet[5]. Unter Windows NT war COFF von Anfang an das Standard Dateiformat für Bibliotheken und ausführbare Dateien. Allerdings unterscheidet sich der Windows-Dialekt Portable Executable (PE) (manchmal auch PE/COFF) geringfügig von COFF.[6]

Struktur

Eine COFF Datei besteht aus mehreren Teilen. Sie beginnt mit dem File Header und einem Optional Header. Dann folgt eine Anzahl von Sektionen, die ebenfalls über einen Header, eine Datensektion, einem Bereich für Liniennummerneinträge und eine Bereich für Relokationseinträgen bestehen. Am Schluss der Datei folgt eine Symboltabelle und eine Zeichenkettentabelle.

Der File Header steht am Anfang einer Datei. In ihm sind Daten die den Aufbau der gesamten Datei betreffen gespeichert. Dazu gehört die Magische Zahl, die für die Unterschiedlichen Dialekte (PE, XCOFF etc.) unterschiedlich ist, ein Timestamp mit dem Zeitpunkt der Erstellung der Datei sowie die Größe anderer (variablen) Sektionen. Zudem können mittels Flag verschiedene Eigenschaften der Datei definiert werden (z.B. ob sie ausführbar ist).

struct filehdr {
    unsigned short  f_magic;        /* Magische Zahl */
    unsigned short  f_nscns;        /* Anzahl der Sektionen in der Datei */
    long            f_timdat;       /* Timestamp der Erstellung */
    long            f_symptr;       /* Pointer zur '''Symboltabelle''' */
    long            f_nsyms;        /* Größe der '''Symboltabelle''' */
    unsigned short  f_opthdr;       /* Größe der ''optional Header''' */
    unsigned short  f_flags;        /* Verschiedene Flags */
};

Der Optionale Header enthält unterschiedliche Daten in den verschiedenen Dialekten. Oft wird er genutzt um Daten zur Ausführung zu enthalten (z.B. die Eintstiegsadresse). Da der optionale Header unterschiedlich lang sein kann, ist seine Größe in dem File Header gespeichert.

Der Sektion Header enthält Daten über die Sektion, insbesondere wie groß diese ist und wohin sie in den Virtuellen Speicher geladen werden sollte (für ausführbare Dateien in der Regel an den Anfang des Speichers, d.h. die erste Sektion an die Adresse 0, für gelinkte Daten kann dies anders sein). Zudem enthalten sie einen Pointer auf und die Größe der Liniennummerneinträge und der Relokationseinträgen.

struct sectionhdr {
    char           s_name[8];  /* Der Name der Sektion */
    unsigned long  s_paddr;    /* Speicheradresse an die diese Sektion geladen werden sollte*/
    unsigned long  s_vaddr;    /* Die Virtuelle Adresse, an die diese Sektion geladen werden sollte */
    unsigned long  s_size;     /* Die Größe der Sektion (inklusive Header)*/
    unsigned long  s_scnptr;   /* Pointer zu den Daten dieser Sektion */
    unsigned long  s_relptr;   /* Pointer zu dem Relokationseinträgen dieser Sektion */
    unsigned long  s_lnnoptr;  /* Pointer zu dem Liniennummerneinträgen dieser Sektion         */
    unsigned short s_nreloc;   /* Anzahl der Relokationseinträge */
    unsigned short s_nlnno;    /* Anzahl der Liniennummerneinträge */
    unsigned long  s_flags;    /* Verschiedene Flags */
};

Die Datensektion kann unterschiedlich lang sein. Sie enthält die eigentlichen Daten in der Datei. Dies sind in der Regel Anweisungen in Maschinencode, Platz für Variablen und Daten, die für die Ausführung benötig werden.

Ein Relokationseintrag definiert, wo die Symbole in der Datensektion gefunden werden können. Dies wird für jedes Symbol einzeln definiert.

typedef struct reloc{
    unsigned long  r_vaddr;   /* Adresse für die Relokation      */
    unsigned long  r_symndx;  /* Symbol für das die Relokation gilt */
    unsigned short r_type;    /* Type der Relokation*/
};

Ein Liniennummerneintrag definiert, welche Zeile im Quellcode welcher Anweisung im Maschinencode entspricht. Dies ist insbesondere zum Debuggen von Anwendungen wichtig. Jede Sektion hat ihre eigene Tabelle mit Liniennummern. Die Nummern werden dabei für jede Funktion in der Sektion einzeln gezählt.

typedef struct lineno{
    union l_addr{
        unsigned long l_symndx;  /* function name symbol index */
        unsigned long l_paddr;   /* address of line number     */
    };
    unsigned short l_lnno;     /* line number                */
};

Für die Linie, auf der die Funktion beginnt, wird ein Eintrag mit 0 als l_lnno und der Symbol der Funktion als l_symndx erstellt. Für jede Weitere Linie im Quellcode wird ein Eintrag mit der Anzahl an Linien seit dem Funktionsbeginn als l_lnno und der Adresse der ersten Anweisung, die aus dieser Linie erstellt wurde als l_paddr.

Die Symboltabelle enthält Informationen über die in der Datei vorhandenen Symbole. Symbole sind z.B. Funktionen oder Variablen, die von anderen Programmen verwendet werden können. Die Größe und die Position der Symboltabelle wird in dem File Header festgelegt. Die Symboltabelle besteht aus Einträgen der Form

typedef struct sysent{
  union e {
    char e_name[8];             /* Name des Symboles */
    struct e {
      unsigned long e_zeroes;   /* Wenn null ist der Name des Symboles in der Zeichenkettentabelle angelegt*/
      unsigned long e_offset;   /* Index des Symboles in der Zeichnekettentabelle */
    };
  };
  unsigned long e_value;        /* Wert (in der Regel Adresse) des Symboles */
  short e_scnum;                /* Sektion */
  unsigned short e_type;        /* Type */
  unsigned char e_sclass;       /* Speicherklasse */
  unsigned char e_numaux;       /* Anzahl des Zusätzlichen Einträge*/
};

Der Name des Symboles wird in e_name gespeichert, solange er 8 Zeichen lang oder kürzer ist. Ansonsten wird er in der Zeichenkettentabelle abgelegt, dann ist e_zeros 0 und e_offset gibt den Offset dieses Eintrags in der Zeichenkettentabelle an. Der Wert des Symboles wird in e_value gespeichert. Dies ist in der Regel die Adresse, an der dieses Symbol abgelegt ist. Dies hängt vom Typen und der Speicherklasse ab. Die Sektion, in der das Symbol definiert ist, wird in e_sclass abgelegt. e_type definiert den Typen des Symboles. Dies kann entweder ein Elementarer Datentyp (int, float etc.) oder ein zusammengesetzter Type (struct, union) sein. Zudem kann das Symbol einen Wert, einen Pointer, einen Array oder eine Funktion, die diesen Wert zurückgibt, definieren. e_class definiert, wo und wie das Symbol abgelegt ist (z.B. kann es ein Externes Symbol sein, ein Funktionsargument, eine globale oder statische Variable etc.). Abhängig von Typen des Symbols können zusätzliche Einträge folgen. Die Anzahl dieser Einträge ist mit e_numaux angegeben.

Die Zeichenkettentabelle folgt am Schluss der Datei. Sie beginnt mit einem integer, in dem die Länge der Tabelle gespeichert ist. Danach folgen alle Zeichenketten hintereinander. Um einen Zeichenkette zu lesen, muss man den Offset kennen, und kann an dieser Stelle mit dem Lesen beginnen. Die Zeichenketten sind null terminiert.

Weitere Details zu einer COFF Implementierung können unter [7] gefunden werden.

Vorteile

Mit COFF wurde es möglich, Debugging Informationen einfach in die Binärdatei einzubetten. Zudem wurden dynamische Bibliotheken ermöglicht. Dazu werden alle Adressen in den Relokationseinträgen relativ zur eigentlichen Adresse der Sektion in dem Virtuellen Speicher der Anwendung geladen. Dadurch kann die Adresse der Sektion erst zur Übersetzungszeit festgelegt werden.

Einzelnachweise

  1. Common Object File Format Texas Instruments, Aufgerufen am 8. März 2014
  2. Overview over SCO System V Release 3 HP, Aufgerufen am 8. März 2014
  3. XCOFF Object File Format IBM, Aufgerufen am 8. März 2013
  4. Object File / Symbol Table Format Specification Compaq/HP, Aufgerufen am 8. März 2014
  5. Typer of Executable Linux.org, Aufgerufen am 8. März 2014
  6. PE and COFF Specification Microsoft, Aufgerufen am 8. März 2014
  7. DJGPP COFF Spec Delorie Software, Abgerufen am 9. März 2014

Vorlage:Navigationsleiste ausführbare Binär- und Objekt-Formate