Eingabe lesen
read (Nr. 0) liest von einem Dateideskriptor (0 = Tastatur/stdin). Um die Beispiele reproduzierbar zu halten, lesen wir hier aus einer Datei, die wir selbst anlegen:
section .data
pfad db "/tmp/asm_demo.txt", 0
inhalt db "Hallo aus einer Datei", 10
laenge equ $ - inhalt
section .bss
puffer resb 64
zeile resb 32
section .text
global _start
_start:
; open(pfad, O_WRONLY|O_CREAT|O_TRUNC, 0644)
mov rax, 2
mov rdi, pfad
mov rsi, 0x241
mov rdx, 0644o
syscall
mov r12, rax ; Dateideskriptor merken
; write(fd, inhalt, laenge)
mov rax, 1
mov rdi, r12
mov rsi, inhalt
mov rdx, laenge
syscall
; close(fd)
mov rax, 3
mov rdi, r12
syscall
; open(pfad, O_RDONLY)
mov rax, 2
mov rdi, pfad
xor rsi, rsi
xor rdx, rdx
syscall
mov r12, rax
; read(fd, puffer, 64)
xor rax, rax
mov rdi, r12
mov rsi, puffer
mov rdx, 64
syscall
mov r13, rax ; Anzahl gelesener Bytes
; Ausgabe auf stdout
mov rax, 1
mov rdi, 1
mov rsi, puffer
mov rdx, r13
syscall
; close und unlink (Datei löschen)
mov rax, 3
mov rdi, r12
syscall
mov rax, 87 ; unlink
mov rdi, pfad
syscall
mov rax, 60
xor rdi, rdi
syscallAusgabe
Hallo aus einer Datei
Fehler melden Systemaufrufe über negative Rückgabewerte in rax (-ENOENT usw.).
Kommandozeilenargumente und Umgebung
Beim Start eines Programms liegt auf dem Stack: argc, dann die Zeiger argv[0], argv[1] ..., ein Nullzeiger und die Umgebungsvariablen:
section .bss
puffer resb 32
section .text
global _start
_start:
mov rax, [rsp] ; argc (Anzahl der Argumente)
call ausgabe_zahl
mov rbx, [rsp + 8] ; argv[0] = Programmname
xor rcx, rcx
.laenge:
cmp byte [rbx + rcx], 0
je .fertig
inc rcx
jmp .laenge
.fertig:
mov rax, rcx
call ausgabe_zahl ; Länge des Programmnamens (./prog → 6)
mov rax, 60
xor rdi, rdi
syscall
; --- Hilfsroutine: gibt die Zahl in RAX dezimal aus (mit Zeilenumbruch), alle Register bleiben erhalten ---
ausgabe_zahl:
push rax
push rbx
push rcx
push rdx
push rsi
push rdi
mov rcx, puffer + 20 ; Ende des Puffers
mov byte [rcx], 10 ; Zeilenumbruch ans Ende
mov rbx, 10
.schleife:
xor rdx, rdx
div rbx ; RAX = RAX / 10, RDX = Rest
add dl, '0' ; Ziffer in ASCII umwandeln
dec rcx
mov [rcx], dl
test rax, rax
jnz .schleife
mov rax, 1 ; write
mov rdi, 1
mov rsi, rcx
lea rdx, [puffer + 21]
sub rdx, rcx
syscall
pop rdi
pop rsi
pop rdx
pop rcx
pop rbx
pop rax
retAusgabe
1 6
Wichtige Linux-Systemaufrufe
| Nr. | Name | Zweck |
|---|---|---|
| 0 | read | lesen |
| 1 | write | schreiben |
| 2 | open | Datei öffnen |
| 3 | close | schließen |
| 9 | mmap | Speicher anfordern |
| 39 | getpid | Prozess-ID |
| 57 | fork | Prozess kopieren |
| 59 | execve | Programm starten |
| 60 | exit | beenden |
| 87 | unlink | Datei löschen |
| 35 | nanosleep | warten |
Die vollständige Liste: man 2 syscalls, /usr/include/asm/unistd_64.h.
Programme analysieren (Reverse Engineering)
Assembler ist das Werkzeug, um fertige Programme zu verstehen:
objdump -d -M intel programm | less # Disassemblieren
gdb ./programm
(gdb) break _start # Haltepunkt
(gdb) run
(gdb) stepi # einen Befehl ausführen
(gdb) info registers # Register ansehen
(gdb) x/8gx $rsp # Speicher ab dem Stack anzeigen (8 Wörter)
(gdb) x/s 0x402000 # Zeichenkette an Adresse anzeigen
(gdb) disassembleWeitere Werkzeuge: Ghidra (kostenloser Dekompilierer der NSA), radare2/Cutter, IDA Free, strace (zeigt Systemaufrufe eines Programms), ltrace, readelf, nm, strings.
strace ./hallo # Systemaufrufe live: write(1, "Hallo, Welt!\n", 13) = 13Compiler zeigen ihren Assembler-Code mit gcc -S -O2 -masm=intel datei.c und im Browser mit Compiler Explorer (godbolt.org).
Merke
- Systemaufrufe sind die Schnittstelle zum Betriebssystem:
read,write,open,close,exit - Fehler stehen als negative Werte in
rax - Beim Start liegen
argc,argvund die Umgebung auf dem Stack - Zum Analysieren:
objdump,gdb,strace, Ghidra, Compiler Explorer
Aufgabe
Schreibe ein Programm, das mit getpid (Nr. 39) die Prozess-ID abfragt und prüft, dass sie größer als 0 ist.