### x86-64 ASM

von TS

Obwohl mit über 15 Jahren schon ein Oldtimer unter den CPUs, hat sich der 80386er in seinen Grundzügen bis heute als Motor der meisten PCs erhalten. Zwar wurde zwischenzeitlich der Befehlssatz (x86) um einige spezielle Vektoroperationen erweitert und der physikalisch addressierbare Speicher auf 64 Gigabyte vergrößert, aber die wesentlichen Eigenschaften wie 4GB logischer Addressraum, nur 8 allgemein verwendbare 32-Bit-Register sowie die vorhandenen Addressierungsmodi und Schutzmechanismen haben sich seitdem nur unwesentlich verändert.

Für größere Server und ähnliche Anwendungen reicht der verfügbare Addreßbereich von 4GB längst nicht mehr aus. Aber auch ein 08/15-PC von der Stange wird wohl noch in diesem Jahrzehnt mit 4GB Hauptspeicher aus seiner Pappschachtel kommen.
Eine x86-kompatible CPU, die die kommenden Anforderungen erfüllen kann, ist mittlerweile schon mehr als überfällig.

## x86-64 - eine hammerharte Sache

AMDs Lösung zu diesem Problem nennt sich x86-64, die erste dazu passende CPU nennt sich (Sledge-)Hammer.
Folgende Neuheiten sollen ihr den durchschlagenden Erfolg bringen:

- ein Befehlssatz, der auf dem der bewährten x86er CPUs basiert
- 16 universelle 64-Bit Ganzzahlregister und 16 128-Bit SSE-Register für Vektoroperationen
- 64 Bit logischer Addreßraum
- insgesamt 16 8-Bit Teilregister

#### Die Universalregister (GPRs)

Im Vergleich zu bisherigen x86-Prozessoren gibt es nicht nur doppelt so viele Ganzzahlregister, sondern sie wurden auch von 32 auf 64 Bit erweitert. Zusätzlich können nun bei allen Registern die unteren 8 addressiert werden. Ein Zugriff auf die unteren 16 Bit ist ebenfalls möglich. Als 32-Bit-Register heißen die neuen Register R8D - R15D, als 64-Bit Register werden sie als R8 - R15 bezeichnet, EAX - ESP werden dabei zu RAX - RSP.
RIP steht übrigens nicht für den Fall, daß die CPU zu heiß wird, sondern ist die 64-Bit-Form des Instruction Pointers EIP.

#### Die Vektorregister (XMM0 - XMM15)

Für die gleichzeitige Verarbeitung mehrerer Daten hat AMD den auch im Pentium4 verwendeten SSE2-Befehlssatz übernommen, der bei x86-64 mit doppelt so vielen Registern als beim Pentium4 arbeiten kann. Die 8 herkömmlichen FPU/MMX-Register sowie deren Befehlssatz gibt es zwar auch noch, aber da sie unverändert geblieben sind und SSE den Großteil deren Funktionalität abdeckt werden sie kaum noch gebraucht.

#### REX - der König unter den Präfixen

Um die zusätzlichen Register in voller Länge nutzen zu können, ist ein neues Präfix-Byte erforderlich. Es wird genau so verwendet wie die bekannten Präfixe 66h und 67h, allerdings handelt es sich gleich um 16 verschiedene Bytes. Die oberen 4 Bit haben den Wert 4 (hex), so daß das Präfix die Opcodes zwischen 40h und 4Fh belegt. Diese wurden bisher für die Ein-Byte INC/DEC-Befehle verwendet, so daß für diese nun die entsprechende 2-Byte-Form verwendet werden muß.
Die unteren 4 Bits dienen jeweils als Schalter:

- das Operand-Size-Bit gibt an, ob die folgende Instruktion 32 oder 64 Bit-Daten verwendet
- das MOD r/m - Bit gibt an, ob für den Quell- bzw. Zieloperand eines der neuen 8 Register verwendet werden soll oder nicht
- das Base - Bit erledigt dasselbe für das Basisregister bei Speicheraddressen der Form [basis + c*index + offset]
- und das Index - Bit bestimmt ob für das Indexregister eines der 8 neuen Register verwendet werden soll oder nicht

Um die unteren 16 Bit zu verwenden wird weiterhin 66h verwendet. 8-Bit-Registeroperationen verwenden andere Opcodes als 16/32/64-Bit-Registeroperationen. Bei diesen dient das REX-Byte dazu, die Zahl der 8-Bit-Register um die unteren 8 Bit von RSI, RDI, RBP, RSP sowie R8 - R15 zu erweitern.

