ASM86FAQ - Dateiformate
COM-Format (Programm)
Juergen Thelen 2:2450/55.5:
Um es gleich vorwegzunehmen: COMs besitzen KEIN definiertes Dateiformat.
Da es bei Einsteigern aber immer wieder zu Verständnisproblemen kommt, warum denn COMs kein definiertes Dateiformat besitzen, möchte ich das Thema hier kurz anreißen.
Im folgenden lassen wir der Einfachheit halber einmal Gerätetreiber, interne Befehle und Batches soweit wie möglich außer acht, und konzentrieren uns nur auf die Befehle, die DOS als sogenannte externe Befehle klassifiziert: Core Images (COMs) und Executables (EXEs).
Einer der Hauptunterschiede zwischen beiden Typen liegt darin, daß COMs ihre Daten, ihren Stack und ihren Programmcode normalerweise innerhalb eines ein- zigen 64 KByte-Segmentes unterbringen, EXEs dagegen i.d.R. aus mehreren Seg- menten bestehen (man kann zwar mit etwas Trickserei auch COMs mit mehreren Segmenten betreiben und/oder solche nachladen, aber das ist für die Erläu- terungen hier eher irrelevant, und würde nur unnütze Verwirrung stiften...).
Segmente dürfen unter DOS auf (nahezu) jeder beliebigen 0MOD16-Boundary be- ginnen, d.h. an Adressen, die restlos durch 16 teilbar sind. DOS arbeitet dabei mit einer dynamischen Speicherverwaltung, was bedeutet, daß die Seg- mente des Programmes nicht an statische Adressen geladen werden, sondern bei jedem Start auf völlig anderen 0MOD16-Boundaries landen können (je nachdem, wieviele Programme/Treiber/TSRs sich zu diesem Zeitpunkt bereits im Speicher befinden).
Und genau hier haben wir den casus knacktus... ;)
Der ursprüngliche Hintergedanke von Core Images war, daß ein Programm direkt von einem Datenträger ins RAM geladen und gestartet werden kann, ohne daß zuvor irgendwelche Adressangleichungen (Relokationen) durchgeführt werden müssen.
Da die Segmentadresse des COM-Segmentes aufgrund der dynamischen Speicher- verwaltung aber variieren kann, muß man sich bei der Erstellung von COMs ge- wissen Bedingungen unterwerfen: alle Adressbezüge müßen zur Assemblat- bzw. Linkzeit des COM-Programmes bereits bekannt sein, d.h. sie müssen entweder intrasegmentärer (Offsets innerhalb des COM-Segments), oder aber statischer bzw. konstanter Natur (z.B. 0040H (BIOS-Datensegment), usw.) sein.
Alle anderen (intersegmentären FAR) Bezüge könnte COMMAND.COM nur mit Hilfe entsprechender Relokationstabellen angleichen (so wird es bei EXEs gemacht), und die sind bei reinen Speicherabbildungen, wie es Core Images sind, nunmal einfach nicht in der Datei vorhanden. Da COMs also nur aus reinem Binärcode bestehen, können sie folglich auch kein definiertes Dateiformat besitzen, da ja schließlich jedes Programm anders aussieht.
Ob man unzulässige Bezüge verwendet hat, merkt man übrigens spätestens dann, wenn einem der Core Image Erzeuger (z.B. EXE2BIN von DOS), bzw. die in einem Linker integrierten Core Image Routinen (z.B. Borlands TLINK) Meldungen der Art "Cannot create COM file" um die Ohren haut... >)
Bei den Arbeitsgeschwindigkeiten der heute üblichen CPUs bzw. Speichermedien ist der ehemals einmal große Geschwindigkeitsvorteil beim Laden und Starten von COMs gegenüber EXEs natürlich erheblich geschrumpft. Trotzdem spricht bei kleineren Projekten die kompaktere (overheadlose) Form auch heute noch eindeutig für COMs. Gleiches gilt für einige Spezialbereiche (z.B. TSRs)...
Togal... &)
Abschließend noch ein Satz zu der für viele Einsteiger ominösen ORG 100H An- weisung (in COM-Quelltexten): DOS erzeugt vor dem Start von COMs einen soge- nannten Program Segment Prefix (PSP, siehe Topic STR0002), der dem eigent- lichen Programm automatisch vorangestellt wird. Gleiches gilt übrigens auch für EXEs, aber das nur nebenbei. Ein PSP ist exakt 256 = 100H Bytes lang...
Na, klingelts? ;)
Genau. Ein ORG 100H bewirkt also einfach nur, daß der Assembler das COM-Pro- gramm mit Offset 100H beginnend übersetzt. Somit ist sozusagen genug Platz für den PSP und DOS kann das COM unmittelbar hinter dem PSP plazieren, ohne das die Adressen durcheinander geraten oder angepasst werden müßten.
EXE-Format [MZ] (Programm)
Juergen Thelen 2:2450/55.5:
Nachstehend das Old-Executable Format (.EXE vom Typ 'MZ'). Auf die Dokumen- tation der Bereiche Linker/Compressor Data und Debug/Overlay Data wurde be- wußt verzichtet, da diese Bereiche bei der alltäglichen Programmierung sehr selten von Belang sind. Außerdem wäre die Doku ziemlich umfangreich...;)
{ Dateiformat: EXE [MZ]
Offset Typ Bezeichnung
--- Header ---------------------------------------------------------------
0000H 2 B Identifier 0002H W RestBytes 0004H W TotalPages 0006H W NumOfReloItems 0008H W HeaderParas 000aH W MinAddParas 000cH W MaxAddParas 000eH W InitialSS 0010H W InitialSP 0012H W Checksum 0014H W InitialIP 0016H W InitialCS 0018H W ReloTblOfs 001aH W OvlNum
--- Linker/Compressor Data -----------------------------------------------
001cH ? ? : : (ReloTblOfs - 1) ? ?
--- Relocation Table -----------------------------------------------------
ReloTblOfs[0*4] W ReloItemOfs ReloTblOfs[(0*4)+2] W ReloItemSeg : : ReloTblOfs[(ReloEntries-1)*4] W ReloItemOfs ReloTblOfs[((ReloEntries-1)*4)+2] W ReloItemSeg
--- Pad ------------------------------------------------------------------
ReloTblOfs[ReloEntries*4] ? ? : (HeaderParas * 16) - 1 ? ?
--- Binary Code ----------------------------------------------------------
HeaderParas*16 ? ? :
--- [Debugger/Overlay Data] ----------------------------------------------
[: ? ?]
}
Identifier, Offset 0000H, 2 Bytes:
Old-Executable Kennung ('MZ' = 4dH, 5aH oder 'ZM' = 5aH, 4dH).
RestBytes, Offset 0002H, Word:
Anzahl relevante Bytes in der letzten Load Image Page bzw. ohne Funktion, wenn die Größe des Load Images ein Vielfaches von 512 beträgt:
RestBytes Load Image Size
= 0000H TotalPages * 512 Bytes # 0000H ((TotalPages - 1) * 512) + RestBytes
HINWEIS: Die vorstehenden Formeln gelten nur zur Größenberechnung des Load Images (Load Image = Header (inklusive Linker/Compressor Data und Relocation Table) plus Binary Code).
Debugger/Overlay Daten, die noch unmittelbar auf den Binary Code folgen könnten, werden von den Formeln NICHT berücksichtigt.
HINWEIS: Wurde die Executable mit einem alten MS-Linker (vor 1.1) gelinkt, gilt ein RestBytes = 0004H als RestBytes = 0000H... &)
TotalPages, Offset 0004H, Word:
Anzahl Sektoren bzw. Pages zu 512 = 200H Bytes, die gelesen werden müssen, um das gesamte Load Image ins RAM zu laden. Ob dabei die Bytes des letzten gelesenen Sektors (bzw. der letzten Page) vollständig, oder nur teilweise zur Datei gehören, wird durch RestBytes (s. dort) bestimmt. Beispiele:
Load Image Size RestBytes TotalPages Formel
1024 0 2 2 * 512 = 1024 6000 368 12 ((12 - 1) * 512) + 368 = 6000
HINWEIS: Einige Dokumentationen bezeichnen TotalPages fälschlicherweise generell als Entsprechung "Dateilänge DIV 512". Dies ist aber nur bei RestBytes = 0 der Fall, und selbst dann auch nur, wenn hinter dem Binary Code keine Debugger/Overlay Daten folgen... ;)
NumOfReloItems, Offset 0006H, Word:
Anzahl der vor der Ausführung dieser Datei zu relozierenden Segmentbezüge im Binary Code. Die Zeiger auf die betroffenen Segment-Words befinden sich im Relocation Table (ab Offset ReloTblOfs relativ zum Dateibeginn).
HeaderParas, Offset 0008H, Word:
Größe des Headers in Paragraphen (Paragraph = Para = 16 Byte). Der Header schließt normalerweise die Bereiche Linker/Compressor Data und Relocation Table mit ein.
Der reine (noch zu relozierende) Binary Code der Datei folgt unmittelbar auf den Header, also bei Datei-Offset (HeaderParas * 16).
MinAddParas bzw. MaxAddParas, Offset 000aH bzw. 000cH, je ein Word:
Anzahl RAM-Paragraphen, die das OS mindestens bzw. maximal noch zusätzlich (zur Größe des Load Images) reservieren muß, damit das Programm lauffähig ist.
Unter DOS spielt MinAddParas nur eine untergeordnete Rolle, die Vorgehens- weise wird primär durch MaxAddParas bestimmt:
Ist MaxAddParas ungleich 0, wird versucht, exakt MaxAddParas Paragraphen RAM zu reservieren. Bei Erfolg ist alles in Butter ;) und MinAddParas wird ignoriert. Bei Mißerfolg dagegen muß der größte noch freie RAM-Block min- destens MinAddParas Paragraphen groß sein, anderenfalls wird abgebrochen.
Ist MaxAddParas gleich 0, plaziert DOS das Programm nicht mehr wie üblich im ersten (von der Größe her passenden und freien) RAM-Block, sondern ver- sucht das Programm so hoch im Arbeitsspeicher zu deponieren, wie es geht.
MinAddParas wird in diesem Fall zwar ignoriert, sollte aber vom Compiler bzw. Assembler aus auch tatsächlich Null sein, sonst landen (speziell bei Hochsprachen) der Stack bzw. Variablenbereiche i.d.R. irgendwo im Grafik- segment... ;)
InitialSS, Offset 000eH, Word:
Unrelozierter, initialer Wert für das Stack Segment.
HINWEIS: InitialSS versteht sich relativ zum Programmbeginn im RAM, d.h. relativ zum ersten Byte hinter dem PSP (Program Segment Prefix).
InitialSP, Offset 0010H, Word:
Initialer Wert für den Stack Pointer (relativ zu SS).
Checksum, Offset 0012H, Word:
Eigentlich als Prüfsumme gedacht (NOT (Summe aller Words im Binary Code)), wird aber kaum genutzt und ist daher fast immer Null.
InitialIP, Offset 0014H, Word:
Initialer Wert für den Instruction Pointer (relativ zu CS).
InitialCS, Offset 0016H, Word:
Unrelozierter, initialer Wert für das Code Segment.
HINWEIS: InitialCS versteht sich relativ zum Programmbeginn im RAM, d.h. relativ zum ersten Byte hinter dem PSP (Program Segment Prefix).
ReloTblOfs, Offset 0018H, Word:
Offset des Relocation Table (relativ zum Dateibeginn).
Der Relocation Table beinhaltet NumOfReloItems segmentierte 16:16 Zeiger vom Typ ReloItemSeg:ReloItemOfs (mehr dazu weiter unten).
OvlNum, Offset 001aH, Word:
Normalerweise immer 0 (Hauptprogramm), ansonsten Nummer des Overlays.
RelItemOfs und RelItemSeg, 16:16 Zeiger:
Diese 16:16 Zeiger aus dem Relocation Table zeigen auf vor der Ausführung des Programmes noch zu relozierende Segmentbezüge. Die Zeiger verstehen sich relativ zum Beginn des Binary Codes und adressieren jeweils ein Word, welches wiederum Bestandteil irgendeines Assembler-Befehles ist.
Für die, denen der Begriff Relokation nichts sagt:
EXE-Programme können aus (nahezu) beliebig vielen Segmenten bestehen. Der Programmierer bestimmt bei der Entwicklung von EXE-Programmen allerdings meist nur die Reihenfolge der Segmente, nicht aber deren spätere physika- lische Lage zur Laufzeit.
Sieht man einmal von statischen Segmenten, wie z.B. dem Video-RAM Segment (b800H), oder dem BIOS-Datensegment (0040H), ab, kann er das meistens auch gar nicht, da DOS mit einer dynamischen Speicherverwaltung arbeitet. Diese dynamische Speicherverwaltung hat zur Folge, daß der Programmierer vorher normalerweise nicht wissen kann, an welchen Adressen die Segmente seines EXE-Programmes zur Laufzeit einmal landen werden.
Das einzige was feststeht ist, daß die Segmente auf 0MOD16-Boundaries lan- den, aber welche dies sein werden, hängt davon ab, wieviele Treiber, TSRs und MCBs sich bereits im RAM befinden, wenn COMMAND.COM das EXE-Programm für die Ausführung lädt.
Daher bedienen sich Assembler und Linker von EXE-Programmen eines einfach- en Prinzips: alle Segmentbezüge des Programmes werden als relative Angaben zu einem fiktiven Segment 0000H verarbeitet. Das bedeutet, das erste Seg- ment eines Programmes beginnt für den Assembler/Linker mit Segment 0000H, das zweite z.B. bei 003bH, das dritte z.B. bei 0122H, usw., halt je nach Länge und Ausrichtung der einzelnen Segmente im Quelltext...
Angenommen, COMMAND.COM wollte nun ein EXE-Programm mit den beispielhaften drei Segmenten ins RAM laden, und die nächste freie 0MOD16-Boundary wäre zu diesem Zeitpunkt 1000H, bräuchte COMMAND.COM nur jeweils 1000H zu den einzelnen Segmentbezügen zu addieren (= 1000H, 103bH, 1122H), um das Pro- gramm an die vorliegende Situation anzupassen. Und genau diese Adressan- passung nennt man Relokation.
Intrasegmentäre Adressierungen müssen sich dieser Prozedur natürlich nicht unterziehen, denn Offsets wirken bekanntlich nur relativ zu einem Segment- beginn. Die Lage des Segmentes spielt für den Offset keine Rolle (jeden- falls solange der Kontext erhalten bleibt), denn ein Offset 1234H ist bei 0000:1234H genauso 1234H Bytes weg vom Segmentbeginn, wie bei 1000:1234H.
Damit COMMAND.COM das Programm nicht extra analysieren muß, um die Relo- kation durchführen zu können, merkt sich der Linker beim Linken der ent- sprechenden OBJ-Datei(en) alle zu relozierenden Segmentbezüge in Form von 16:16 Zeigern in einer Relokationstabelle, die dann neben diversen anderen Dingen zusammen mit dem Binary Code als EXE-Datei gespeichert wird.
EXE-Format [NE] (Programm)
Topic noch zu vergeben.
EXE-Format [LE] (Programm)
Topic noch zu vergeben.
PCX-Format (Grafik)
Topic noch zu vergeben.
MOD-Format (Musik)
Martin Alexander Michels 2:2446/311.1: Topic in Arbeit.