4. DASM32 - Der Assembler

DASM32 ist ein 32Bit-Makroassembler, der aus Assemblerquelltexten Objektmodule zur Weitergabe an den Linker erzeugt. Er besitzt folgende Eigenschaften :

In diesem Kapitel wird die Arbeitsweise und Syntax des Assemblers erläutert, um mit ihm Programme für die x86-Plattform zu erstellen, die unter dem DiceRTE-Laufzeitsystem ablaufen werden. Die Aufgabe dieses Kapitels ist es nicht, Ihnen die Assemblersprache beizubringen, denn dazu gibt es sehr gute Literatur im Internet und im Buchhandel. Die Beschreibungen in diesem Kapitel gehen vielmehr davon aus, daß Sie die x86-Assemblersprache (möglichst im Protected Mode) bereits beherrschen. Sollten Sie bereits mit TASM und/oder MASM vertraut sein, ist dies sehr hilfreich, da DASM32 weitgehend kompatibel zu diesen Assemblern ist.

Zur Steuerung der Arbeitsweise erwartet DASM32 bestimmte Optionen, die nachfolgend beschrieben werden.

4.1 Optionen

Der Aufruf von DASM32 geschieht auf folgende Weise :

Dabei gilt : 4.2 Ein Beispielprogramm

Um Ihnen die Arbeitsweise von DASM32 zu verdeutlichen, wollen wir ein kleines Beispielprogramm betrachten:

.586p
.model flat
extrn dosopen:near
extrn dosclose:near
extrn xmalloc:near
extrn memfree:near
extrn dosputstr:near
extrn crlf:near
extrn _decstrl:near
extrn _cpu:dword

public start

.code
start:
    	mov eax,offset meldung
	call near ptr dosputstr
	push dword ptr _cpu
	call _decstrl
	add esp,4
	call dosputstr
	call crlf
	mov eax,100000h
	call xmalloc
	mov memzeiger,eax
	mov edi,eax
	add edi,100000h
label1:
	mov byte ptr [eax],0
	inc eax
	cmp eax,edi
	jb short label1
	mov byte ptr -100[edi],10
	mov eax,memzeiger
	call memfree
	retf

.data
meldung db 'Installierter CPU-Typ : ',0

.data?
memzeiger dd ?

end start
Prozessor-Direktiven :

Die erste Anweisung im Programm ist die Prozessor-Direktive .586p. Diese teilt dem Assembler mit, daß alle Prozessor-Befehle bis einschließlich Pentium zur Verfügung stehen sollen. Diese Direktiven dienen nur der Kompatibilität zu anderen Assemblern, denn DASM32 ignoriert grundsätzlich jede Prozessor-Direktive, d.h. es stehen zu jeder Zeit alle Prozessor-Befehle zur Verfügung. Es liegt in der Verantwortung des Programmierers dafür zu sorgen, daß Programme, die z.B. Pentium Pro- oder MMX-Befehle nutzen, auch nur unter den entsprechenden CPUs ausgeführt werden dürfen (das gleiche gilt für Coprozessor-Befehle), weshalb eine solche Prüfung durch den Assembler irrelevant ist.

Speichermodell :

Die Anweisung .model flat in der zweiten Zeile teilt dem Assembler mit, daß das Programm im Flat-Speichermodell ablaufen wird. Da DASM32 ausschließlich Code für dieses Speichermodell erzeugt, wird auch die .model-Direktive ignoriert (sie dient ebenfalls nur für Kompatibilitätszwecke). DASM32 impliziert daher auch folgende Anweisungen :

_TEXT steht für das 32Bit-Codesegment, _DATA für das initialisierte 32Bit-Datensegment und _BSS für das uninitialisierte 32Bit-Datensegment. Sollten im Quelltext andere ASSUME- und GROUP-Direktiven auftreten, so werden diese ignoriert.

Externe Symbole :

Die Zeilen 4 bis 11 deklarieren die erwähnten Symbole mit der EXTRN-Direktive als externe Referenzen, d.h. diese Symbole wurden in anderen Modulen definiert und dort mit der PUBLIC-Direktive veröffentlicht.

Veröffentlichte Symbole :

In Zeile 13 wird das Symbol "start" durch die PUBLIC-Direktive anderen Modulen zur Verfügung gestellt, die ihrerseits über eine EXTRN-Direktive auf dieses Symbol zugreifen können.

Codesegment :

Die Anweisung .code in Zeile 15 ist das Kennzeichen für den Assembler, daß der folgende Programmtext für das Codesegment bestimmt ist. Diese Anweisung ist eine Alternative für diese Anweisung :

In der ersten Zeile des Codesegments wird das Symbol "start" definiert. Generell können Symbole auf fünf Arten definiert werden :

1. Label: [ Befehl ]

Ein Label ist ein Symbol, das eine Position im Programmcode markiert und z.B. als Ziel für einen (un-)bedingten Sprung dienen kann. Das Label bekommt die aktuelle Offsetadresse im jeweiligen Segment und den Typ NEAR zugewiesen. Hinter dem Doppelpunkt darf ein Prozessorbefehl stehen :
label1:	mov byte ptr [eax],0
	jmp label1
Ein Label wird prinzipiell wie eine Konstante behandelt. Die Anweisung
mov eax,label1
kopiert daher die konstante Adresse von label1 als 32Bit-Direktwert in EAX.

ACHTUNG : Durch das Flat-Speichermodell sind alle Label grundsätzlich 32Bit-Direktwerte. Die Anweisung

