Assembler-Ausdrücke

Aus RAD Studio
Wechseln zu: Navigation, Suche

Nach oben zu Verwendung des integrierten Assemblers - Index

Der integrierte Assembler wertet alle Ausdrücke als 32-Bit-Integerwerte aus. Gleitkomma- und String-Werte werden mit Ausnahme von String-Konstanten nicht unterstützt.

Ausdrücke werden aus Ausdruckselementen und Operatoren erstellt und gehören zu einer bestimmten Ausdrucksklasse und zu einem bestimmten Ausdruckstyp.

Unterschiede zwischen Ausdrücken in Delphi und Assembler

Der größte Unterschied zwischen Delphi-Ausdrücken und Ausdrücken des integrierten Assemblers besteht darin, dass Assembler-Ausdrücke einen konstanten Wert ergeben müssen, d.h. einen Wert, der während der Compilierung berechnet werden kann. Ausgehend von den Deklarationen:

const
  X = 10;
  Y = 20;
var
  Z: Integer;

ist folgende Assembler-Anweisung zulässig:

asm
  MOV      Z,X+Y
end;

Da X und Y Konstanten sind, ist der Ausdruck X + Y nur eine andere Möglichkeit zur Darstellung der Konstante 30. Die resultierende Anweisung bewirkt eine direkte Speicherung des Wertes 30 in der Variable Z. Wenn X und Y aber Variablen sind, also:

var
  X, Y: Integer;

kann der integrierte Assembler den Wert von X + Y nicht während der Compilierung berechnen. In diesem Fall müssten Sie folgende Anweisung verwenden, um die Summe von X und Y in Z zu speichern:

asm
  MOV          EAX,X
  ADD          EAX,Y
  MOV          Z,EAX
end;

In einem Delphi-Ausdruck zeigt eine Variablenreferenz auf den Inhalt der Variable. In einem Assembler-Ausdruck hingegen gibt eine Variablenreferenz die Adresse der Variable an. Beispielsweise bezieht sich in Delphi der Ausdruck X + 4 (in dem X eine Variable ist) auf den Inhalt von X + 4. Im integrierten Assembler bedeutet dieser Ausdruck, dass sich der Inhalt des Word an einer Adresse befindet, die um vier Byte höher ist als die Adresse von X. So ist zwar folgende Anweisung zulässig:

asm
  MOV          EAX,X+4
end;

Der Wert von X + 4 wird aber nicht in AX geladen, sondern der Word-Wert, der vier Byte nach X gespeichert ist. Um 4 zum Inhalt von X zu addieren, müssen Sie folgende Anweisung verwenden:

asm
  MOV          EAX,X
  ADD          EAX,4
end;

Ausdruckselemente

Zu den Elementen eines Ausdrucks gehören Konstanten, Register und Symbole.

Numerische Konstanten

Bei numerischen Konstanten muss es sich um Integerwerte zwischen 2.147.483.648 und 4.294.967.295 handeln.

Per Voreinstellung wird bei numerischen Konstanten die dezimale Schreibweise verwendet. Der integrierte Assembler unterstützt aber auch die binäre, oktale und hexadezimale Notation. Bei der binären Notation geben Sie nach der Zahl den Buchstaben B, bei der oktalen nach der Zahl den Buchstabe O und bei der hexadezimalen nach der Zahl den Buchstaben H oder vor der Zahl das Zeichen $ an.

Numerische Konstanten müssen mit den Ziffern 0 bis 9 oder mit dem Zeichen $ beginnen. Wenn Sie eine hexadezimale Konstante mit dem Suffix H angeben, und die erste signifikante Stelle eine hexadezimale Ziffer zwischen A und F ist, müssen Sie der Zahl eine zusätzliche Null an den Beginn der Zahl voranstellen. So handelt es sich beispielsweise bei 0BAD4H und $BAD4 um hexadezimale Konstanten, bei BAD4H aber um einen Bezeichner, weil der Ausdruck mit einem Buchstaben beginnt.

String-Konstanten

