Assembler-Syntax

Aus Appmethod Topics
Wechseln zu: Navigation, Suche

Nach oben zu Verwendung des integrierten Assemblers - Index

In diesem Thema werden die Elemente der Assembler-Syntax beschrieben.

Anweisungen

Die Syntax für eine Assembly-Anweisung lautet:

Label: Prefix Opcode Operand1, Operand2

Label steht für einen Label-Bezeichner, Prefix für einen Assembly-Präfix-Opcode (Operationscode), Opcode für eine Assembly-Anweisung oder -Direktive und Operand für einen Assembly-Ausdruck. Label und Prefix sind optional. Es gibt Opcodes mit nur einem oder überhaupt keinem Operanden.

Kommentare sind nur zwischen, nicht aber innerhalb von Assembly-Anweisungen erlaubt. Zum Beispiel:

     MOV AX,1 {Initial value}  { OK }
     MOV CX,100 {Count}        { OK }
     
     MOV {Initial value} AX,1; { Error! }
     MOV CX, {Count} 100       { Error! }

Labels

Label werden in integrierten Assembly-Anweisungen auf die gleiche Weise wie in Object Pascal definiert. Geben Sie vor einer Anweisung ein Label und einen Doppelpunkt ein. Für ein Label gibt es keine Längenbeschränkung. Wie in Object Pascal müssen alle Label im label-Deklarationsabschnitt des Blocks definiert werden, der die asm-Anweisung enthält. Von dieser Regel gibt es eine Ausnahme: lokale Label.

Lokale Label beginnen immer mit dem Zeichen @. Danach können ein oder mehrere Buchstaben, Ziffern, Unterstriche oder @-Zeichen angegeben werden. Ein lokales Label ist auf asm-Anweisungen beschränkt. Der Gültigkeitsbereich eines lokalen Labels erstreckt sich vom reservierten Wort asm bis zum Ende der asm-Anweisung, die das Label enthält. Ein lokales Label muss nicht deklariert werden.

Anweisungs-Opcodes

Der integrierte Assembler unterstützt alle von Intel dokumentierten Opcodes. Beachten Sie, dass bestimmte betriebssystemspezifische Anweisungen unter Umständen nicht unterstützt werden. Die folgenden Anweisungsfamilien werden in jedem Fall unterstützt:

  • IA-32
    • Pentium-Familie
    • Pentium Pro und Pentium II
    • Pentium III
    • Pentium 4
  • Intel 64

Darüber hinaus unterstützt der integrierte Assembler die folgenden Erweiterungen des Befehlssatzes:

  • Intel SSE (einschließlich SSE4.2)
  • AMD 3DNow! (ab AMD K6 aufwärts)
  • AMD Enhanced 3DNow! (ab AMD Athlon aufwärts)

Eine vollständige Beschreibung aller Anweisungen finden Sie in der Dokumentation Ihres Mikroprozessors.

Automatische Sprungoptimierung

Wenn nicht anders angegeben, optimiert der integrierte Assembler Sprunganweisungen durch automatische Auswahl der kürzesten und damit effektivsten Form eines Sprungbefehls. Diese automatische Anpassung wird bei der nicht bedingten Sprunganweisung (JMP) und allen bedingten Sprunganweisungen angewendet, wenn es sich bei dem Ziel um ein Label (und nicht um eine Prozedur oder Funktion) handelt.

Bei einer nicht bedingten Sprunganweisung (JMP) erzeugt der integrierte Assembler einen kurzen Sprung (ein Byte Opcode und ein Byte mit Angabe der Sprungweite), wenn die Adressdifferenz zum Ziel-Label im Bereich von -128 bis 127 Byte liegt. Andernfalls wird ein Near-Sprung generiert (ein Byte Opcode und zwei Byte für die Sprungweite).

Bei einer bedingten Sprunganweisung wird ein kurzer Sprung (ein Byte Opcode und ein Byte mit Angabe der Sprungweite) erzeugt, wenn der Adressabstand zum Ziel-Label im Bereich von -128 bis 127 Byte liegt. Andernfalls generiert der integrierte Assembler einen Near-Sprung zum Ziel-Label.

Sprünge zu Eintrittspunkten von Prozeduren und Funktionen sind immer Near-Sprünge.

Direktiven

Der integrierte Assembler unterstützt drei Assembly-Definitionsdirektiven: DB (Define Byte), DW (Define Word) und DD (Define Double Word). Jede dieser Direktiven erzeugt Daten, die den durch Kommas voneinander getrennten, nachgestellten Operanden entsprechen.

Direktive Beschreibung

DB