mov ax,label1
ist daher nicht erlaubt (Typkonflikt).
2. Label PROC [ NEAR ] Diese Syntax ist nur im Codesegment erlaubt, da sie das Label als Beginn einer Prozedur definiert. DASM32 behandelt diese Definition wie Label: mit der Einschränkung, daß dahinter kein Prozessorbefehl folgen darf. Das Ende der so definierten Prozedur muß so angezeigt werden :

Label ENDP

Der NEAR-Zusatz ist optional und nur für Kompatibilität zu anderen Assemblern erforderlich. Als Standardeinstellung wird automatisch NEAR benutzt (im Flat-Speichermodell gibt es ohnehin keinen far-Typ).
3. Variable DB|DW|DD|DQ|DT Werte Eine Variable versieht Programmdaten an einer bestimmten Adresse mit einem symbolischen Namen. Die Variable bekommt die aktuelle Offsetadresse im jeweiligen Segment und den Typ zugewiesen, der durch die nachfolgende Datendefinition impliziert wird (DB=byte, DW=word, DD=dword, DQ=qword, DT=tbyte). 4. Variable LABEL Typ Die Variable bekommt die aktuelle Offsetadresse im jeweiligen Segment und den angegebenen Typ zugewiesen. Der Unterschied zu Variante 3 besteht darin, daß hierdurch keine eigentlichen Programmdaten erzeugt werden. Hier ein paar Beispiele :
dwordvar label dword
bytevar1 db 0,0,0,0 ;ansprechbar als bytevar oder dwordvar

tbytevar label tbyte
bytevar2 db 0,0,0,0,0,0,0,0,0,0 ;ansprechbar als bytevar oder tbytevar
Es können folgende Typen benutzt werden : BYTE, WORD, DWORD, FWORD, QWORD, TBYTE.
5. Symbol = Ausdruck Durch diese Definition bekommt Symbol das Ergebnis der Auswertung von Ausdruck zugewiesen. Durch diese Definition werden ebenfalls wie in Variante 4 keine Programmdaten erzeugt, denn Symbol steht lediglich für einen numerischen Wert zur Assemblierzeit. Das Symbol kann somit in allen Ausdrücken, in denen numerische Werte erlaubt sind, benutzt werden. Im Gegensatz zu den vorherigen vier Arten können Symbole, die auf diese Weise definiert wurden, jederzeit verändert (redefiniert) werden.

Hier ein Beispiel :

WERT = 100*2+3 ;WERT=203
mov eax,WERT*2 ;EAX = 406
WERT = 400+1 ;WERT=401 (Redefinition von WERT)
mov edx,WERT/2 ;EDX=200

Symbole, die auf diese Weise definiert wurden, können nicht mit der PUBLIC-Direktive veröffentlicht werden, da sie keinen eigentlichen Speicher im Programm belegen. Sie sind prinzipiell nur "Platzhalter" für Ausdrücke.
In den Zeilen 16 bis 41 stehen die zu assemblierenden Befehle für das Codesegment :

mov eax,offset meldung
Das Register EAX bekommt die Adresse der Variablen meldung als Direktwert zugewiesen.

call near ptr dosputstr
Die Prozedur dosputstr wird angesprungen. Durch die near ptr-Angabe wird dosputstr in ein Near-Label konvertiert, was in diesem Fall aber überflüssig ist, da dies bereits vorher durch die EXTRN-Direktive festgelegt wurde. Die near ptr-Angabe ist bei DASM32 generell überflüssig, da es im Flat-Modell keine Far-Label bzw. Far-Prozeduren gibt.

push dword ptr _cpu
Der Inhalt der Variablen _cpu wird als DWORD auf den Stack geschoben. Da _cpu in einer vorherigen EXTRN-Direktive bereits als DWORD-Typ deklariert wurde, kann die dword ptr-Angabe in diesem Fall auch weggelassen werden.

call _decstrl
Die Near-Prozedur _decstrl wird angesprungen, da _decstrl durch eine vorherige EXTRN-Direktive als Near-Label deklariert wurde.

add esp,4
Der Inhalt des Registers ESP wird um 4 erhöht.

call @dosputstr
Die Near-Prozedur dosputstr wird angesprungen, da dosputstr durch eine vorherige EXTRN-Direktive als Near-Label deklariert wurde.

call @crlf
Die Near-Prozedur crlf wird angesprungen, da crlf durch eine vorherige EXTRN-Direktive als Near-Label deklariert wurde.

mov eax,100000h
Das Register EAX bekommt den Wert 100000h als hexadezimalen Direktwert zugewiesen.

call xmalloc
Die Near-Prozedur xmalloc wird angesprungen, da xmalloc durch eine vorherige EXTRN-Direktive als Near-Label deklariert wurde.

mov memzeiger,eax
Der Inhalt des Registers EAX wird in die DWORD-Variable memzeiger kopiert. Diese Variable wird weiter unten im _BSS-Segment als DWORD definiert.

mov edi,eax
Der Inhalt des Registers EAX wird in EDI kopiert.

add edi,100000h
Der Inhalt des Registers EDI wird um den hexadezimalen Direktwert 100000h erhöht.

label1: mov byte ptr [eax],0
Das Label label1 wird definiert und markiert von nun an die nachfolgende Anweisung als mögliches Sprungziel. Die Anweisung kopiert den Direktwert 0 in das Byte im Speicher, dessen Adresse im Register EAX vermerkt ist.

