Assembler-Ausdrücke
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.
Inhaltsverzeichnis
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
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. |