"Define byte": Erzeugt eine Byte-Sequenz. Jeder Operand kann ein konstanter Ausdruck mit einem Wert zwischen 128 und 255 oder ein String von beliebiger Länge sein. Konstante Ausdrücke erzeugen Code mit einer Länge von einem Byte. Strings definieren eine Byte-Folge, die den ASCII-Codes der enthaltenen Zeichen entspricht.

DW

"Define word": Erzeugt eine Word-Sequenz. Jeder Operand kann ein konstanter Ausdruck mit einem Wert zwischen 32.768 und 65.535 oder ein Adressausdruck sein. Bei einem Adressausdruck erzeugt der integrierte Assembler einen Near-Zeiger, d.h. einen Word-Wert mit dem Offset-Anteil der Adresse.

DD

"Define double word": Erzeugt eine Double-Word-Sequenz. Jeder Operand kann ein konstanter Ausdruck mit einem Wert zwischen 2.147.483.648 und 4.294.967.295 oder ein Adressausdruck sein. Bei einem Adressausdruck erzeugt der integrierte Assembler einen Far-Zeiger, d.h. einen Word-Wert mit dem Offset und einen Word-Wert mit dem Segment der Adresse.

DQ

"Define quad word": Definiert ein Quad-Word für Int64-Werte.

Die durch die Direktiven DB, DW und DD erzeugten Daten werden wie der Code, der von anderen integrierten Assembly-Anweisungen erzeugt wird, immer im Code-Segment gespeichert. Zum Erstellen nicht initialisierter Daten im Datensegment müssen Sie die var- und const-Deklarationen von Object Pascal verwenden.

Hier einige Beispiele für die Direktiven DB, DW und DD:

asm
  DB     FFH                           { One byte }
  DB     0.99                          { Two bytes }
  DB     'A'                           { Ord('A') }
  DB     'Hello world...',0DH,0AH      { String followed by CR/LF }
  DB     12,'string'                   { Object Pascal style string }
  DW     0FFFFH                        { One word }
  DW     0,9999                        { Two words }
  DW 	'A'                             { Same as DB  'A',0 }
  DW 	'BA'                            { Same as DB 'A','B' }
  DW 	MyVar                           { Offset of MyVar }
  DW 	MyProc                          { Offset of MyProc }
  DD 	0FFFFFFFFH                      { One double-word }
  DD 	0,999999999			{ Two double-words }
  DD 	'A'				{ Same as DB 'A',0,0,0 }
  DD 	'DCBA'				{ Same as DB 'A','B','C','D' }
  DD 	MyVar 				{ Pointer to MyVar }
  DD 	MyProc				{ Pointer to MyProc }
 end;

Wenn vor der Direktive DB, DW oder DD ein Bezeichner angegeben wird, führt dies zur Deklaration einer Variable mit einem Byte, einem Word bzw. einem Double Word an der Speicheradresse der Direktive. Die folgenden Anweisungen sind beispielsweise im Assembler zulässig:

     ByteVar 		DB	?
     WordVar 		DW 	?
     IntVar		DD 	?
	.
	.
	.
     			MOV 	AL,ByteVar
     			MOV 	BX,WordVar
     			MOV	ECX,IntVar

Der integrierte Assembler unterstützt diese Variablendeklarationen jedoch nicht. Das einzige Symbol, das in einer integrierten Assembly-Anweisung definiert werden kann, ist ein Label. Alle Variablen müssen in der Object Pascal-Syntax deklariert werden. Die obige Konstruktion entspricht folgenden Deklarationen:

var
  ByteVar: Byte;
  WordVar: Word;
  IntVar: Integer;
       .
       .
       .
asm
  MOV AL,ByteVar
  MOV BX,WordVar
  MOV ECX,IntVar
end;

Mit den Direktiven SMALL und LARGE kann die Sprungweite festgelegt werden:

MOV EAX, [LARGE $1234]

Diese Anweisung generiert eine "normale" Verschiebung mit einer 32-Bit-Sprungweite ($00001234):

MOV EAX, [SMALL $1234]

Die zweite Anweisung generiert eine Verschiebung mit einem Präfix zur Adressgrößenüberschreibung und einer 16-Bit-Sprungweite ($1234).

Mit der Direktive SMALL kann Speicherplatz gespart werden. Das folgende Beispiel generiert eine Adressgrößenüberschreibung und eine 2-Byte-Adresse (insgesamt 3 Byte):

 MOV EAX, [SMALL 123]

im Gegensatz zu:

 MOV EAX, [123]

Letzteres generiert keine Adressgrößenüberschreibung und eine 4-Byte-Adresse (insgesamt also 4 Byte).

Zwei zusätzliche Direktiven ermöglichen dem Assembly-Code den Zugriff auf dynamische und virtuelle Methoden: VMTOFFSET und DMTINDEX.