inc eax
Der Inhalt des Registers EAX wird um 1 erhöht.

cmp eax,edi
Der Inhalt der Register EAX und EDI wird verglichen.

jb short label1
Falls EAX < EDI (jump-if-below) wird zu label1 gesprungen. Durch den short-Zusatz wird ein 8Bit-Offset erzwungen, d.h. das Sprungziel muß zwischen -128 und +127 Bytes von dieser Anweisung entfernt sein.

mov byte ptr -100[edi],10
Das Speicherbyte, dessen Adresse sich aus dem Inhalt des Registers EDI - 100 ergibt, bekommt den Dezimalwert 10 zugewiesen.

mov eax,memzeiger
Der Inhalt der DWORD-Variable memzeiger wird in das Register EAX kopiert.

call memfree
Die Near-Prozedur memfree wird angesprungen, da memfree durch eine vorherige EXTRN-Direktive als Near-Label deklariert wurde.

retf
Es wird ein Far-Return ausgeführt, wodurch CS und EIP vom Stack zurückgeholt werden.

Datensegment (initialisiert) :

Die Anweisung .data in Zeile 43 ist das Kennzeichen für den Assembler, daß der folgende Programmtext für das initialisierte Datensegment bestimmt ist. Diese Anweisung ist eine Alternative für die Anweisung

In der Zeile 44 steht die einzige Definition für das _DATA-Segment :

meldung db 'Installierter CPU-Typ : ',0
Hier wird die Byte-Variable meldung definiert, die den nullterminierten String "Installierter CPU-Typ : " adressiert.

Datensegment (uninitialisiert) :

Die Anweisung .data? in Zeile 46 ist das Kennzeichen für den Assembler, daß der folgende Programmtext für das uninitialisierte Datensegment bestimmt ist. Diese Anweisung ist eine Alternative für :

In der Zeile 47 steht die einzige Definition für das _BSS-Segment :

memzeiger dd ?
Hier wird die Dword-Variable memzeiger mit unbestimmtem Inhalt definiert.

Quelltextende

Die END-Direktive in Zeile 49 zeigt dem Assembler das Ende des Quelltextes an. Alle nachfolgenden Zeilen im Quelltext werden ignoriert. Die Angabe des Labels "start" hinter der END-Direktive definiert dieses Label als Einstiegspunkt für die Programmausführung.

4.3 Ausdrücke und Symbole

Ausdrücke und Symbole sind grundlegende Elemente eines Assembler-Programms. Ausdrücke werden zur Berechnung von Werten und Speicheradressen benutzt, während Symbole verschiedenartige Werte repräsentieren können.

4.3.1 Konstanten

Konstanten sind Zahlen oder Strings, die als konstante Werte interpretiert werden.



4.3.2 Symbole

Ein Symbol steht für einen Wert, der eine Variable, ein Label oder ein Operand zu einem Befehl sein kann. Symbolnamen dürfen aus Buchstaben (groß und klein), Ziffern und den Sonderzeichen '_', '@', '?' und '$' bestehen, aber nicht mit einer Ziffer beginnen und nicht länger als 127 Zeichen sein (jeder längere Bezeichner wird auf diese Länge gekürzt). Die Unterscheidung zwischen Groß- und Kleinschreibung kann durch die Option -ml beeinflußt werden (standardmäßig wird nicht zwischen Groß- und Kleinschreibung bei Symbolnamen unterschieden).

Jedem Symbol ist ein Typ zugeordnet, der Auskunft über die Eigenschaften und gespeicherten Informationen gibt. Der Typ wird durch die Art der Definition eines Symbols festgelegt.

Folgende Typen sind erlaubt :

Typ Bedeutung
Num Symbol steht für einen numerischen Wert 
Makro Symbol steht für ein definiertes Makro 
Label Symbol steht für eine Adresse im Programmtext 
Byte Symbol adressiert ein Byte 
Word Symbol adressiert ein Word (2 Bytes) 
Dword Symbol adressiert ein Dword (4 Bytes) 
Fword Symbol adressiert ein Fword (6 Bytes) 
Qword  Symbol adressiert ein Qword (8 Bytes) 
Tbyte Symbol adressiert ein Tbyte (10 Bytes) 
Xmmword Symbol adressiert ein XmmWord (16 Bytes) 

Es gibt zwei vordefinierte Symbole : "$" und "?". Das "$"-Symbol repräsentiert den aktuellen Programmzähler im aktuellen Segment des Programmoduls, während das "?"-Symbol ein Synonym für den Wert 0 (Null) darstellt.

4.3.3 Ausdrücke

Ausdrücke werden vom Assembler zum Zeitpunkt der Assemblierung ausgewertet und können die Lesbarkeit und Modularisierung des Codes erhöhen, indem z.B. bestimmte Werte durch Symbole dargestellt werden. Ausdrücke werden grundsätzlich auf 32Bit-Integer-Basis ausgewertet. In jedem Ausdruck können Konstanten benutzt werden :

Ausdrücke können sich auch aus Symbolen und Konstanten zusammensetzen :
.data
  tabelle db 1024

.code
  MASKE = 511
  test byte ptr tabelle[eax+6],MASKE AND 127
Beide Operanden des obigen test-Befehls sind Ausdrücke. Der erste Operand beschreibt eine Adresse im Speicher und hat den Wert tabelle+eax+6, während der zweite einen Direktwert darstellt (MASKE AND 127 = 127). In Ausdrücken können folgende Operatoren verwendet werden :

