Extremis
Assembly

Extremis

Przygoda RPG napisana w asemblerze RISC-V

GitHub|

( boże, kto zaprogramował .iris, żeby nie można było go zcommitować? )

Tekstowa gra RPG napisana w asemblerze RISC-V 32-bit, symulowana przez QEMU.

"Wszechświat zdecydował, że nie chce, żebym robił coś lepszego, na przykład może znalazł sobie życie."

Zależności

  • riscv32-elf-as — asembler RISC-V
  • riscv32-elf-ld — linker RISC-V
  • qemu-riscv32 — emulator trybu użytkownika RISC-V

W Arch Linux:

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

Budowanie i uruchamianie

make build   # asembluj i linkuj do main.bin
make run     # buduj + uruchom przez qemu-riscv32
make debug   # uruchom z -strace, aby sprawdzić wywołania systemowe

Struktura projektu

Extremis/
├── main.s                      # Punkt wejścia (_start), wywołuje run_chapter_1
├── src/
│   ├── include/
│   │   └── function.s          # Makra ramki stosu startF / endF
│   ├── engine/
│   │   ├── print.s             # print — wypisz pojedynczy napis zakończony nullem (a0)
│   │   └── utils.s             # printl — wypisz listę napisów (a0=adres tablicy, a1=liczba)
│   ├── chapters/
│   │   └── chapter_1.s         # Logika rozdziału 1; ładuje napisy intro i wywołuje printl
│   └── dialogue/
│       └── intro.s             # Dane napisów dla sekwencji intro
└── build/                      # Skompilowane pliki .o (odzwierciedla strukturę src/)

Architektura

Silnik to czysty asembler RISC-V 32-bit, korzystający z ABI Linuksa przez emulację trybu użytkownika QEMU.

Konwencja ramki stosu

Każda funkcja używa makr startF / endF z src/include/function.s, aby zapisać i przywrócić ra, s0, s1, s2 na stosie:

startF    # push: zaalokuj 16 bajtów, zapisz ra/s0/s1/s2
...
endF      # pop: przywróć ra/s0/s1/s2, zwolnij 16 bajtów
ret

Wywołania systemowe

Ostrzeżenie: Numery wywołań środowiska RARS/MARS nie działają pod qemu-riscv32. Ten projekt używa numerów wywołań systemowych ABI Linuksa RISC-V.

Numer wywołania systemowego umieszcza się w a7, wywołuje przez ecall. Wartość zwracana trafia do a0.

Wyjście

Zamiar RARS a7 Linux a7 a0 a1 a2 Uwagi
Wypisz napis 4 64 1 (fd stdout) adres buf długość Linux NIE zatrzymuje się na nullu — podaj długość w bajtach
Wypisz znak 11 64 1 buf znaku 1 zapisz znak w pamięci, przekaż jego adres
Wypisz liczbę 1 — liczba — — brak odpowiednika w Linuxie; najpierw konwertuj na napis
Wypisz hex 34 — liczba — — brak odpowiednika w Linuxie; konwertuj ręcznie
Wypisz unsigned 36 — liczba — — brak odpowiednika w Linuxie; konwertuj ręcznie

Wejście

Zamiar RARS a7 Linux a7 a0 a1 a2 Uwagi
Czytaj napis 8 63 0 (fd stdin) adres buf maks. bajtów Linux zwraca surowe bajty, włączając znak nowej linii
Czytaj znak 12 63 0 adres buf 1 zwraca a0 = liczba przeczytanych bajtów; znak jest w buf
Czytaj liczbę 5 — — — — brak odpowiednika w Linuxie; czytaj napis, parsuj go

Proces

Zamiar RARS a7 Linux a7 a0 Uwagi
Wyjście 10 93 — RARS ignoruje a0; Linux czyta go jako kod wyjścia
Wyjście(kod) 17 93 kod wyjścia Linux exit_group

Pamięć

Zamiar RARS a7 Linux a7 a0 Zwraca Uwagi
Alokuj (sbrk) 9 214 bajty (RARS) / nowy adres brk (Linux) a0 = początek zaalokowanego bloku Linux brk działa inaczej — ustawiasz nowy szczyt, nie rozmiar

Operacje na plikach

Zamiar RARS a7 Linux a7 a0 a1 a2 a3 Uwagi
Otwórz plik 13 56 dirfd (-100=bieżący katalog) adres nazwy pliku flagi tryb Linux używa openat
Czytaj plik 14 63 fd adres buf maks. bajtów — zwraca liczbę przeczytanych bajtów
Zapisz plik 15 64 fd adres buf liczba bajtów — zwraca liczbę zapisanych bajtów
Zamknij plik 16 57 fd — — — zwraca 0 przy sukcesie

Czas

Zamiar RARS a7 Linux a7 a0 a1 Uwagi
Czas 30 113 id zegara (1=RZECZYWISTY) timespec* buf RARS zwraca podzielone lo/hi w a0/a1; Linux zapisuje strukturę do buf
Uśpij 32 115 id zegara timespec* RARS przyjmuje milisekundy w a0; Linux używa clock_nanosleep ze strukturą

Dodawanie treści

  • Nowe napisy dialogowe umieszczaj w src/dialogue/.
  • Nowe rozdziały umieszczaj w src/chapters/ i dołączaj przez .include w pliku rozdziału, który ich potrzebuje.
  • Makefile automatycznie znajduje wszystkie pliki .s w src/ (z wyłączeniem src/include/).