String-Konstanten müssen in halbe oder ganze Anführungszeichen eingeschlossen werden. Zwei aufeinander folgende Anführungszeichen desselben Typs als umgebende Anführungszeichen werden als ein Zeichen interpretiert. Es folgen einige Beispiele für String-Konstanten:

'Z'
'Delphi'
'Windows'
"That's all folks"
'"That''s all folks," he said.'''
'100'
'"'
"'"

In DB-Direktiven sind String-Konstanten von beliebiger Länge zulässig. Sie bewirken die Zuweisung einer Byte-Folge, die die ASCII-Werte der Zeichen in dem String enthält. In allen anderen Fällen darf eine String-Konstante nicht länger als vier Zeichen sein und muss einen in einem Ausdruck zulässigen numerischen Wert angeben. Der numerische Wert einer String-Konstante wird wie folgt berechnet:

Ord(Ch1) + Ord(Ch2) shl 8 + Ord(Ch3) shl 16 + Ord(Ch4) shl 24

Dabei ist Ch1 das am weitesten rechts stehende (letzte) Zeichen und Ch4 das am weitesten links stehende (erste) Zeichen. Wenn der String weniger als vier Zeichen enthält, werden für die am weitesten links stehenden Zeichen Nullen angenommen. Die folgende Tabelle enthält einige Beispiele für String-Konstanten und die entsprechenden numerischen Werte.

Beispiele für String-Konstanten und ihre Werte:

String Wert

'a'

00000061H

'ba'

00006261H

'cba'

00636261H

'dcba'

64636261H

'a'

00006120H

' a'

20202061H

'a' * 2

000000E2H

'a'-'A'

00000020H

not 'a'

FFFFFF9EH

Register

Die reservierten Symbole in der folgenden Tabelle geben die CPU-Register im Inline-Assembler an:


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, ...


x64-CPU-Allzweckregister, x86-FPU-Datenregister und x64-SSE-Datenregister

X64 GPR.pngX86 FPU.pngX64 SSE.png

Wenn ein Operand nur aus einem Registernamen besteht, wird er als Register-Operand bezeichnet. Als Register-Operanden können alle Register verwendet werden. Einige Register lassen sich auch in einem anderen Kontext einsetzen.

Die Basisregister (BX und BP) und die Indexregister (SI und DI) können zur Kennzeichnung der Indizierung in eckigen Klammern angegeben werden. Folgende Kombinationen von Basis-/Indexregistern sind zulässig: [BX], [BP], [SI], [DI], [BX+SI], [BX+DI], [BP+SI] und [BP+DI]. Sie können auch mit allen 32-Bit-Registern indizieren, z.B. [EAX+ECX], [ESP] und [ESP+EAX+5].

Die Segmentregister (ES, CS, SS, DS, FS und GS) werden unterstützt, aber in 32-Bit-Anwendungen ist die Verwendung von Segmenten normalerweise nicht sinnvoll.

Das Symbol ST bezeichnet das oberste Register im 8087-Gleitkommaregister-Stack. Jedes der acht Gleitkommaregister kann mit ST(X) referenziert werden, wobei X eine Konstante von 0 bis 7 ist, die den Abstand vom oberen Stack-Ende angibt.

Symbole

Der integrierte Assembler ermöglicht den Zugriff auf nahezu alle Delphi-Bezeichner in Assembly-Ausdrücken, einschließlich Konstanten, Typen, Variablen, Prozeduren und Funktionen. Außerdem ist im integrierten Assembler das spezielle Symbol @Result implementiert, das der Variable Result im Rumpf einer Funktion entspricht. So kann beispielsweise die Funktion:

function Sum(X, Y: Integer): Integer;
begin
  Result := X + Y;
end;

in Assembly folgendermaßen angegeben werden:

function Sum(X, Y: Integer): Integer; stdcall;
begin
  asm
    MOV        EAX,X
    ADD        EAX,Y
    MOV        @Result,EAX
  end;
end;

Die folgenden Symbole dürfen in asm-Anweisungen nicht verwendet werden:

  • Standardprozeduren und -funktionen (z.B. Writeln und Chr).
  • String-, Gleitkomma- und Mengenkonstanten (außer beim Laden von Registern).
  • Label, die nicht im aktuellen Block deklariert sind.
  • Das Symbol @Result außerhalb einer Funktion.