Operator Bedeutung
OFFSET <Symbol> Adresse einer Speichervariable bestimmen 
( <Ausdruck> ) Klammerung von Ausdrücken
+ <Ausdruck> Ausdruck ist positiv
- <Ausdruck> Negation des Ausdrucks
<Ausdruck1> * <Ausdruck2> Multiplikation
<Ausdruck1> / <Ausdruck2> Division
<Ausdruck1> MOD <Ausdruck2> Divisionsrest
<Ausdruck1> SHL <Ausdruck2> Links-Shift
<Ausdruck1> SHR <Ausdruck2> Rechts-Shift
<Ausdruck1> + <Ausdruck2> Addition
<Ausdruck1> - <Ausdruck2> Subtraktion
NOT <Ausdruck1> bitweises NOT
<Ausdruck1> AND <Ausdruck2> bitweise UND-Verknüpfung
<Ausdruck1> OR <Ausdruck2> bitweise ODER-Verknüpfung
<Ausdruck1> XOR <Ausdruck2> bitweise EXKLUSIV-ODER-Verknüpfung

Bei Speicheroperanden können spezielle Operatoren eingesetzt werden, die eine Typkonvertierung von Ausdrücken durchführen oder die Adressierung von Speicherobjekten ermöglichen :

Operator Bedeutung
<Typ> PTR <Ausdruck>  Der Ausdruck wird in den angegebenen Typ konvertiert 
[ <Ausdruck> ] Der Ausdruck wird als Speicheroperand betrachtet 

Hier ein paar Beispiele dazu :

In den Beispielen wurde gezeigt, daß die Benutzung von [ ] in Ausdrücken mit Speicheroperanden eine implizite Addition zur Folge hat. Der [ ]-Operator ist überflüssig bei Ausdrücken, die bereits als Speicheroperanden interpretiert werden aber unerläßlich bei der Benutzung von Registern als Bestandteile von Speicheroperanden. Der [ ]-Operator ist ebenfalls überflüssig bei der Konvertierung von Ausdrücken, die bereits den gewünschten Typ besitzen. Hierzu einige Beispiele :
.data
  _dwordvar dd 12345678h

.code
  mov eax,[_dwordvar]           ;[] überflüssig, da bereits Speichervariable
  mov eax,dword ptr _dwordvar   ;Dword-Konvertierung ist überflüssig
  mov eax,dword ptr [_dwordvar] ;Dword-Konvertierung und [] sind überflüssig
  mov eax,_dwordvar             ;hat gleiche Wirkung wie alle drei Zeilen zuvor
  mov ax,word ptr _dwordvar+2 	;AX = 1234h
  mov al,byte ptr _dwordvar     ;AL = 78h
  mov ebx,offset _dwordvar+1
  mov ax,[ebx]                  ;AX=3456h

Die folgende Tabelle zeigt die Reihenfolge bei der Operator-Auswertung. Die Priorität der Operatoren nimmt dabei mit jeder Zeile ab.

Priorität  Operatoren 
8
PTR
7
OFFSET
6
( ), [ ]
5
*, /, MOD, SHL, SHR
4
+, -
3
NOT
2
AND
1
OR, XOR
4.4 Segmentierung

DASM32 ist ein reiner 32Bit-Assembler, was bedeutet, daß er ausschließlich 32Bit-Code erzeugt, der mit 32Bit-Linkern weiterverarbeitet werden kann. Grundlage dessen ist das Flat-Speichermodell, bei dem alle Segmente eine Größe von 4G besitzen. Da hierdurch praktisch keine Segmentierung mehr notwendig ist (denn alle Adressen können mit einem "Near-Pointer" erreicht werden) werden für Programme auch nur zwei Segmente benötigt : EIN Code- und EIN Datensegment. Das Codesegment beinhaltet alle ausführbaren Anweisungen des Programms und das Datensegment alle Daten, auf die das Programm zugreift. Diesem Prinzip trägt auch DASM32 Rechnung, indem es alle Anweisungen und alle Daten in eines dieser beiden Segmente schreibt. Folgende Segmentbezeichnungen werden dazu eingesetzt :

Segmentname  Bedeutung
_TEXT Codesegment
_DATA Datensegment mit initialisierten Daten 
_BSS Datensegment mit uninitialisierten Daten 
DGROUP gesamtes Datensegment bestehend aus _DATA und _BSS 

Auffällig ist, daß das Datensegment (DGROUP) aus zwei Bestandteilen zusammengesetzt wird : _DATA und _BSS. Das _DATA-Segment nimmt alle Daten auf, die einen festen bzw. vordefinierten Wert haben müssen, z.B. Tabellen mit definiertem Inhalt, Zeichenketten etc. Alle Daten hingegen, die im _BSS-Segment abgelegt werden, benötigen keine explizite Initialisierung. Hier ein Beispiel dazu :

Die Variable filename muß im _DATA-Segment stehen, da sie einen konstanten Text adressiert (in diesem Fall ein Dateiname). Die Variable handle dagegen kann im _BSS-Segment stehen, da sie keinen vordefinierten Inhalt besitzen muß, denn sie wird vom Programm selbst gefüllt. Alle Daten im _BSS besitzen in Wirklichkeit keinen undefinierten Inhalt, sondern das komplette _BSS-Segment wird beim Programmstart mit Null initialisiert, d.h. die Variable handle besitzt beim Programmstart automatisch den Inhalt 0 (als Dword).

