Lasagne

Lasagne

Lernübung

Einführung

Grundlagen

Kommentare

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

Kommentare können an beliebiger Stelle im Programm stehen. Alles, was nach einem ; folgt, ignoriert der Assembler.

Konstanten

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

UNIVERSE equ 42

Konstanten werden einmal ausgewertet, nämlich bei der Definition, und können danach nicht neu definiert werden.

Allzweckregister (GPRs)

In der Assembly-Programmierung verwenden wir normalerweise Register statt Variablen, um Werte zu speichern. Ein CPU-Register ist ein Stück schneller Speicher im Prozessor eines Computers. Die meisten Berechnungen finden in den Registern der CPU statt.

Einige der Register werden für die verschiedensten 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 aufgeführt; dabei reicht n in rn von 8 bis 15: 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 du weniger als 64 Bit verwendest, stammen die angesprochenen Bits normalerweise aus dem unteren Teil des Registers. Die Ausnahme von dieser Regel sind ah, bh, ch und dh: Sie greifen auf die oberen 8 Bit des 16-Bit-Teils des Registers zu.

Veranschaulichung, 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. Wenn du sie nicht erhältst, 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.

Caution

Später im Lehrplan wirst du lernen, wie du Register erhältst. Verwende vorerst nur die Register des zweiten Typs: rax, rcx, rdx, rdi, rsi, r8, r9, r10 und r11.

Befehle

Befehle sind Rechenoperationen, die eine CPU ausführen kann. Sie haben normalerweise die folgende Form:

name destination, source

Der Name des Befehls steht also zuerst, dann mindestens ein Leerzeichen, dann der Ziel-Operand, ein Komma (,) und schließlich ein Quell-Operand. Der Quell-Operand wird von einem Befehl normalerweise nicht verändert, nur der Ziel-Operand.

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

Um einen Wert in einem Register zu speichern, können wir zum Beispiel den Befehl 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 Quell-Operanden, in alle 64 Bit von rax, den Ziel-Operanden des Befehls.

In den meisten Befehlen kannst du konstante Ganzzahlen als Quell-Operanden verwenden (aber nicht als Ziel-Operanden):

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

Beachte, dass das Schreiben in 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 Befehle add, sub und imul verwenden:

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

Funktionen

Befehle werden 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 Befehlen, die die Funktion definieren.
  3. Dem Rücksprungbefehl ret.

Dieser Track verwendet die Aufrufkonvention System V AMD64 ABI, und die ersten sechs ganzzahligen Argumente werden in Registern an eine Funktion übergeben. Die Reihenfolge ist: 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 den Befehl call. Eine Funktion kann überall in derselben Quelldatei aufgerufen werden. Damit eine Funktion auch für andere Quelldateien sichtbar ist, musst du die Direktive global verwenden.

Das folgende Beispiel deklariert 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

Anleitung

In dieser Übung schreibst du etwas Code, der dir hilft, eine großartige Lasagne aus deinem Lieblingskochbuch zu kochen.

Du hast vier Aufgaben, die sich alle mit der Zeit befassen, die du für das Kochen der Lasagne brauchst.

Note

Das sind die Anweisungen, die in diesem Konzept erwähnt werden:

Anweisung Beschreibung
mov a, b kopiert den Inhalt von b nach a
add a, b a = a + b
sub a, b a = a - b
imul a, b a = a * b
call a ruft Funktion a auf
ret kehrt aus einer Funktion zurück

1. Definiere die erwartete Backzeit in Minuten

Definiere die Funktion expected_minutes_in_oven, die keine Parameter entgegennimmt und zurückgibt, wie viele Minuten die Lasagne im Ofen bleiben sollte. Laut Kochbuch beträgt die erwartete Backzeit 40 Minuten:

expected_minutes_in_oven();
// => 40

2. Berechne die verbleibende Backzeit in Minuten

Definiere die Funktion remaining_minutes_in_oven, die die tatsächlichen Minuten, die die Lasagne schon im Ofen war, als Parameter entgegennimmt und zurückgibt, wie viele Minuten die Lasagne noch im Ofen bleiben muss. Dabei gehst du von der erwarteten Backzeit aus der vorherigen Aufgabe aus.

remaining_minutes_in_oven(30);
// => 10

3. Berechne die Zubereitungszeit in Minuten

Definiere die Funktion preparation_time_in_minutes, die die Anzahl der Schichten, die du der Lasagne hinzugefügt hast, als Parameter entgegennimmt und zurückgibt, wie viele Minuten du mit der Zubereitung der Lasagne verbracht hast. Dabei gehst du davon aus, dass jede Schicht 2 Minuten zum Vorbereiten braucht.

preparation_time_in_minutes(2);
// => 4

4. Berechne die verstrichene Zeit in Minuten

Definiere die Funktion elapsed_time_in_minutes, die zwei Parameter entgegennimmt, und zwar in dieser Reihenfolge:

  1. Die Anzahl der Schichten, die du der Lasagne hinzugefügt hast.
  2. Die Anzahl der Minuten, die die Lasagne schon im Ofen war.

Die Funktion sollte zurückgeben, wie viele Minuten du mit dem Kochen der Lasagne verbracht hast. Das ist die Summe aus der Zubereitungszeit in Minuten und der Zeit in Minuten, die die Lasagne bis zu diesem Zeitpunkt im Ofen verbracht hat.

elapsed_time_in_minutes(3, 20);
// => 26
Über GitHub bearbeiten Der Link öffnet sich in einem neuen Fenster oder Tab
x86-64 Assembly Exercism

Bereit, mit Lasagne zu starten?

Melde dich bei Exercism an, um x86-64 Assembly mit 22 Konzepte130 Übungen und echtem menschlichen Mentoring zu lernen und zu meistern, alles kostenlos.