Die folgende Tabelle enthält die Symbolarten, die in asm-Anweisungen verwendet werden können.

Im integrierten Assembler verwendbare Symbole:

Symbol Wert Klasse Typ

Label

Adresse des Labels

Speicherreferenz  

Größe des Typs

Konstante

Wert der Konstante

Direkter Wert

0

Typ

0

Speicherreferenz

Größe des Typs

Feld

Offset des Feldes

Speicher

Größe des Typs

Variable

Adresse der Variable oder Adresse eines Zeigers auf die Variable

Speicherreferenz

Größe des Typs

Prozedur

Adresse der Prozedur

Speicherreferenz

Größe des Typs

Funktion

Adresse der Funktion

Speicherreferenz

Größe des Typs

Unit

0

Direkter Wert

0

@Result

Offset der Ergebnisvariable

Speicherreferenz

Größe des Typs

Bei deaktivierter Optimierung werden lokale (in Prozeduren und Funktionen deklarierte) Variablen immer auf dem Stack abgelegt. Der Zugriff erfolgt stets relativ zu EBP. Der Wert eines lokalen Variablensymbols ist sein mit Vorzeichen versehenes Offset von EBP aus. Der Assembler addiert zu Referenzen auf lokale Variablen automatisch [EBP] hinzu. Wenn eine Funktion oder Prozedur beispielsweise die Deklaration:

var Count: Integer;

enthält, wird die Anweisung:

MOV    EAX,Count

in MOV EAX,[EBP4] assembliert.

Der integrierte Assembler behandelt var-Parameter als 32-Bit-Zeiger. Die Größe eines var-Parameters beträgt immer 4 Byte. Die Syntax für den Zugriff auf einen var-Parameter unterscheidet sich von derjenigen für einen Wertparameter. Für den Zugriff auf den Inhalt eines var-Parameters müssen Sie zuerst den 32-Bit-Zeiger laden und dann auf die Speicheradresse zugreifen, auf die er zeigt. Beispiel:

function Sum(var X, Y: Integer): Integer; stdcall;
begin
  asm
    MOV                EAX,X
    MOV                EAX,[EAX]
    MOV                EDX,Y
    ADD                EAX,[EDX]
    MOV                @Result,EAX
  end;
end;

Bezeichner können in asm-Anweisungen qualifiziert werden. Ausgehend von den Deklarationen:

type
  TPoint = record
    X, Y: Integer;
  end;
  TRect = record
    A, B: TPoint;
  end;
var
  P: TPoint;
  R: TRect;

kann mit folgenden Konstruktionen in einer asm-Anweisung auf die Felder zugegriffen werden:

MOV    EAX,P.X
MOV    EDX,P.Y
MOV    ECX,R.A.X
MOV    EBX,R.B.Y

Typbezeichner können zur einfachen und schnellen Erstellung von Variablen verwendet werden. Die folgenden Anweisungen erzeugen denselben Maschinencode, der den Inhalt von [EDX] in das Register EAX lädt.

MOV    EAX,(TRect PTR [EDX]).B.X
MOV    EAX,TRect([EDX]).B.X
MOV    EAX,TRect[EDX].B.X
MOV    EAX,[EDX].TRect.B.X

Ausdrucksklassen

Der integrierte Assembler unterteilt Ausdrücke in drei Klassen: Register, Speicherreferenzen und direkte Werte.

Ausdrücke, die nur aus einem Registernamen bestehen, nennt man Registerausdrücke. Beispiele hierfür sind AX, CL, DI und ES. Als Operanden verwendete Registerausdrücke weisen den Assembler an, Anweisungen zu erzeugen, die mit CPU-Registern arbeiten.

Ausdrücke, die Speicheradressen bezeichnen, nennt man Speicherreferenzen. Zu dieser Kategorie gehören Label, Variablen, typisierte Konstanten, Prozeduren und Funktionen von Delphi.