Die Benennung der Segmente mit _TEXT, _DATA, _BSS und DGROUP ist kompatibel mit TASM und MASM, d.h. Programme sind in Bezug auf die Segmentierung leicht auf einen anderen Assembler zu übertragen.

4.4.1 _TEXT-Segment

Das _TEXT-Segment dient zur Aufnahme aller Anweisungen eines Programms. Dem Assembler kann die Verwendung des _TEXT-Segments auf zwei Weisen angezeigt werden :

oder Die allgemeine Syntax für die Markierung eines Segmentstarts ist diese : Als Segmentname sind _TEXT, _DATA und _BSS erlaubt. Die Ausrichtung teilt dem Linker mit, auf welche Adressgrenze das Segment dieses Moduls ausgerichtet werden soll. Es sind folgende Angaben erlaubt :

Ausrichtung  Ausrichtung auf...
byte Bytegrenze (=> keine Ausrichtung)
word Wortgrenze (gerade Adresse) 
dword Doppelwortgrenze (ganzzahlig durch 4 teilbare Adresse) 
para Paragraphengrenze (ganzzahlig durch 16 teilbare Adresse) 
page Seitengrenze (ganzzahlig durch 4096 teilbare Adresse) 

Wird die Ausrichtung weggelassen, wird automatisch DWORD benutzt.

Die Kombination gibt dem Linker an, wie er einzelne Segmente dieses Namens kombinieren soll. Zur Zeit wird nur die Angabe PUBLIC unterstützt. Die Größe zeigt die Segmentgröße an. Im Flat-Speichermodell ist dies immer USE32 (= 32Bit-Segment). Die Klasse zeigt dem Linker an, welche Segmente er aus einzelnen Modulen im Speicher gruppieren muß. Auf diese Weise lassen sich das Code-, das initialisierte und das uninitialisierte Datensegment zu einem lauffähigen Programm zusammenfassen. Diese Angaben sind erlaubt :

'CODE' : Klasse für das Codesegment (_TEXT)
'DATA' : Klasse für das initialisierte Datensegment (_DATA)
'BSS' : Klasse für das uninitialisierte Datensegment (_BSS)

Innerhalb eines Quelltextmoduls können die verschiedenen Segmente aus beliebig vielen Teilen bestehen, sie werden vom Assembler automatisch aneinander gehängt. Das Attribut Ausrichtung eines Segments muß nur bei der ersten Verwendung eines Segments in einem Quellmodul angegeben werden, alle späteren Verwendungen dieses Segments benutzen dann dieselbe Ausrichtung. Die Assemblierung von _TEXT-Anweisungen geschieht solange, bis die ENDS-Direktive auftritt, ein anderes Segment begonnen wird oder der Quelltext endet. Die ENDS-Direktive besitzt diese Syntax :

Wird der Segmentname weggelassen, wird das jeweils zuletzt begonnene Segment beendet.

4.4.2 _DATA-Segment

Das _DATA-Segment dient zur Aufnahme aller initialisierten Daten eines Programms. Die Assemblierung von Prozessorbefehlen ins _DATA-Segment ist nicht möglich. Dem Assembler kann die Verwendung des _DATA-Segment wieder auf zwei Arten angezeigt werden :

oder Das Segment endet ebenfalls entweder beim Antreffen der ENDS-Direktive, dem Start eines anderen Segments oder dem Ende des Quelltextes.

4.4.3 _BSS-Segment

Das _BSS-Segment dient zur Aufnahme aller Daten, die keinen vordefinierten Inhalt besitzen müssen (oder mit Null initialisiert sein sollen). Auch hier können keine Anweisungen ins Segment assembliert werden.

oder Das Segment endet auf die gleiche Weise wie das _DATA-Segment.

4.4.4 Stacksegment

Das Anlegen eines Stacks für DiceRTE-Programme ist nicht Aufgabe des Assemblers, sondern des Linkers. Dies hängt mit der Verwendung des Portable-Executable-Formats für ausführbare Programme zusammen.

4.4.5 ASSUME-Direktive

Die ASSUME-Direktive ist im Flat-Speichermodell nicht nötig, da es nur zwei Segmente gibt. Genauer gesagt gilt grundsätzlich immer diese ASSUME-Direktive :

Diese Zuweisungen können nicht verändert werden, d.h. jede anderslautende ASSUME-Direktive im Quelltext wird ignoriert.

4.4.6 GROUP-Direktive

Im Flat-Speichermodell wird grundsätzlich nur eine Gruppe definiert : DGROUP. Diese besteht aus den beiden Segmenten _DATA und _BSS. Jede anderslautende Gruppendefinition wird ignoriert.

4.4.7 ALIGN-Direktive

Die ALIGN-Direktive wird benutzt, um den folgenden Prozessorbefehl, die folgende Datendefinition oder das folgende Symbol auf eine Adresse zu justieren, die ein Vielfaches von 2 darstellt. Die Syntax lautet wie folgt :

grenze muß ein Vielfaches von 2 zwischen 2 und 4096 sein. Der Programmzähler im jeweiligen Segment wird bei Antreffen der ALIGN-Direktive auf die nächste ganzzahlig durch grenze teilbare Adresse erhöht. Im _TEXT-Segment werden hierzu NOP-Prozessorbefehle, im _DATA-Segment Nullbytes und im _BSS-Segment Bytes mit undefiniertem Inhalt eingefügt. Hier ein paar Beispiele : Befindet sich der aktuelle Programmzähler bereits auf einer entsprechend justierten Grenze, wird die ALIGN-Direktive ignoriert.

