Extremis
Assembly

Extremis

Avventura RPG scritta in Assembly RISC-V

(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/).