Hinweis: Diese Inhalte wurden mit Unterstützung von Künstlicher Intelligenz erstellt und redaktionell überprüft (Transparenzhinweis gemäß Art. 50 EU AI Act).
Assembler (ASM) auf Linux¶
Assembler (ASM) ist die niedrigste Abstraktionsebene der Programmierung – hier schreibst du direkt Maschinenbefehle für die CPU. Linux unterstützt Assembler vollständig für alle gängigen Architekturen wie x86-64, ARM, RISC-V und mehr.
📌 Warum Assembler lernen?¶
| Vorteil | Beschreibung |
|---|---|
| Maximale Performance | Keine Overheads durch Compiler-Optimierungen oder Abstraktionen. |
| Hardware-Kontrolle | Direkter Zugriff auf CPU-Features (SIMD, Cache, Register). |
| Verständnis | Lernen, wie Computer wirklich funktionieren. |
| Debugging | Binaries analysieren und Reverse Engineering betreiben. |
| Kernel-Entwicklung | Linux-Kernel und Treiber nutzen Assembler für kritische Pfade. |
| Embedded Systems | Mikrocontroller und Bare-Metal-Programmierung. |
🏗️ Grundlagen¶
CPU-Register (x86-64)¶
| Register | Größe | Beschreibung |
|---|---|---|
rax, rbx, rcx, rdx |
64 Bit | Allgemeine Register (A, B, C, D) |
rsi, rdi |
64 Bit | Source/Destination Index (für syscalls) |
rbp, rsp |
64 Bit | Base Pointer / Stack Pointer |
rip |
64 Bit | Instruction Pointer (aktuelle Adresse) |
r8–r15 |
64 Bit | Zusätzliche Register (x86-64) |
eflags |
32 Bit | Status-Flags (Zero, Carry, Overflow etc.) |
Hinweis: In 32-Bit-Modus heißen die Register
eax,ebxetc. (32 Bit), in 16-Bit-Modusax,bx(16 Bit).
Grundlegende Instruktionen¶
| Kategorie | Instruktion | Beispiel | Beschreibung |
|---|---|---|---|
| Datenübertragung | mov |
mov $42, %rax |
Wert in Register laden |
mov %rbx, %rcx |
Register kopieren | ||
| Arithmetik | add, sub, mul, div |
add $10, %rax |
Addition |
inc %rax, dec %rax |
Inkrement/Dekrement | ||
| Logik | and, or, xor, not |
and $0xFF, %rax |
Bitweise AND |
| Vergleiche | cmp |
cmp %rax, %rbx |
Vergleicht zwei Werte |
test %rax, %rax |
Prüft auf Null | ||
| Sprünge | jmp |
jmp label |
Unbedingter Sprung |
je, jne, jg, jl |
je label |
Sprung wenn gleich (Equal) | |
| Funktionsaufrufe | call |
call function |
Funktion aufrufen |
ret |
ret |
Aus Funktion zurückkehren | |
| Stack-Operationen | push, pop |
push %rax |
Wert auf Stack pushen |
pop %rbx |
Wert vom Stack poppen | ||
| System Calls | syscall |
syscall |
Linux-Kernel aufrufen |
Speicheradressierung¶
| Modus | Syntax | Beispiel | Beschreibung |
|---|---|---|---|
| Direkt | [adresse] |
mov $42, (%rax) |
Speicherzelle an Adresse in %rax |
| Indirekt mit Offset | [register + offset] |
mov $42, 8(%rbp) |
8 Bytes über rbp |
| Indexiert | [base + index] |
mov (%rax, %rbx, 4), %rcx |
rax + rbx*4 |
| Absolute Adresse | adresse |
mov msg, %rsi |
Adresse von msg laden |
📝 Syntax-Varianten¶
AT&T-Syntax (GNU Standard)¶
- Format:
instruktion quell, ziel - Register: Präfix
%(z. B.%rax) - Immediate Werte: Präfix
$(z. B.$42) - Speicheradressen: Klammern (z. B.
(%rax)) - Kommentare:
# Kommentar
Beispiel:
mov $42, %rax # Lade 42 in rax
mov %rax, %rbx # Kopiere rax nach rbx
add $10, %rbx # Addiere 10 zu rbx
Intel-Syntax (NASM/YASM Standard)¶
- Format:
instruktion ziel, quell(umgekehrt zu AT&T!) - Register: Kein Präfix (z. B.
rax) - Immediate Werte: Kein Präfix (z. B.
42) - Speicheradressen: Klammern (z. B.
[rax]) - Kommentare:
; Kommentar
Beispiel:
Vergleich: AT&T vs. Intel¶
| Operation | AT&T | Intel |
|---|---|---|
| Lade 42 in rax | mov $42, %rax |
mov rax, 42 |
| Kopiere rax nach rbx | mov %rax, %rbx |
mov rbx, rax |
| Addiere 10 zu rbx | add $10, %rbx |
add rbx, 10 |
| Speicherzugriff | mov (%rax), %rbx |
mov rbx, [rax] |
🔌 System Calls auf Linux¶
Grundlagen¶
Linux verwendet Software-Interrupts (oder syscall-Instruktion auf x86-64) für Systemaufrufe. Jeder Syscall hat:
- Eine Nummer (z. B. 1 für write)
- Argumente in speziellen Registern
- Ein Rückgabewert in %rax
Syscall-Nummern finden¶
Die Syscall-Nummern sind in den Kernel-Headern definiert:
# x86-64 Syscall-Nummern anzeigen
cat /usr/include/asm/unistd_64.h
# oder online:
# https://filippo.io/linux-syscall-table/
| Syscall | Nummer (x86-64) | Description | Argumente |
|---|---|---|---|
read |
0 | Lesen aus Datei | rax=0, rdi=fd, rsi=buffer, rdx=count |
write |
1 | Schreiben in Datei | rax=1, rdi=fd, rsi=buffer, rdx=count |
open |
2 | Datei öffnen | rax=2, rdi=path, rsi=flags, rdx=mode |
close |
3 | Datei schließen | rax=3, rdi=fd |
exit |
60 | Prozess beenden | rax=60, rdi=status |
fork |
57 | Prozess erzeugen | rax=57 |
execve |
59 | Programm ausführen | rax=59, rdi=path, rsi=argv, rdx=envp |
Syscall-Register (x86-64)¶
| Register | Argument |
|---|---|
rax |
Syscall-Nummer |
rdi |
1. Argument |
rsi |
2. Argument |
rdx |
3. Argument |
r10 |
4. Argument |
r8 |
5. Argument |
r9 |
6. Argument |
rax |
Rückgabewert |
Hinweis: Bei 32-Bit (x86) werden die Register
eax,ebx,ecx,edx,esi,edi,ebpverwendet.
🛠️ Tools für Assembler auf Linux¶
GNU Assembler (as)¶
Der Standard-Assembler unter Linux (Teil von Binutils).
Installation:
Grundlegende Befehle:
| Befehl | Beschreibung |
|---|---|
as [--64] file.s -o file.o |
Assemblieren ( --64 für x86-64) |
ld file.o -o file |
Linken |
as --64 hello.s && ld hello.o -o hello |
Komplett: Assemblieren + Linken |
Linker (ld)¶
Kombiniert Objektdateien (*.o) zu ausführbaren Programmen.
Beispiel:
# Einfaches Programm linken
ld hello.o -o hello
# Mit Startcode (für _start anstatt main)
ld -e _start hello.o -o hello
Disassembler (objdump)¶
Zeigt den Maschinencode als lesbaren Assembler-Code an.
Beispiele:
# Disassemblieren einer Objektdatei
objdump -d hello.o
# Disassemblieren eines Executables
objdump -d ./hello
# Nur die .text-Sektion anzeigen
objdump -d -j .text ./hello
Debugger (gdb)¶
Interaktives Debugging mit Assembler-Ansicht.
Beispiele:
# Programm starten
gdb ./hello
# Disassemblieren der main-Funktion
(gdb) disassemble main
# Schrittweise Ausführung (Assembler-Ebene)
(gdb) layout asm
(gdb) stepi # Single instruction
(gdb) nexti # Next instruction
# Register anzeigen
(gdb) info registers
(gdb) print $rax
# Speicher anzeigen
(gdb) x/10x $rsp # 10 Hex-Werte ab Stack-Pointer
NASM (Netwide Assembler)¶
Unterstützt Intel-Syntax und ist populär für Cross-Platform-Assembler.
Installation:
Beispiel (Intel-Syntax):
; hello.asm
section .data
msg db 'Hello, NASM!', 0xA
len equ $ - msg
section .text
global _start
_start:
mov rax, 1 ; sys_write
mov rdi, 1 ; stdout
mov rsi, msg ; buffer
mov rdx, len ; length
syscall
mov rax, 60 ; sys_exit
xor rdi, rdi ; status 0
syscall
Kompilieren & Linken:
💻 Praktische Beispiele¶
1. Hello World in x86-64 Assembler (AT&T-Syntax)¶
# hello.s
.section .data
msg: .ascii "Hello, Linux ASM!\n"
len = . - msg
.section .text
.global _start
_start:
# write(1, msg, len)
mov $1, %rax # sys_write (1)
mov $1, %rdi # fd (stdout = 1)
lea msg, %rsi # buffer address
mov $len, %rdx # length
syscall
# exit(0)
mov $60, %rax # sys_exit (60)
xor %rdi, %rdi # status = 0
syscall
Kompilieren & Ausführen:
2. Addition von zwei Zahlen¶
# add.s
.section .text
.global _start
_start:
mov $10, %rax # a = 10
mov $20, %rbx # b = 20
add %rbx, %rax # rax = rax + rbx (30)
# exit(30)
mov $60, %rax # sys_exit
mov %rax, %rdi # status = 30
syscall
Testen:
3. Schleife (Zählen von 1 bis 10)¶
# loop.s
.section .text
.global _start
_start:
mov $1, %rcx # Zähler = 1
loop_start:
# Hier könnte man %rcx ausgeben (für Einfachheit weggelassen)
inc %rcx # Zähler++
cmp $10, %rcx # Vergleiche mit 10
jle loop_start # Springe zurück, wenn <= 10
# exit(0)
mov $60, %rax
xor %rdi, %rdi
syscall
4. Inline-Assembler in C¶
// inline_asm.c
#include <stdio.h>
int main() {
int a = 10, b = 20, result;
__asm__ volatile (
"addl %1, %0" // %0 = %0 + %1
: "=r" (result) // Ausgabe: result
: "r" (a), "0" (b) // Eingabe: a, b (b in %0 vorinitialisieren)
);
printf("Result: %d\n", result); // 30
return 0;
}
Kompilieren:
5. Inline-Assembler in Rust (Nightly)¶
// inline_asm.rs
fn main() {
let a: u64 = 10;
let b: u64 = 20;
let mut result: u64;
unsafe {
asm!(
"add {}, {}",
inout("rax") a => result,
in("rbx") b,
);
}
println!("Result: {}", result); // 30
}
Kompilieren (benötigt Nightly Rust):
6. Datei lesen mit Syscalls¶
# read_file.s
.section .data
filename: .ascii "test.txt\0"
buffer: .space 1024
.section .text
.global _start
_start:
# open(filename, O_RDONLY)
mov $2, %rax # sys_open
lea filename, %rdi # filename
xor %rsi, %rsi # flags = 0 (O_RDONLY)
xor %rdx, %rdx # mode = 0
syscall
mov %rax, %r8 # fd in r8 speichern
# read(fd, buffer, 1024)
mov $0, %rax # sys_read
mov %r8, %rdi # fd
lea buffer, %rsi # buffer
mov $1024, %rdx # count
syscall
# write(1, buffer, rax) # rax = gelesene Bytes
mov $1, %rax # sys_write
mov $1, %rdi # stdout
lea buffer, %rsi # buffer
mov %rax, %rdx # count (aus read)
syscall
# close(fd)
mov $3, %rax # sys_close
mov %r8, %rdi # fd
syscall
# exit(0)
mov $60, %rax
xor %rdi, %rdi
syscall
🎯 Architektspezifische Hinweise¶
x86-64¶
- Standard auf modernen Linux-Systemen
- Register: 16 allgemeine Register (
rax–r15) - Syscall:
syscall-Instruktion - ABI: System V AMD64 ABI
ARM (AArch64)¶
- Register:
x0–x30(64 Bit),w0–w30(32 Bit) - Syscall:
svc #0 - Syscall-Nummern:
x8 - Argumente:
x0–x5
Beispiel (ARM64):
// hello-arm.s
.data
msg: .ascii "Hello, ARM64!\n"
len = . - msg
.text
.global _start
_start:
mov x0, #1 // fd (stdout)
ldr x1, =msg // buffer
ldr x2, =len // length
mov x8, #64 // sys_write (64)
svc #0 // syscall
mov x0, #0 // status
mov x8, #93 // sys_exit (93)
svc #0
Kompilieren (Cross-Compiling):
aarch64-linux-gnu-as hello-arm.s -o hello-arm.o
aarch64-linux-gnu-ld hello-arm.o -o hello-arm
qemu-aarch64 ./hello-arm
RISC-V¶
- Register:
x0–x31(64 Bit bei RV64) - Syscall:
ecall - Syscall-Nummern:
a7 - Argumente:
a0–a5
Beispiel (RISC-V):
# hello-riscv.s
.data
msg: .ascii "Hello, RISC-V!\n"
len = . - msg
.text
.global _start
_start:
li a7, 64 # sys_write (64)
li a0, 1 # fd (stdout)
la a1, msg # buffer
li a2, len # length
ecall
li a7, 93 # sys_exit (93)
li a0, 0 # status
ecall
Kompilieren:
riscv64-linux-gnu-as hello-riscv.s -o hello-riscv.o
riscv64-linux-gnu-ld hello-riscv.o -o hello-riscv
qemu-riscv64 ./hello-riscv
🔍 Debugging & Analyse¶
Disassemblieren mit objdump¶
# Vollständiges Disassemblieren
objdump -d ./programm
# Nur eine Funktion anzeigen
objdump -d -j .text ./programm
# Mit Adressen und Symbolen
objdump -d -S ./programm
# ELF-Header anzeigen
objdump -f ./programm
# Alle Sections anzeigen
objdump -h ./programm
GDB für Assembler-Debugging¶
# Programm starten
gdb ./programm
# Breakpoint bei _start setzen
(gdb) break _start
# Programm starten
(gdb) run
# Assembler-Ansicht aktivieren
(gdb) layout asm
# Register anzeigen
(gdb) info registers
(gdb) info all-registers
# Speicher anzeigen
(gdb) x/10i $rip # 10 Instruktionen ab Instruction Pointer
(gdb) x/10x $rsp # 10 Hex-Werte ab Stack-Pointer
# Schrittweise ausführen
(gdb) stepi # Einzelschritt (in Funktion)
(gdb) nexti # Nächste Instruktion (ohne in Funktion)
(gdb) continue # Weiter bis zum nächsten Breakpoint
Radare2 (Alternative zu GDB)¶
# Installieren
sudo apt install radare2
# Programm analysieren
r2 -A ./programm
# Assembler anzeigen
[0x00000000]> pd 20 # 20 Instruktionen anzeigen
# Breakpoint setzen und ausführen
[0x00000000]> db _start
[0x00000000]> dc
strace – System Calls beobachten¶
# Alle System Calls eines Programms anzeigen
strace ./programm
# Nur bestimmte Syscalls anzeigen
strace -e write ./programm
# Statistik über Syscalls
strace -c ./programm
📚 Lernressourcen¶
Bücher¶
- Programming from the Ground Up – Kostenloses x86-Assembler-Buch
- Assembly Language for x86 Processors – Kip R. Irvine
- The Art of Assembly Language – Randy Hyde
Online-Tutorials¶
- OSDev Wiki – x86 Assembly – OS-Entwicklung mit ASM
- ASM Tutorial (TutorialsPoint) – Grundlagen
- NASM Documentation – Offizielle NASM-Doks
- Linux Syscall Reference – Syscall-Tabelle
Videos¶
- LiveOverflow – x86 Assembly – Security & ASM
- MIT 6.004: Computation Structures – Computer-Architektur
- The Primeagen – Learning Assembly – Praktische Einführung
Tools¶
- Godbolt Compiler Explorer – C/C++/Rust in ASM umwandeln
- Compiler Explorer (ARM/RISC-V) – Mehrere Architekturen
- Online Assembler (Jbeale) – Einfacher Online-Assembler
🎯 Best Practices¶
1. Register richtig nutzen¶
rax: Rückgabewert von Funktionen/Syscallsrsp: Stack-Pointer (nie direkt manipulieren, außer in Prolog/Epilog)rbp: Base-Pointer (für Stack-Frames)rdi,rsi,rdx,rcx,r8,r9: Funktionsargumente (x86-64 ABI)- Kalibrieren: Nach Funktionsaufrufen Register sichern, die ändert werden
2. Stack-Frames¶
Ein typischer Funktionsprolog (x86-64):
push %rbp # Alten Base-Pointer sichern
mov %rsp, %rbp # Neuen Base-Pointer setzen
sub $16, %rsp # Platz für lokale Variablen
Funktionsepilog:
mov %rbp, %rsp # Base-Pointer wiederherstellen
pop %rbp # Alten Base-Pointer laden
ret # Zurückspringen
3. Syscalls sicher aufrufen¶
- Immer Register vor Syscalls sichern, die verändert werden
rax,rdi,rsi,rdxwerden oft überschrieben- Beispiel für sicheres Syscall:
4. Fehlerbehandlung¶
- Syscall-Fehler: Negativer Rückgabewert in
rax(errno in-rax) - Fehler prüfen:
5. Performance-Tipps¶
- SIMD nutzen:
mm0–mm7(MMX),xmm0–xmm15(SSE),ymm0–ymm15(AVX) - Cache-optimieren: Daten nah beieinander speichern
- Branch Prediction:
cmov(conditional move) stattjmpfür einfache Bedingungen - Inline: Kritische Code-Pfade in ASM schreiben
🔗 Verwandte Themen¶
- Compiler – Wie Assembler-Code generiert wird
- Rust, C++ & C Integration – Inline-Assembler in Hochsprachen
- Linux-Systemprogrammierung – Syscalls und Kernel-Interaktion
Letzte Aktualisierung: {{ git_revision_date_localized() }}
Hinweis: Diese Inhalte wurden mit Unterstützung von Künstlicher Intelligenz erstellt und redaktionell überprüft (Transparenzhinweis gemäß Art. 50 EU AI Act).