VMTOFFSET ruft den Byte-Offset des Tabelleneintrags mit dem virtuellen Methodenzeiger des virtuellen Methodenarguments vom Anfang der virtuellen Methodentabelle (VMT) ab. Diese Direktive benötigt einen vollständig angegebenen Klassennamen mit einem Methodennamen als Parameter (beispielsweise TExample.VirtualMethod) oder einen Interface-Namen und einen Interface-Methodennamen.

DMTINDEX ruft den dynamischen Methodentabellenindex der übergebenen dynamischen Methode ab. Diese Direktive benötigt ebenfalls einen vollständig angegebenen Klassennamen mit einem Methodennamen als Parameter (z.B. TExample.DynamicMethod). Sie rufen die dynamische Methode mit System.@CallDynaInst mit dem (E)SI-Register auf, das den von DMTINDEX abgerufenen Wert enthält.

Hinweis: Methoden mit der message-Direktive werden als dynamische Methoden implementiert und können auch mit DMTINDEX aufgerufen werden. Beispiel:

  TMyClass = class
    procedure x; message MYMESSAGE;
  end;

Das folgende Beispiel verwendet sowohl DMTINDEX als auch VMTOFFSET für den Zugriff auf dynamische und virtuelle Methoden:

  program Project2;
  type
    TExample = class
      procedure DynamicMethod; dynamic;
      procedure VirtualMethod; virtual;
    end;
  procedure TExample.DynamicMethod;
  begin

  end;
  procedure TExample.VirtualMethod;
  begin

  end;
 
  procedure CallDynamicMethod(e: TExample);
  asm
    	 // Save ESI register
     	PUSH    ESI
       // Instance pointer needs to be in EAX
     	MOV     EAX, e

	// DMT entry index needs to be in (E)SI
     	MOV     ESI, DMTINDEX TExample.DynamicMethod

	// Now call the method
     	CALL    System.@CallDynaInst

	// Restore ESI register
     	POP ESI

   end;

   procedure CallVirtualMethod(e: TExample);
   asm
     	// Instance pointer needs to be in EAX
     	MOV     EAX, e
      // Retrieve VMT table entry
       MOV     EDX, [EAX]
      // Now call the method at offset VMTOFFSET
     	CALL    DWORD PTR [EDX + VMTOFFSET TExample.VirtualMethod]
   end;

  var
     e: TExample;
     begin
     	e := TExample.Create;
     	try
     	  CallDynamicMethod(e);
     	  CallVirtualMethod(e);
     	finally
     	  e.Free;
     end;
   	end.

Operanden

Die Operanden des Inline-Assemblers sind Ausdrücke, die aus Konstanten, Registern, Symbolen und Operatoren bestehen.

Die folgenden reservierten Wörter haben bei ihrer Verwendung in Operanden eine vordefinierte Bedeutung:

Reservierte Wörter im integrierten Assembler

CPU-Register

Kategorie

Bezeichner

8-Bit-CPU-Register

AH, AL, BH, BL, CH, CL, DH, DL (Allzweckregister);

16-Bit-CPU-Register

AX, BX, CX, DX (Allzweckregister); DI, SI, SP, BP (Indexregister); CS, DS, SS, ES (Segmentregister); IP (Anweisungszeiger)

32-Bit-CPU-Register

EAX, EBX, ECX, EDX (Allzweckregister); EDI, ESI, ESP, EBP (Indexregister); FS, GS (Segmentregister); EIP

FPU

ST(0), ..., ST(7)

MMX-FPU-Register

mm0, ..., mm7

XMM-Register

xmm0, ..., xmm7 (..., xmm15 auf x64)

Intel-64-Register

RAX, RBX, ...


Daten und Operatoren

Kategorie

Bezeichner

Daten

BYTE, WORD, DWORD, QWORD, TBYTE

Operatoren

NOT, AND, OR, XOR; SHL, SHR, MOD; LOW, HIGH; OFFSET, PTR, TYPE

VMTOFFSET, DMTINDEX

SMALL, LARGE


Reservierte Wörter haben immer Vorrang vor benutzerdefinierten Bezeichnern. Beispiel:

var
  Ch: Char;
 .
 .
 .
asm
  MOV	CH, 1
end;

Im vorangegangenen Codefragment wird 1 nicht in die Variable Ch, sondern in das Register CH geladen. Wenn Sie auf ein benutzerdefiniertes Symbol zugreifen möchten, das den Namen eines reservierten Wortes trägt, müssen Sie den Operator & zum Überschreiben des Bezeichners verwenden:

MOV&Ch, 1

Benutzerdefinierte Bezeichner sollten möglichst nie mit den Namen reservierter Wörter belegt werden.

Siehe auch