Blog
Hinweise zur Implementierung der Systemaufrufschnittstelle
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 r8
… r15
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:
%macro SYSSTUB 2
[GLOBAL sys_%1]
ALIGN 8
sys_%1:
; ...
%endmacro
SYSSTUB read, 1
SYSSTUB write, 2
SYSSTUB sleep, 3
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.