Ausdrücke, bei denen es sich nicht um Register handelt und die auch nicht auf Speicheradressen zeigen, werden als direkte Werte bezeichnet. Zu dieser Gruppe gehören untypisierte Konstanten und Typbezeichner von Delphi.

Wenn direkte Werte und Speicherreferenzen als Operanden verwendet werden, führt dies zu unterschiedlichem Code. Beispiel:

const
  Start = 10;
var
  Count: Integer;
// …
asm
  MOV  EAX,Start       { MOV EAX,xxxx }
  MOV  EBX,Count       { MOV EBX,[xxxx] }
  MOV  ECX,[Start]     { MOV ECX,[xxxx] }
  MOV  EDX,OFFSET Count    { MOV EDX,xxxx }
end;

Da Start ein direkter Wert ist, wird das erste MOV in eine Move-Immediate-Anweisung assembliert. Das zweite MOV wird in eine Move-Memory-Anweisung übersetzt, weil Count eine Speicherreferenz ist. Im dritten MOV wird Start wegen der eckigen Klammern in eine Speicherreferenz umgewandelt (in diesem Fall handelt es sich um das Word mit dem Offset 10 im Datensegment). Im vierten MOV sorgt der Operator OFFSET für die Konvertierung von Count in einen direkten Wert (mit dem Offset von Count im Datensegment).

Die eckigen Klammern und der Operator OFFSET ergänzen einander. Die folgende asm-Anweisung erzeugt denselben Maschinencode wie die ersten beiden Zeilen der obigen asm-Anweisung:

asm
  MOV      EAX,OFFSET [Start]
  MOV      EBX,[OFFSET Count]
end;

Bei Speicherreferenzen und direkten Werten findet eine weitere Unterteilung in verschiebbare und absolute Ausdrücke statt. Unter einer Verschiebung versteht man den Vorgang, bei dem der Linker Symbolen eine absolute Adresse zuweist. Ein verschiebbarer Ausdruck bezeichnet einen Wert, für den beim Linken eine Verschiebung (Relokation) erforderlich ist. Dagegen bezeichnet ein absoluter Ausdruck einen Wert, bei dem dies nicht nötig ist. In der Regel handelt es sich bei Ausdrücken, die ein Label, eine Variable, eine Prozedur oder eine Funktion referenzieren, um verschiebbare Ausdrücke, weil die endgültige Adresse dieser Symbole zur Compilierungszeit nicht bekannt ist. Absolut sind dagegen Ausdrücke, die ausschließlich mit Konstanten arbeiten.

Im integrierten Assembler kann mit absoluten Werten jede Operation ausgeführt werden. Mit verschiebbaren Ausdrücken ist dagegen nur die Addition und Subtraktion von Konstanten möglich.

Ausdruckstypen

Jedem Assembler-Ausdruck ist ein bestimmter Typ (genauer gesagt eine bestimmte Größe) zugeordnet, weil der Assembler den Typ eines Ausdrucks einfach aus der Größe seiner Speicherposition abliest. Beispielsweise hat eine Integer-Variable den Typ Quad, weil sie vier Byte Speicherplatz belegt. Der integrierte Assembler führt, wenn möglich, immer eine Typprüfung durch. Beispiel:

var
  QuitFlag: Boolean;
  OutBufPtr: Word;
// …
asm
  MOV      AL,QuitFlag
  MOV      BX,OutBufPtr
end;

Der Assembler prüft, ob die Größe von QuitFlag eins (ein Byte) und die Größe von OutBufPtr zwei (ein Word) beträgt. Die folgende Anweisung führt zu einem Fehler:

MOV        DL,OutBufPtr

Das Problem liegt darin, dass DL nur ein Byte groß ist, während OutBufPtr ein Word ist. Der Typ einer Speicherreferenz kann durch eine Typumwandlung geändert werden. Die obige Anweisung müsste also folgendermaßen formuliert werden:

MOV        DL,BYTE PTR OutBufPtr
MOV        DL,Byte(OutBufPtr)
MOV        DL,OutBufPtr.Byte

Diese MOV-Anweisungen referenzieren das erste (niederwertige) Byte der Variable OutBufPtr.