#### Alles ist relativ - nun auch die Speicheraddressen

Die zweite Neuerung im Befehlssatz besteht aus einer neuen Addressierungsart: Bisher gab es die direkte Addressierung mittels einer vom Compiler/Assembler/Linker generierten Konstante, die die Addresse von 00000000h aus angibt sowie die indizierte Addressierung über ein Register, dessen Wert die Addresse von 00000000h aus angibt.
Neu dazu kommt nun auch die Möglichkeit, die Addresse relativ zum Befehlszeiger RIP anzugeben. Statt von 00000000h wird nun von der Position des Befehlszeigers ausgehend die Addresse gebildet. Die Addressen, die dadurch gebildet werden können, liegen zwischen RIP - 2GB und RIP + 2GB, es liegt also ein vorzeichenbehafteter 32-Bit-Offset vor.
Für die relative Addressierung wurde übrigens kein neues Präfix eingeführt, sondern es wurden Befehle dafür verwendet, die im originalen x86-Befehlssatz auf mehrere Arten codiert werden konnten. Wer davon noch nie gehört hat: die Operanden können bei x86 auf 3 verschiedene Arten angegeben werden, entweder als Teil der Instruktion selbst, als MOD r/m - Byte (r/m = register/memory) oder als SIB - Byte (SIB = Scale-Index-Base). Einige Assembler und Compiler nutzten dies aus, um dadurch eine Art Wasserzeichen im kompilierten Code zu speichern.
Der Sinn der relativen Addressierung besteht darin, daß die Addressen dadurch unabhängig von der Position des Programmes im Speicher gültig sind, so daß die bisher bei DLLs notwendige Relozierung entfallen kann.

### Verfügbare Instruktionen -
Wie benutze ich einen Hammer

Bei x86-64 handelt es sich im wesentlichen um einen etwas zurechtgeklopften herkömmlichen x86 - Befehlssatz, welcher standardmäßig 64-Bit-Addressen und 32-Bit-Operanden verwendet. Mit den Präfixen REX, 66h und 67h können diese zu 64 und 16 Bit modifiziert werden. Zusätzlich gibt es noch die separaten x86 - 8-Bit-Instruktionen.
Dadurch vereinfacht sich zwar die Kompatibilität zu x86 erheblich, aber es bringt auch einige Nachteile mit sich. So sind Konstanten als Operanden (=Immediates) und als Speicheroffsets (=Displacements) auf 32 Bit signed beschränkt, bei Verwendung mit 64-Bit - Operanden werden die oberen 32 Bit mit dem Vorzeichen erweitert, der Wertebereich bleibt somit zwischen -2^31 und +2^32. Einzig beim Laden von Konstanten in ein Register per MOV - Befehl kann ein voller 64-Bit-Wert angegeben werden.
Auch können die zusätzlichen 8-Bit-Teilregister nicht mit den Registern AH,BH,CH und DH kombiniert werden, sondern nur mit anderen unteren 8-Bit Registern.

Da standardmäßig zwar mit 32 Bit gerechnet wird, aber mit 64 Bit addressiert wird, setzen 32-Bit-Operationen die oberen 32 Bit des Zieloperanden auf 0. Dies verringert einerseits die Anzahl nötiger Präfixe und Konversionen von 32 Bit nach 64 Bit, verringert aber andererseits die Flexibilität welche durch die Verwendung der unteren 32 Bit allein mäglich wäre. Operationen mit 8 und 16 Bit lassen aber wie gewohnt die restlichen Bits unverändert.

Weitere Unterschiede zwischen x86 und x86-64 sind:

- Stackoperationen arbeiten mit 64 statt 32 Bit, RSP wird deshalb jeweils um 8 statt um 4 Bytes geändert.
- Alle BCD/ASCII - Instruktionen wurden entfernt (hat eh fast keiner mehr benützt)
- LDS, LES, PUSH/POP CS/DS/ES/SS sowie Far-Calls und Far-Jumps wurden entfernt
- PUSHA / POPA wurden entfernt (macht nix, da sie 128 Bytes (!) transferieren müßten statt nur 32 wie bei x86)
- SAHF / LAHF und SALC gibt es auch nicht mehr
- MOVD kann nun auch ein 64-Bit GPR in ein 64-Bit MMX-Register laden

Weiter geht es im zweiten Teil...