Extremis
Assembly

Extremis

RPG-Abenteuer geschrieben in RISC-V-Assembly

(Gott, wer hat .iris so programmiert, dass es nicht committet werden kann?)

Ein textbasiertes RPG, geschrieben in RISC-V 32-Bit-Assembler, simuliert über QEMU.

"Das Universum hat entschieden, dass ich nichts Besseres tun soll, wie vielleicht ein Leben zu bekommen."

Abhängigkeiten

  • riscv32-elf-as — RISC-V-Assembler
  • riscv32-elf-ld — RISC-V-Linker
  • qemu-riscv32 — RISC-V-Benutzermodus-Emulator

Auf Arch Linux:

sudo pacman -S riscv64-elf-binutils qemu-user

Bauen & Ausführen

make build   # assemblieren und zu main.bin linken
make run     # bauen + ausführen über qemu-riscv32
make debug   # ausführen mit -strace zur Inspektion von Syscalls

Projektstruktur

Extremis/
├── main.s                      # Einstiegspunkt (_start), ruft run_chapter_1 auf
├── src/
│   ├── include/
│   │   └── function.s          # startF / endF Stack-Frame-Makros
│   ├── engine/
│   │   ├── print.s             # print  — gibt einen einzelnen nullterminierten String aus (a0)
│   │   └── utils.s             # printl — gibt eine Liste von Strings aus (a0=addr Tabelle, a1=Anzahl)
│   ├── chapters/
│   │   └── chapter_1.s         # Kapitel 1 Logik; lädt Intro-Strings und ruft printl auf
│   └── dialogue/
│       └── intro.s             # String-Daten für die Intro-Sequenz
└── build/                      # Kompilierte .o-Dateien (spiegelt src/-Struktur wider)

Architektur

Die Engine ist reiner RISC-V 32-Bit-Assembler, der über QEMU-Benutzermodus-Emulation auf das Linux-ABI abzielt.

Stack-Frame-Konvention

Jede Funktion verwendet die Makros startF / endF aus src/include/function.s, um ra, s0, s1, s2 auf dem Stack zu sichern und wiederherzustellen:

startF    # push: 16 Bytes allozieren, ra/s0/s1/s2 sichern
...
endF      # pop:  ra/s0/s1/s2 wiederherstellen, 16 Bytes freigeben
ret

Syscalls

Warnung: RARS/MARS-Umgebungsaufrufnummern funktionieren nicht unter qemu-riscv32. Dieses Projekt verwendet Linux-RISC-V-ABI-Syscallnummern.

Die Syscallnummer kommt in a7, aufgerufen mit ecall. Der Rückgabewert kommt in a0 zurück.

Ausgabe

Zweck RARS a7 Linux a7 a0 a1 a2 Hinweise
String ausgeben 4 64 1 (stdout fd) buf addr Länge Linux stoppt NICHT bei Null — Byte-Länge übergeben
Zeichen ausgeben 11 64 1 char buf 1 Zeichen im Speicher speichern, Adresse übergeben
Ganzzahl ausgeben 1 Ganzzahl kein Linux-Äquivalent; zuerst in String umwandeln
Hex-Ganzzahl ausg. 34 Ganzzahl kein Linux-Äquivalent; manuell umwandeln
Unsigned int ausg. 36 Ganzzahl kein Linux-Äquivalent; manuell umwandeln

Eingabe

Zweck RARS a7 Linux a7 a0 a1 a2 Hinweise
String lesen 8 63 0 (stdin fd) buf addr max Bytes Linux gibt rohe Bytes inkl. Zeilenumbruch zurück
Zeichen lesen 12 63 0 buf addr 1 gibt a0 = gelesene Bytes zurück; Zeichen in buf
Ganzzahl les. 5 kein Linux-Äquivalent; String lesen, parsen

Prozess

Zweck RARS a7 Linux a7 a0 Hinweise
Beenden 10 93 RARS ignoriert a0; Linux liest es als Exit-Status
Beenden(Code) 17 93 Exit-Code Linux exit_group

Speicher

Zweck RARS a7 Linux a7 a0 Rückgabe Hinweise
Allozieren (sbrk) 9 214 Bytes (RARS) / neue brk-Adresse (Linux) a0 = Start des allozierten Blocks Linux brk funktioniert anders — man setzt die neue Spitze, nicht eine Größe

Datei-E/A

Zweck RARS a7 Linux a7 a0 a1 a2 a3 Hinweise
Datei öffnen 13 56 dirfd (-100=CWD) Dateiname addr Flags Modus Linux verwendet openat
Datei lesen 14 63 fd buf addr max Bytes gibt gelesene Bytes zurück
Datei schreiben 15 64 fd buf addr Byte-Anzahl gibt geschriebene Bytes zurück
Datei schließen 16 57 fd gibt 0 bei Erfolg zurück

Zeit

Zweck RARS a7 Linux a7 a0 a1 Hinweise
Zeit 30 113 clock id (1=REALTIME) timespec* buf RARS gibt geteilt lo/hi in a0/a1 zurück; Linux schreibt Struct in buf
Schlafen 32 115 clock id timespec* RARS nimmt Millisekunden in a0; Linux verwendet clock_nanosleep mit einem Struct

Inhalte hinzufügen

  • Neue Dialog-Strings kommen in src/dialogue/.
  • Neue Kapitel kommen in src/chapters/ und werden mit .include in die Kapiteldatei eingebunden, die sie benötigt.
  • Das Makefile erkennt automatisch alle .s-Dateien unter src/ (außer src/include/).