4.4.8 EVEN-Direktive

Durch EVEN wird der aktuelle Programmzähler auf die nächste gerade Adresse ausgerichtet. Es tritt damit der gleiche Effekt wie bei

ein.

4.4.9 EVENDATA-Direktive

Durch EVENDATA wird der Programmzähler des aktuellen Datensegments auf die nächste gerade Adresse ausgerichtet. Es tritt der gleiche Effekt wie bei Verwendung von

in einem der beiden Datensegmente ein.

4.4.10 Segmentreihenfolge

Die Segmentreihenfolge im Quelltext gibt die Reihenfolge vor, in der der Linker die einzelnen Segmente im Programm aneinanderreiht. DASM32 sortiert die Segmentreihenfolge automatisch so, daß _TEXT immer vor _DATA und _BSS im Objektmodul erscheint.


4.5 Datendefinition

Datendefinitions-Direktiven werden benutzt, um Speicher in Segmenten zu belegen, wobei zwischen initialisierten und uninitialisierten Daten unterschieden wird. Initialisierte Daten werden bei der Definition mit einem bestimmten Inhalt definiert, während unitialisierte Daten mit "?" definiert werden sollten. Mit Hilfe der DUP-Direktive können außerdem ganze Blöcke mit Daten belegt werden. Die allgemeine Syntax für Datendefinitionen sieht so aus :

Das Symbol zeigt jeweils auf den Beginn der auf diese Weise definierten Daten. Der Typ der Variable hängt von der verwendeten Direktive hab. Für Datenausdruck sind folgende Angaben erlaubt : Der Konstantenausdruck gibt an, wie oft der Datenblock wiederholt werden soll. Hinter dem DUP-Schlüsselwort folgen in Klammern eingeschlossen die Elemente, die den Datenblock beschreiben. Hier ein Beispiel :
bytevar db 5 dup (1,2,3)
wordvar dw 3 dup (?)
Diese Anweisungen können auch so gesehen werden :
bytevar db 1,2,3,1,2,3,1,2,3,1,2,3,1,2,3
wordvar dw ?,?,?
Daten können mit folgenden Direktiven definiert werden :

Direktive Bedeutung
DB 1 Byte Datenfeld belegen
DW 2 Byte Datenfeld (Word) belegen
DD 4 Byte Datenfeld (Dword) belegen
DQ 8 Byte Datenfeld (Qword) belegen
DT 10 Byte Datenfeld (Tbyte) belegen

Für jede Direktive sind unterschiedliche Definitionen erlaubt :

DB (Byte)

DW (Word) DD (Dword) DQ (Qword) DT (Tbyte) Fließkommazahlen können bei DD, DQ und DT benutzt werden. Hier sind einige Beispiele für Fließkommazahlen : Fließkommazahlen werden grundsätzlich am Dezimalpunkt in der ersten Zahl erkannt.

4.6 Makros

Mit Makros bekommt man die Möglichkeit, häufig benutzte Zeichenketten oder Codefolgen mit einem symbolischen Namen anzusprechen, um so die Tipparbeit zu verringern.

4.6.1 Einfache Textmakros

Ein einfaches Textmakro ist ein Symbol, das für eine feste Zeichenkette steht. Bei der Assemblierung wird jedes Auftreten dieses Symbols im Quelltext durch die zugewiesene Zeichenkette ersetzt. Hier ein Beispiel :

Diese Definition ist identisch mit : Durch die oben benutzte EQU-Direktive werden einfache Textmakros definiert : Der Symbolname bekommt hierdurch Zeichenkette zugewiesen. Diese einfachen Textmakros sind grundsätzlich redefinierbar, d.h. die Zeichenkette kann jederzeit durch erneute Zuweisung mittels EQU geändert werden. Die Zeichenkette darf auch in spitzen Klammern stehen, wie in diesem Beispiel : 4.6.2 Mehrzeilige Makros

Mehrzeilige Makros bezeichnen (wie der Name schon sagt) Makros, die über mehrere Zeilen verteilt sind. Diesen Makros können Argumente zugewiesen werden, die beim Aufruf durch übergebene Parameter ersetzt werden. DASM32 setzt diese Makros an jeder Stelle im Quelltext ein, wo der Makroname benutzt wird :

Makroinhalt steht für den Text, der für den Makronamen eingesetzt werden soll. Als Inhalt können beliebige Befehle, Definitionen oder Anweisungen, aber keine Makrodefinitionen oder Bedingungen benutzt werden. Das Ende der Makrodefinition wird durch das Schlüsselwort ENDM angezeigt. Mehrzeilige Makros können nicht redefiniert werden, falls sie vorher nicht explizit mit PURGE gelöscht wurden. Hier ein Beispiel zur Definition : Die (optionale) Parameterliste ist eine Liste von Symbolen, die als Argumente für das Makro benutzt werden können. Die einzelnen Argumente werden durch Kommata voneinander getrennt. Hier ein Beispiel : Die Argumente a1 und a2 sind Platzhalter für echte Parameter, die beim Aufruf des Makros übergeben und innerhalb des Makros ersetzt werden : erzeugt diese Befehlsfolgen : Die einzelnen Parameter für den Aufruf des Makros werden durch Kommata oder Leerzeichen voneinander getrennt. Um bei einem Makroaufruf andere (einfache) Makros als Parameter zu übergeben, wird der %-Operator benutzt : Dem Makro MSGOUT wird hier der String "Dies ist ein Text" übergeben und nicht das Zeichen 'm'. Sollen einem Makro konstante Zeichenketten mit Leerzeichen oder Kommata übergeben werden, so müssen diese in spitze Klammern eingeschlossen werden : Innerhalb eines Makros können auch weitere Makros aufgerufen werden : erzeugt diese Befehle : Der '&'-Operator hat innerhalb eines Makrotextes eine spezielle Bedeutung : er dient zur Kennzeichnung eines Makroparameters, der von DASM32 normalerweise nicht gefunden wird (z.B. weil er Bestandteil eines Wortes ist). Hier ein Beispiel : Der Aufruf erzeugt diese Befehlsfolge : Durch den &-Operator "sieht" DASM32 das Argument sym und kann es somit durch den übergebenen Parameter ersetzen.

