Assembler-Syntax

Aus RAD Studio
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 Delphi definiert. Geben Sie vor einer Anweisung ein Label und einen Doppelpunkt ein. Für ein Label gibt es keine Längenbeschränkung. Wie in Delphi 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 Delphi 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'                   { {{Delphi}} 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 Delphi-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