Kernel-Funktionen (Syscalls) über einen Interrupt aufrufen

Der Linux-Kernel hat einige grundlegende Funktionen, die wir in unseren Programmen nutzen können. Diese Funktionen werden „Syscalls“ genannt und durch eine eindeutige und konstanten Nummer zwischen 0 und 255 identifiziert. Syscalls bilden die Schnittstelle zwischen Programmen und dem Linux-Kernel.

Details zu Syscalls finden sich in der Manpage:

$ man syscalls

Linux-Callgate

Diese vom Kernel bereitgestellte Funktionen können aber nicht direkt aufgerufen werden. Sie müssen über das sogenannte „Callgate“ angesprochen werden. Dabei wird die Nummer der jeweiligen Funktion im rax-Register gespeichert und ein Software-Interrupt ausgelöst. Dieser bestimmte Interrupt hat den hexadezimalen Wert 80. Das ist gleichzeitig auch der einzige in NASM verfügbare Interrupt.
Den Kernel-Funktionen können je nach Bedarf noch Werte übergeben werden, die in anderen Registern platziert werden.
Sehen wir uns das folgende Beispiel an:

mov rax, 4
mov rbx, 1
mov rcx, MyString
mov rdx, MyStringLength
int 80h

Dabei wird über rax festgelegt, dass die System-Funktion mit der eindeutigen Nummer 4 aufgerufen werden soll. In diesem Fall ist das ein Aufruf der Funktion write. Diese Funktion schreibt Daten einer bestimmten Länge an ein Ziel. C-Programmierer können mit dieser Signatur etwas anfangen:

ssize_t write( int fd, const void *buf, size_t count );

Die anderen 3 Register sind Parameter für die Funktion. In rbx befindet sich das Ziel des Schreibens. Dabei steht die Konstante 1 für die Standardausgabe, was im Normalfall die Konsole ist. rcx enthält die Daten und rdx die Anzahl der Bytes die geschrieben werden sollen.
Tatsächlich aufgerufen wird diese Funktion erst durch das mit int gekennzeichnete Interrupt mit dem hexadezimalen Wert 80.
Nach dem Aufruf wird die Ausführung nach der Zeile int 80h fortgesetzt.

Ein weiteres Beispiel:

mov rax, 1
mov rbx, 0
int 80h

Die Funktion mit der Nummer 1 nennt sich exit und wird zum Beenden eines Programmes verwendet. Zum Vergleich die Signatur der Funktion in der Programmiersprache C:

void exit( int status );

Diese Funktion nimmt nur einen Parameter, nämlich einen Status-Code. Dieser Code gibt an, ob das Programm ordnungsgemäß beendet wurde. Dabei steht 0 für Erfolg, jeder andere Wert für einen Fehler. Der Status-Code befindet sich im Register rbx.

Ablauf des Aufrufs

Das war eine grobe Beschreibung des Ablaufs eines Syscall-Interrupts. Im Hintergrund geschehen jedoch noch einige Dinge, um die wir uns nicht kümmern müssen.
Kernel-Funktionen befinden sich an einer ganz anderen Stelle im Speicher als unser Assembler-Code. Um eine sichere Rückkehr zu dieser Stelle in der Ausführung zu garantieren, muss die Position gespeichert werden. Deshalb speichert int 80h noch vor dem Aufruf der Kernel-Funktion die Adresse der nachfolgenden Anweisung am Stack.
Ist die Rückkehr gesichert, wird die Nummer der System-Funktion in eine Adresse umgesetzt. Weiß das Programm nun an welcher Stelle die Funktion im Speicher liegt, kann sie vom Kernel ausgeführt werden.
Nach der vollständigen Ausführung der Funktion wird die von int 80h gespeicherte Adresse wieder vom Stack genommen und zu ihr zurückgekehrt. Die Anweisung dafür heißt IRET (Interrupt RETurn) und wird automatisch aufgerufen.
Nun ist der Aufruf der System-Funktion wirklich komplett und es folgen die nächsten Anweisungen in unserem Programm.

FIXME Grafik

Liste der Linux-Syscalls

FIXME /usr/include/asm/unistd_64.h