Blog

Hinweise zur Implementierung der Systemaufrufschnittstelle

Bernhard Heinloth

2022-05-11

Es hat sich in der Rechnerübung ein von mir nicht erwarteter Stolperstein aufgetan, vor dem ich euch hiermit bewahren will: Wenn ihr in Assembler den Einsprung für die Systemaufrufe baut, also z.B.

und das mit IDT::handle in C++ referenzieren wollt, so müsst ihr das externe Symbol (mit C Linkage!) deklarieren:

und passend dazu irgendwo

Hier wird implizit ein Zeiger auf die Funktionsadresse verwendet, man kann aber auch explizit &syscall_entry schreiben, ist gleichwertig.

Wenn ihr stattdessen jedoch

schreibt, dann behauptet ihr gegenüber eurem Übersetzer, dass dies eine Speicheradresse mit einem void-Pointer (= 8 Byte groß) sei! Ein IDT::handle(0x80, syscall_entry, ... würde dadurch dann die ersten 8 Byte Maschinencode der Funktion als Zieladresse interpretieren, im Beispiel also irgendwas 0x...f8010f (weil swapgs die erste Instruktion ist und den Opcode 0f 01 f8 hat) – ein General Protection Fault ist das Resultat.

Die einzige richtige Lösung bei dieser Deklaration wäre ein Aufruf mittels

Hierbei unbedingt den Adressoperator & berücksichtigen!

Weiterer Hinweis zum Bearbeiten der Aufgabe:

Für die neuen x64 Register r8r15 gibt es im inline-Assembly keine schöne Lösung (nur Umkopieren oder Hacks mit expliziten Registervariablen, was eine GCC Erweiterung ist) – ich würde euch deshalb empfehlen die Syscall-Stubs direkt in NASM zu schreiben.

Man kann da zum Beispiel hervorragend ein generisches Makro (siehe interrupt/handler.asm) für alle Syscalls bauen, dem man als Makroparameter den Syscallnamen und -nummer übergibt:

Das Makro ist dabei nur wenige Zeilen lang.

Natürlich muss man dann noch eine C-Deklaration zu sys_read, sys_write, sys_sleep angeben, und es gibt keinen (mir bekannten) schönen Weg um C enums in NASM einzubauen – aber das braucht ihr gar nicht, es ist für diese Aufgabe absolut in Ordnung wenn hier an zwei Stellen (sowohl im Assembler-Stub als auch im C++-Syscallhandler) die Systemaufrufnummern definiert werden.

Und dieser Ansatz lässt sich auch sehr einfach auf schnelle Systemaufrufe (fast_read, …) erweitern

Zusammengefasst: Bitte seht besser von zu viel Präprozessor-Magie ab, der Code sollte wartbar und verständlich bleiben.

Zurück zur Übersicht