Es gibt Fälle, in denen eine Speicherreferenz untypisiert ist. Ein Beispiel hierfür ist ein direkter Wert (Buffer), der in eckige Klammern gesetzt ist:

procedure Example(var Buffer);
asm
   MOV AL,     [Buffer]
   MOV CX,     [Buffer]
   MOV EDX,   [Buffer]
end;

Der integrierte Assembler lässt beide Anweisungen zu, da dem Ausdruck [Buffer] kein Typ zugeordnet ist. [Buffer] bezeichnet einfach den Inhalt der von Buffer angegebenen Adresse, und der Typ kann anhand des ersten Operanden festgestellt werden (Byte für AL, Word für CX und Double Word für EDX).

Falls sich der Typ nicht über einen anderen Operanden ermitteln lässt, ist für den integrierten Assembler eine explizite Typumwandlung erforderlich. Beispiel:

INC     BYTE PTR [ECX]
IMUL    WORD PTR [EDX]

Die folgende Tabelle enthält die vordefinierten Typsymbole, die der integrierte Assembler zusätzlich zu den aktuell deklarierten Delphi-Typen bereitstellt.

Vordefinierte Typsymbole:

Symbol    Typ   

BYTE

1

WORD

2

DWORD

4

QWORD

8

TBYTE

10

Ausdrucksoperatoren

Der integrierte Assembler stellt eine Vielzahl von Operatoren bereit. Die Regeln für die Rangfolge der Operatoren unterscheiden sich von den Regeln in Delphi. Beispielsweise haben die Operatoren für Addition und Subtraktion in einer asm-Anweisung Vorrang gegenüber AND. Die folgende Tabelle enthält die Ausdrucksoperatoren des integrierten Assemblers. Die Operatoren sind nach ihrer Rangfolge sortiert.

Rangfolge der Ausdrucksoperatoren des integrierten Assemblers

Operatoren Bemerkungen Rangfolge

&

Höchste Stufe

(... ), [... ],., HIGH, LOW

+, -

Unäres + und -

:

OFFSET, TYPE, PTR, *, /, MOD, SHL, SHR, +, -   

Binäres + und -

NOT, AND, OR, XOR

Niedrigste Stufe


Die folgende Tabelle fasst die Ausdrucksoperatoren des integrierten Assemblers zusammen:

Erläuterung der Ausdrucksoperatoren des integrierten Assemblers:

Operator Beschreibung

&

Überschreiben von Bezeichnern. Der Bezeichner, der unmittelbar auf das Zeichen & folgt, wird als benutzerdefiniertes Symbol betrachtet. Dies gilt auch dann, wenn er mit einem reservierten Symbol des integrierten Assemblers identisch ist.

(... )

Unterausdruck. Ausdrücke in Klammern werden vollständig ausgewertet und als einzelnes Ausdruckselement betrachtet. Optional kann dem Ausdruck in Klammern ein weiterer Ausdruck vorangestellt werden. Das Resultat ist in diesem Fall die Summe der Werte der beiden Ausdrücke. Der Typ des ersten Ausdrucks bestimmt den Ergebnistyp.

[... ]

Speicherreferenz. Der Ausdruck in eckigen Klammern wird vollständig ausgewertet und dann als einzelnes Ausdruckselement betrachtet. Das Resultat ist in diesem Fall die Summe der Werte der beiden Ausdrücke. Der Typ des ersten Ausdrucks bestimmt den Ergebnistyp. Das Ergebnis ist immer eine Speicherreferenz.

.

Selektor für Strukturelemente. Das Resultat ergibt sich aus der Addition der Ausdrücke vor und nach dem Punkt. Der Typ des Ausdrucks nach dem Punkt bestimmt den Ergebnistyp. Im Ausdruck nach dem Punkt kann auf Symbole, die zum Gültigkeitsbereich des Ausdrucks vor dem Punkt gehören, zugegriffen werden.

HIGH

Gibt die höherwertigen acht Bits des Word-Ausdrucks zurück, der auf den Operator folgt. Der Ausdruck muss ein absoluter direkter Wert sein.

