Gr

Grundlagen in x86-64 Assembly

1 Übung

Über Grundlagen

Kommentare

Der x86-64-Track bei Exercism verwendet NASM (The Netwide Assembler) als Assembler. In NASM werden Kommentare durch ein Semikolon (;) eingeleitet.

Kommentare können überall im Programm stehen, und alles, was nach einem ; kommt, wird vom Assembler ignoriert.

Konstanten

Eine Konstante zur Assemblerzeit lässt sich in NASM mit equ definieren. Zum Beispiel definiert dies eine Konstante namens UNIVERSE mit dem Wert 42:

UNIVERSE equ 42

Konstanten werden einmal ausgewertet, wenn sie definiert werden, und können später nicht neu definiert werden.

Allzweckregister (GPRs)

In Assembler verwenden wir zum Speichern von Werten üblicherweise Register statt Variablen. Ein CPU-Register ist ein schneller Speicher im Prozessor eines Computers. Die meisten Berechnungen finden in den Registern der CPU statt.

Einige der Register werden für ganz verschiedene Berechnungen genutzt und heißen Allzweckregister (GPRs). Andere haben spezielle oder fest zugewiesene Aufgaben.

In x86-64 gibt es 16 64-Bit-Allzweckregister (GPRs), auf die auch als 32-Bit-, 16-Bit- oder 8-Bit-Register zugegriffen werden kann. Die GPRs sind unten beschrieben, wobei n in rn von 8 bis 15 reicht: r8, r9, r10, r11, r12, r13, r14 und r15.

64-bit 32-bit 16-bit 8-bit
rax eax ax ah/al
rbx ebx bx bh/bl
rcx ecx cx ch/cl
rdx edx dx dh/dl
rsi esi si sil
rdi edi di dil
rbp ebp bp bpl
rsp esp sp spl
rn rnd rnw rnb

Wenn weniger als 64 Bit verwendet werden, stammen die angesprochenen Bits normalerweise aus dem unteren Teil des Registers. Ausnahmen von dieser Regel sind ah, bh, ch und dh, die auf die oberen 8 Bit des 16-Bit-Teils des Registers zugreifen.

Darstellung, wie auf die Bits des Registers rax zugegriffen wird:

+--------+---------------------------------------+
| 64-bit |                  rax                  |
+--------+-------------------+-------------------+
| 32-bit |                   |        eax        |
+--------+-------------------+---------+---------+
| 16-bit |                             |    ax   |
+--------+-----------------------------+----+----+
| 8-bit  |                             | ah | al |
+--------+-----------------------------+----+----+

Einige dieser Register müssen über Funktionsaufrufe hinweg erhalten bleiben: rbp, rsp, rbx, r12, r13, r14 und r15. Werden sie nicht erhalten, kann das zu einem Fehler oder zu undefiniertem Verhalten führen.

Die übrigen müssen nicht erhalten bleiben und dürfen frei verwendet werden: rax, rcx, rdx, rdi, rsi, r8, r9, r10 und r11.

Note

Register werden üblicherweise mit einer push-Anweisung auf dem Stack gesichert. Vor dem Ende der Funktion werden sie dann mit einer pop-Anweisung wiederhergestellt.

Anweisungen

Anweisungen sind Berechnungsschritte, die eine CPU ausführen kann. Sie haben meist die folgende Form:

name destination, source

Zuerst steht also der Name der Anweisung, dann mindestens ein Leerzeichen, gefolgt vom Zieloperanden, einem Komma (,) und schließlich einem Quelloperanden. Der Quelloperand wird von einer Anweisung normalerweise nicht verändert, nur der Zieloperand.

Beide Operanden müssen dieselbe Größe haben, sofern nicht anders angegeben. Wenn der Quelloperand zum Beispiel 16 Bit hat, muss auch der Zieloperand 16 Bit haben.

Um einen Wert in einem Register zu speichern, können wir zum Beispiel die Anweisung mov verwenden:

mov rax, rdx  ; rax = rdx
              ; mov is the name, rax is the destination operand and rdx is the source operand

Das obige Beispiel kopiert den Inhalt aller 64 Bit von rdx, dem Quelloperanden, in alle 64 Bit von rax, den Zieloperanden der Anweisung.

In den meisten Anweisungen können konstante Ganzzahlen als Quelloperanden verwendet werden (aber nicht als Zieloperanden):

mov rcx, 42 ; rcx = 42
            ; rcx is the destination operand and 42 is the source operand

Beachte, dass ein Schreibzugriff auf ein 32-Bit-Register auch die oberen Bits löscht, mov eax, 42 ist also dasselbe wie mov rax, 42. Für 16-Bit- und 8-Bit-Register gilt das nicht.

Für die Rechenoperationen Addition, Subtraktion und Multiplikation können wir die Anweisungen add, sub und imul verwenden:

add rax, rsi ; rax = rax + rsi
imul rax, rdi ; rax = rax * rdi
sub rax, r8 ; rax = rax - r8

Funktionen

Anweisungen sind in Funktionen organisiert. Alle Funktionen stehen im Abschnitt section .text der Quelldatei.

Eine Funktionsdeklaration besteht aus:

  1. Einem Label mit dem Namen der Funktion, gefolgt von einem :.
  2. Den Anweisungen, die die Funktion definieren.
  3. Der Rücksprung-Anweisung ret.

Es gibt bestimmte Konventionen dafür, wie Argumente an eine Funktion übergeben und von ihr zurückgegeben werden. Die beiden wichtigsten Aufrufkonventionen sind die System V AMD64 ABI und Microsoft x64.

Dieser Track verwendet die Aufrufkonvention System V AMD64 ABI, bei der die ersten sechs ganzzahligen Argumente in Registern an eine Funktion übergeben werden. Sie werden in dieser Reihenfolge übergeben: rdi, rsi, rdx, rcx, r8 und r9.

Ein ganzzahliger Wert wird im Register rax von der Funktion zurückgegeben.

Um eine Funktion aufzurufen, verwenden wir die Anweisung call. Eine Funktion kann überall in derselben Quelldatei aufgerufen werden. Damit eine Funktion für andere Quelldateien sichtbar ist, muss die Direktive global verwendet werden.

Zum Beispiel deklariert dies eine Funktion sum:

section .text ; functions are placed here

global sum ; sum is visible to other source files

sum:
    ; first argument is passed in rdi
    ; second argument is passed in rsi
    ; return value is placed in rax

    mov rax, rdi ; rax is now equal to rdi
    add rax, rsi ; rax = rax + rsi

    ret ; function return

Und dies ruft unsere Funktion sum mit den Argumenten 3 und 5 auf:

mov rdi, 3  ; First argument in rdi
mov rsi, 5  ; Second argument in rsi
call sum

; The rax register now contains the value 8 (3 + 5), after sum returns
Über GitHub bearbeiten Der Link öffnet sich in einem neuen Fenster oder Tab

Lerne Grundlagen