Einstieg in Assembly
/ Aug. 2023, assembly, low-level
Vor einer Weile wurde ich neugierig. So richtig. Wie funktionieren Computer eigentlich, ganz unten? Die Antwort hieß Assembly. Statt mir ein 500-Seiten-Handbuch reinzuziehen, habe ich eine kleine Sammlung simpler Programme gebaut, von denen mir jedes ein anderes Konzept beigebracht hat. Daraus wurde BergerAPI/asm, und dieser Beitrag stellt dir Assembly vor. Keine Angst, wir fangen sachte an.
Voraussetzungen
Bevor du Assembly schreibst, brauchst du (einen Teil von) diesem Werkzeug. Hier geht’s um NASM (Netwide Assembler) auf x86-64-Linux.
-
NASM (Netwide Assembler)
sudo apt-get install nasm -
GNU Binutils (bringt den Linker
ldundobjdumpmit)sudo apt-get install binutils -
GNU Make (auf den meisten Linuxen eh schon da)
sudo apt-get install build-essential -
GDB (optional, aber wärmstens empfohlen)
sudo apt-get install gdb -
gcc (optional, falls du später Assembly mit C mischen willst)
sudo apt-get install gcc
Die Grundlagen
Programmaufbau
Code liegt in Sektionen:
section .datafür initialisierte globale und statische Daten (so was wiechar *msg = "hello";)section .textfür die ausführbaren Anweisungensection .bssfür nicht initialisierte globale und statische Daten
Dazu kommen Labels: Namen für Speicheradressen, mit denen du deinen Code gliederst. Für den Programmeinstieg, für Funktionen oder für Stellen, zu denen du springen oder die du aufrufen willst.
_start:
; instructions
loop:
; instructions
Anweisungen sagen der CPU, was zu tun ist. Sie sehen meist so aus (Dummy-Code, keine Sorge):
mov rax, 1
push ebx
ret
Register
Register speichern Daten und lassen sich mit Anweisungen wie mov und add bearbeiten. Reinschreiben, rauslesen. Mehr ist es im Kern nicht. Es gibt viele davon; hier die wichtigsten:
| Register | Zweck |
|---|---|
| rax | Rückgabewert, Syscall-Nummer, Akkumulator |
| rcx | 4. Argument einer Funktion; Schleifenzähler |
| rdx | 3. Argument einer Funktion |
| rsi | 2. Argument einer Funktion |
| rdi | 1. Argument einer Funktion |
| rsp | Stackpointer (Oberkante des Stacks) |
| r8-r15 | Allzweck |
Der Stack
Der Stack ist ein Speicherbereich für temporäres Zeug: Funktionsparameter, lokale Variablen, so was. Er wächst nach unten: Wer Daten drauflegt (push), sieht den Stackpointer (RSP) sinken.
Mit push und pop hantierst du auf dem Stack herum:
push raxlegt ein Element oben drauf und verringert den Stackpointer um die Größe der Datenpop rbxnimmt das oberste Element runter und schiebt es ins angegebene Register. Der Stackpointer wächst entsprechend wieder.
Die .data-Sektion
Mit section .data legst du initialisierte, beschreibbare globale Daten in dein Binary.
Achtung: Große, nicht initialisierte Puffer gehören nach .bss (resb/resq benutzen). Hält das Binary schlank.
section .data
Grundlegende Direktiven
dbdefiniert Byte(s) (gut für Strings und rohe Bytes)dwdefiniert 2‑Byte-Wörterdddefiniert 4‑Byte-Doppelwörterdqdefiniert 8‑Byte-Quadwörter (gut für 64‑Bit-Zahlen/Pointer)$ist die aktuelle Assemblierungsadresse$ - labelrechnet die Bytes vom angegebenen Label bis zur aktuellen Adresse austimes n db 0wiederholt einen Wert n‑mal
Strings und Längen:
msg: db "Hello, world!", 0x0a ; String mit Newline am Ende
len: equ $ - msg ; Länge wird zur Assemble-Zeit berechnet
Zahlenwerte (Little-Endian beachten):
val32: dd 0x11223344 ; liegt als 44 33 22 11 im Speicher
val64: dq 0x1122334455667788
Arrays:
arr: dq 1, 2, 3, 4 ; Array aus 64-Bit-Integers
zeros: times 8 db 0 ; 8 Nullbytes
Erste Schritte mit Hello World
Dieses Beispiel gibt „Hello World“ auf der Konsole aus und liefert Exit-Code 0 zurück. Also: alles gut.
section .data
msg db "Hello, World!", 0x0a ; Byte-Array mit Text "Hello, World!" plus Newline (0x0a)
len equ $ - msg ; ergibt 14 (Länge von "Hello, World!")
section .text
global _start
_start:
; write-Syscall: rax=1, rdi=fd, rsi=buf, rdx=count
mov rax, 1 ; Syscall write
mov rdi, 1 ; Dateideskriptor (stdout)
mov rsi, msg ; Pointer auf die Nachricht
mov rdx, len ; Nachrichtenlänge
syscall ; write(stdout, message, message_length)
; exit-Syscall: rax=60, rdi=code
mov rax, 60 ; Syscall exit
mov rdi, 0 ; Exit-Code 0 (Erfolg)
syscall