Extremis
Assembly

Extremis

Avventura RPG scritta in Assembly RISC-V

GitHub|

(dio, chi ha programmato .iris per non poter essere committato?)

Un RPG testuale scritto in assembly RISC-V a 32 bit, simulato tramite QEMU.

"L'universo ha deciso che non voleva che facessi niente di meglio, tipo magari farmi una vita."

Dipendenze

  • riscv32-elf-as — Assemblatore RISC-V
  • riscv32-elf-ld — Linker RISC-V
  • qemu-riscv32 — Emulatore RISC-V in modalità utente

Su Arch Linux:

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

Compilazione ed Esecuzione

make build   # assembla e linka in main.bin
make run     # compila + esegui tramite qemu-riscv32
make debug   # esegui con -strace per ispezionare le syscall

Struttura del Progetto

Extremis/
├── main.s                      # Punto di ingresso (_start), chiama run_chapter_1
├── src/
│   ├── include/
│   │   └── function.s          # Macro startF / endF per stack frame
│   ├── engine/
│   │   ├── print.s             # print  — stampa una singola stringa terminata da null (a0)
│   │   └── utils.s             # printl — stampa una lista di stringhe (a0=indirizzo tabella, a1=conteggio)
│   ├── chapters/
│   │   └── chapter_1.s         # Logica del Capitolo 1; carica le stringhe introduttive e chiama printl
│   └── dialogue/
│       └── intro.s             # Dati delle stringhe per la sequenza introduttiva
└── build/                      # File .o compilati (rispecchia la struttura di src/)

Architettura

Il motore è puro assembly RISC-V a 32 bit che utilizza l'ABI Linux tramite l'emulazione QEMU in modalità utente.

Convenzione dello Stack Frame

Ogni funzione utilizza le macro startF / endF da src/include/function.s per salvare e ripristinare ra, s0, s1, s2 sullo stack:

startF    # push: alloca 16 byte, salva ra/s0/s1/s2
...
endF      # pop:  ripristina ra/s0/s1/s2, dealloca 16 byte
ret

Syscall

Attenzione: I numeri delle chiamate d'ambiente RARS/MARS non funzionano sotto qemu-riscv32. Questo progetto utilizza i numeri di syscall dell'ABI Linux RISC-V.

Il numero di syscall va in a7, si invoca con ecall. Il valore di ritorno torna in a0.

Output

Intento RARS a7 Linux a7 a0 a1 a2 Note
Stampa stringa 4 64 1 (stdout fd) indirizzo buf lunghezza Linux NON si ferma al null — passa la lunghezza in byte
Stampa carattere 11 64 1 buf carattere 1 memorizza il carattere in memoria, passa il suo indirizzo
Stampa intero 1 — intero — — nessun equivalente Linux; convertire in stringa prima
Stampa intero hex 34 — intero — — nessun equivalente Linux; convertire manualmente
Stampa intero unsigned 36 — intero — — nessun equivalente Linux; convertire manualmente

Input

Intento RARS a7 Linux a7 a0 a1 a2 Note
Leggi stringa 8 63 0 (stdin fd) indirizzo buf byte massimi Linux restituisce byte grezzi inclusa la newline
Leggi carattere 12 63 0 indirizzo buf 1 restituisce a0 = byte letti; il carattere è in buf
Leggi intero 5 — — — — nessun equivalente Linux; leggere stringa, analizzarla

Processo

Intento RARS a7 Linux a7 a0 Note
Esci 10 93 — RARS ignora a0; Linux lo legge come stato di uscita
Esci(codice) 17 93 codice uscita Linux exit_group

Memoria

Intento RARS a7 Linux a7 a0 Restituisce Note
Alloca (sbrk) 9 214 byte (RARS) / nuovo indirizzo brk (Linux) a0 = inizio blocco allocato Linux brk funziona diversamente — imposti il nuovo top, non una dimensione

I/O su File

Intento RARS a7 Linux a7 a0 a1 a2 a3 Note
Apri file 13 56 dirfd (-100=CWD) indirizzo nome file flag modalità Linux usa openat
Leggi file 14 63 fd indirizzo buf byte massimi — restituisce byte letti
Scrivi file 15 64 fd indirizzo buf conteggio byte — restituisce byte scritti
Chiudi file 16 57 fd — — — restituisce 0 in caso di successo

Tempo

Intento RARS a7 Linux a7 a0 a1 Note
Tempo 30 113 id clock (1=REALTIME) timespec* buf RARS restituisce lo/hi divisi in a0/a1; Linux scrive la struct in buf
Sleep 32 115 id clock timespec* RARS prende millisecondi in a0; Linux usa clock_nanosleep con una struct

Aggiungere Contenuti

  • Nuove stringhe di dialogo vanno in src/dialogue/.
  • Nuovi capitoli vanno in src/chapters/ e vengono .include'd nel file del capitolo che li necessita.
  • Il Makefile scopre automaticamente tutti i file .s sotto src/ (escludendo src/include/).