Früher:
Ein aktiver Prozess, der Zugriff auf alles hat (DOS, DOS-Spiele)
Direkter Hardware-Zugriff durch die Prozesse
Heute:
Mehrere Prozesse, die (quasi-)parallel arbeiten
Betriebssystem muss Hardware-Zugriff der Prozesse abstrahieren und verwalten
(Mehr Details in der Veranstaltung Betriebs- und Kommunikationssysteme, dem 2. Teil des Moduls)
Programmfluss des aktuellen Prozesses wird unterbrochen
Betriebssystem wir mitgeteilt, dass ein Syscall des Prozesses ausgeführt wurde
Betriebssystem führt die Funktionalität aus
Prozess wird fortgesetzt
Wollen ein kleines Programm schreiben, welches nicht mit dem gcc
kompiliert werden muss
section .text
global _start ; Wird ein Prozess gestartet, wird bei
_start: ; _start mit der Ausführung begonnen
ret
Wir müssen weiterhin zuerst assemblieren
nasm -f elf64 test.asm
Die entstehende o
-Datei muss ausführbargemacht werden (Linken)
ld -o test test.o
Beim Ausführen stürzt das Programm ab
Wir gucken uns an, an welche Adresse im Code er denn springen möchte
section .text
global _start
_start:
pop rax
ret
Angucken im Debugger:
gdb test # Debugger mit unserem Programm starten
break _start # Beim Erreichen der Marke _start stoppen
run # Programm starten
stepi # Nächste Instruktion (pop rax) ausführen
print $rax # Wert von rax ausgeben
quit # Debugger beenden
Ergebnis: Er will an Adresse 1
Code ausführen, dort liegt aber kein Code
Dies ist offenbar nicht der richtige Weg das Programm zu beenden
Weglassen von ret
führt auch zu Absturz
Lösung: Wir müssen dem Betriebssystem mitteilen, dass wir fertig sind, damit das Betriebssystem den Prozess entfernt
Dafür benötigen wir einen Syscall
Grundlegend ist die Benutzung nicht (viel) anders als bei normalen Funktionsaufrufen
Funktionsparameter werden in den Registern rdi
, rsi
, rdx
, r10
, r8
und r9
übergeben
Unterschied: Statt call funktionsname
muss die Nummer des gewünschten Syscalls in rax
geschrieben und danach der Befehl syscall
verwendet werden
Hier findet sich eine Liste von Syscalls für 64-bit-Linux
Dort finden wir den Syscall sys_exit
mit der Nummer 60
und einem Parameter
section .text
global _start
_start:
mov rax, 60 ; Syscall sys_exit verwenden
mov rdi, 0 ; Programm war erfolgreich
syscall ; Betriebssystem Bescheid geben
Zum lesen aus und schreiben in „Dateien“ gibt es sys_read
und sys_write
, entsprechend den C-Funktionen read und write
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);
Der erste Parameter ist ein sogenannter Dateideskriptor (englisch file descriptor), eine Zahl die eine geöffnete Datei eindeutig referenziert
Es gibt drei Standard-Deskriptoren:
0
für die Standardeingabe (dort kann ich als Benutzer nach Start des Programms etwas eintippen)
1
für die Standardausgabe (dort landet alles, was ihr z.b. auch per printf
ausgegeben habt)
2
für die Fehlerausgabe, für uns nicht so wichtig und fast das gleiche wie 1