4.6.3 PURGE-Direktive

Die PURGE-Direktive löscht Makrodefinitionen. Die gelöschten Makros sind danach nicht mehr ansprechbar :


4.7 Bedingte Assemblierung

Mit Hilfe der bedingten Assemblierung läßt sich die Assemblierung von bestimmten Befehlssequenzen im Quelltext steuern :

IF xxx steht für eine dieser drei Bedingungen :
  1. IF Makroname Operator Zeichenkette


  2. Makroname :  Name eines einfachen Textmakros
    Operator :  einer der Operatoren =, <, >, <=, >=, EQ(gleich), GT(größer), LT(kleiner), NE(ungleich), GE(größer/gleich), LE(kleiner/gleich)
    Zeichenkette :  Die Zeichenkette, die mit dem Makro verglichen werden soll
  1. IFDEF Makroname

  2. Die Bedingung ergibt den Wert TRUE, wenn das angegebene Makro definiert ist.

  3. IFNDEF Makroname

  4. Die Bedingung ergibt den Wert TRUE, wenn das angegebene Makro nicht definiert ist.

Ergibt die Auswertung von IFxxx den Wert TRUE, wird Wahr-Block assembliert, ansonsten (bei FALSE), Falsch-Block. Innerhalb der beiden Blöcke können weitere Bedingungen auftreten, die beliebig tief verschachtelt werden dürfen.

Hier ein Beispiel :

Die Anweisung OUTPUT <"Zeile 1000"> wird hier nur assembliert, wenn vorher das Makro DEBUG definiert wurde. Bei den Vergleichsausdrücken wird immer ein Zeichenvergleich durchgeführt, d.h. das Makro wird zeichenweise mit der Zeichenkette verglichen (es findet keine Umrechnung in numerische Werte statt !).

4.8 Linkerschnittstellen

In der Regel bestehen komplette Programme aus mehreren Modulen, die alle in mehr oder weniger komplexer Weise miteinander verbunden sind. Compiler bzw. Assembler behandeln diese Module getrennt voneinander, während der Linker diese Module zu einem ausführbaren Programm zusammenfügt. Damit dies funktioniert, müssen die einzelnen Module dem Linker mitteilen, welche Daten und Funktionen sie aus anderen Modulen benötigen und welche eigenen Daten und Funktionen sie anderen Modulen zur Verfügung stellen. Dazu gibt es zwei Direktiven : EXTRN und PUBLIC.

4.8.1 EXTRN-Direktive

Die EXTRN-Direktive teilt Assembler und Linker mit, daß bestimmte Symbole in anderen Modulen definiert sind und in diesem Modul benutzt werden. Die Syntax lautet wie folgt :

Symbolname ist der Name des extern definierten Symbols und Typ der (optionale) Typ des Symbols. Als Typ sind folgende Angaben möglich :

Typ Bedeutung
near Label (z.B. Einstiegspunkt einer Prozedur) 
byte Variable vom Typ Byte (8 Bit) 
word Variable vom Typ Word (16 Bit) 
dword Variable vom Typ Dword (32 Bit) 
fword Variable vom Typ Fword (48 Bit) 
qword  Variable vom Typ Qword (64 Bit) 
tbyte Variable vom Typ Tbyte (80 Bit) 
xmmword Variable vom Typ XmmWord (128 Bit) 

Die Typ-Angabe muß mit dem Originaltyp des Symbols bei der Definition übereinstimmen. Wird die Typ-Angabe weggelassen, benutzt DASM32 das deklarierte Symbol als externes DWORD. Die EXTRN-Direktive kann an jeder beliebigen Stelle im Quelltext auftreten, auch nachdem das Symbol bereits benutzt wurde.

4.8.2 PUBLIC-Direktive

Durch die PUBLIC-Direktive werden in diesem Modul definierte Symbole anderen Modulen zur Verfügung gestellt.

Hinter dem PUBLIC-Schlüsselwort können beliebig viele Symbole mit Komma getrennt aufgelistet werden. Die PUBLIC-Direktive kann an jeder beliebigen Stelle im Quelltext auftreten, auch nachdem das Symbol bereits benutzt wurde. Veröffentlicht werden dürfen ausschließlich Variablen und Label. Makros bzw. numerische Symbole können nicht veröffentlicht werden.


4.9 Sprachreferenz

4.9.1 Schlüsselwörter :

align assume  byte db dd dq dt
dup dw dword else end endif endm
endp ends equ extrn fword group if
ifdef ifndef include label  macro  near offset
org page para proc ptr public  purge
qword segment short size tbyte type use32 
word xmmword .code .data .data? .model 

4.9.2 Register :