LOW

Gibt die niederwertigen acht Bits des Word-Ausdrucks zurück, der auf den Operator folgt. Der Ausdruck muss ein absoluter direkter Wert sein.

+

Unäres Plus. Gibt den auf das Pluszeichen folgenden Ausdruck ohne Änderungen zurück. Der Ausdruck muss ein absoluter direkter Wert sein.

-

Unäres Minus. Gibt den negativen Wert des Ausdrucks zurück, der auf das Minuszeichen folgt. Der Ausdruck muss ein absoluter direkter Wert sein.

+

Addition. Die Ausdrücke können direkte Werte oder Speicherreferenzen sein. Nur einer der Ausdrücke darf aus einem verschiebbaren Wert bestehen. Handelt es sich bei einem der Ausdrücke um einen verschiebbaren Wert, ist das Ergebnis ebenfalls ein verschiebbarer Wert. Ist einer der Ausdrücke eine Speicherreferenz, ist auch das Ergebnis eine Speicherreferenz.

-

Subtraktion. Der erste Ausdruck kann zu einer beliebigen Klasse gehören, der zweite muss ein absoluter direkter Wert sein. Das Ergebnis gehört zur gleichen Klasse wie der erste Ausdruck.

:

Überschreiben von Segmenten. Teilt dem Assembler mit, dass der Ausdruck nach dem Doppelpunkt zu dem Segment gehört, das durch den Segmentregisternamen (CS, DS, SS, FS, GS oder ES) vor dem Doppelpunkt angegeben ist. Das Ergebnis ist eine Speicherreferenz mit dem Wert des Ausdrucks nach dem Doppelpunkt. Wird in einem Anweisungsoperanden das Überschreiben eines Segments verwendet, wird der Anweisung eine entsprechende Präfixanweisung zur Segmentüberschreibung vorangestellt. Dies stellt sicher, dass das angegebene Segment ausgewählt ist.

OFFSET

Liefert den Offset-Anteil (Double Word) des Ausdrucks zurück, der auf den Operator folgt. Das Ergebnis ist ein direkter Wert.

TYPE

Liefert den Typ (die Größe in Byte) des Ausdrucks zurück, der auf den Operator folgt. Der Typ eines direkten Wertes ist 0.

PTR

Typumwandlungsoperator. Das Ergebnis ist eine Speicherreferenz mit dem Wert des Ausdrucks, der auf den Operator folgt, und mit dem Typ des Ausdrucks, der dem Operator vorangeht.

*

Multiplikation. Beide Ausdrücke müssen absolute, direkte Werte sein. Das Ergebnis ist ebenfalls ein absoluter direkter Wert.

/

Integerdivision. Beide Ausdrücke müssen absolute, direkte Werte sein. Das Ergebnis ist ebenfalls ein absoluter direkter Wert.

MOD

Rest einer Integerdivision. Beide Ausdrücke müssen absolute, direkte Werte sein. Das Ergebnis ist ebenfalls ein absoluter direkter Wert.

SHL

Logische Linksverschiebung. Beide Ausdrücke müssen absolute, direkte Werte sein. Das Ergebnis ist ebenfalls ein absoluter direkter Wert.

SHR

Logische Rechtsverschiebung. Beide Ausdrücke müssen absolute, direkte Werte sein. Das Ergebnis ist ebenfalls ein absoluter direkter Wert.

NOT

Bitweise Negation. Der Ausdruck muss ein absoluter, direkter Wert sein. Das Ergebnis ist ebenfalls ein absoluter direkter Wert.

AND

Bitweises AND. Beide Ausdrücke müssen absolute, direkte Werte sein. Das Ergebnis ist ebenfalls ein absoluter direkter Wert.

OR

Bitweises OR. Beide Ausdrücke müssen absolute, direkte Werte sein. Das Ergebnis ist ebenfalls ein absoluter direkter Wert.

XOR

Bitweises exklusives OR. Beide Ausdrücke müssen absolute, direkte Werte sein. Das Ergebnis ist ebenfalls ein absoluter direkter Wert.

Siehe auch