al  ax  eax  cs  st(0)/st  cr0  dr0  tr0  mm0  xmm0
bl bx ebx ds st(1) cr1 dr1 tr1 mm1 xmm1
cl cx ecx es st(2) cr2 dr2 tr2 mm2 xmm2
dl dx edx fs st(3) cr3 dr3 tr3 mm3 xmm3
ah sp esp gs st(4) cr4 dr4 tr4 mm4 xmm4
bh bp ebp ss st(5) cr5 dr5 tr5 mm5 xmm5
ch si esi st(6) cr6 dr6 tr6 mm6 xmm6
dh di edi st(7) cr7 dr7 tr7 mm7 xmm7

4.9.3 Vordefinierte Symbole :

_TEXT  _DATA  _BSS  DGROUP 

4.9.4 Unterstützte Mnemonics :

aaa aad aam aas adc add addps addss
and andnps andps arpl bound bsf bsr bswap
bt btc btr bts call cbw cdq clc
cld cli clts cmc cmova cmovae cmovb cmovbe
cmovc cmove cmovg cmovge cmovl cmovle cmovna cmovna
cmovnb cmovnbe cmovnc cmovne cmovng cmovnge cmovnl cmovnle
cmovno cmovnp cmovns cmovnz cmovo cmovp cmovpe cmovpo
cmovs cmovz cmp cmpps cmps cmpss cmpsb cmpsd
cmpsw cmpxchg cmpxchg8b comiss cpuid cvtpi2ps cvtps2pi cvtsi2ss
cvtss2si cvttps2pi cvttss2si cwd cwde daa das dec
div divps divss emms enter f2xm1 fabs fadd
faddp fbld fbstp fchs fclex fcmovb fcmovbe fcmove
fcmovnb fcmovnbe fcmovne fcmovnu fcmovu fcom fcomi fcomip
fcomp fcompp fcos fdecstp fdisi fdiv fdivp fdivr
fdivrp femms feni ffree fiadd ficom ficomp fidiv
fidivr fild fimul fincstp finit fist fistp fisub
fisubr fld fld1 fldcw fldenv fldl2e fldl2t fldlg2
fldln2 fldpi fldz fmul fmulp fnclex fndisi fneni
fninit fnop fnsave fnstcw fnstenv fnstsw fpatan fprem
fprem1 fptan frndint frstor fsave fscale fsetpm fsin
fsincos fsqrt fst fstcw fstenv fstp fstsw fsub
fsubp fsubr fsubrp ftst fucom fucomi fucomip fucomp
fucompp fwait fxam fxch fxrstor fxsave fxtract fyl2x
fyl2xp1 hlt idiv imul in inc ins insb
insd insw int into invd invlpg iret iretd
iretw ja jae jb jbe jc jcxz je
jecxz jg jge jl jle jmp jna jnae
jnb jnbe jnc jne jng jnge jnl jnle
jno jnp jns jnz jo jp jpe jpo
js jz lahf lar ldmxscr lea leave lgdt
lidt lds les lfs lgs lss lldt lmsw
lock lods lodsb lodsd lodsw loop loope loopne
loopnz loopz lsl ltr maskmovq maxps maxss minps
minss mov movaps movd movhps movhlps movlps movlhps
movmskps movntq movntps movq movs movsb movsd movss
movsw movsx movups movzx mul mulps mulss neg
nop not or orps out outs outsb outsd
outsw packssdw packsswb packuswb paddb paddd paddsb paddsw
paddusb paddusw paddw pand pandn pavgb pavgusb pavgw
pcmpeqb pcmpeqd pcmpeqw pcmpgtb pcmpgtd pcmpgtw pextrw pf2id
pfacc pfadd pfcmpeq pfcmpge pfcmpgt pfmax pfmin pfmul
pfrcp pfrcpit1 pfrcpit2 pfrsqit1 pfrsqrt pfsub pfsubr pi2fd
pinsrw pmaddw pmaxsw pmaxub pminsw pminub pmovmskb pmulhrw
pmulhuw pmulhw pmullw pop popa popad popaw popf
popfd popfw por prefetch prefetchnta prefetcht0 prefetcht1 prefetcht2
prefetchw psadbw pshufw pslld psllq psllw psrad psraw
psrld psrlq psrlw psubb psubd psubsb psubsw psubusb
psubusw psubw punpckhbw punpckhdq punpckhwd punpcklbw punpckldq punpcklwd
push pusha pushad pushaw pushf pushfd pushfw pxor
rcl rcpps rcpss rcr rdmsr rdpmc rdtsc rsm
rep repe repne repnz repz ret retf rol
ror rsqrtps rsqrtss sahf sal sar shl shr
sbb scas scasb scasd scasw seta setae setb
setbe setc sete setg setge setl setle setna
setnae setnb setnbe setnc setne setng setnge setnl
setnle setno setnp setns setnz seto setp setpe
setpo sets setz sfence sgdt shufps sidt shld
shrd sldt smsw sqrtps sqrtss stc std sti
stmxcsr stos stosb stosd stosw str sub subps
subss syscall sysret test ucomiss ud2 unpckhps unpcklps
verr verw wait wbinvd wrsmr xadd xchg xlat
xlatb xor xorps

Bei Schlüsselwörtern, Registern und Mnemonics findet keine Unterscheidung zwischen Groß- und Kleinschreibung statt. Die Erkennung der vordefinierten Symbole dagegen ist abhängig von der Kommandozeilen-Option -ml. Sie unterliegen damit den gleichen Regeln wie selbstdefinierte Symbole.