(天啊,谁把.iris编程成无法提交的?)
一款基于RISC-V 32位汇编编写的文本角色扮演游戏,通过QEMU模拟运行。
"宇宙决定不让我做任何更好的事,比如或许找点生活。"
依赖项
riscv32-elf-as— RISC-V 汇编器riscv32-elf-ld— RISC-V 链接器qemu-riscv32— RISC-V 用户模式模拟器
在 Arch Linux 上:
sudo pacman -S riscv64-elf-binutils qemu-user
构建与运行
make build # 汇编并链接成 main.bin
make run # 构建 + 通过 qemu-riscv32 运行
make debug # 使用 -strace 运行以检查系统调用
项目结构
Extremis/
├── main.s # 入口点 (_start),调用 run_chapter_1
├── src/
│ ├── include/
│ │ └── function.s # startF / endF 栈帧宏
│ ├── engine/
│ │ ├── print.s # print — 打印单个以空字符结尾的字符串 (a0)
│ │ └── utils.s # printl — 打印字符串列表 (a0=地址表, a1=数量)
│ ├── chapters/
│ │ └── chapter_1.s # 第一章逻辑;加载开场字符串并调用 printl
│ └── dialogue/
│ └── intro.s # 开场序列的字符串数据
└── build/ # 编译后的 .o 文件(镜像 src/ 布局)
架构
引擎是纯 RISC-V 32 位汇编,通过 QEMU 用户模式模拟针对 Linux ABI。
栈帧约定
每个函数使用 src/include/function.s 中的 startF / endF 宏在栈上保存和恢复 ra、s0、s1、s2:
startF # 压栈:分配 16 字节,保存 ra/s0/s1/s2
...
endF # 出栈:恢复 ra/s0/s1/s2,释放 16 字节
ret
系统调用
警告: RARS/MARS 环境调用号在
qemu-riscv32下不适用。本项目使用 Linux RISC-V ABI 系统调用号。
系统调用号放入 a7,通过 ecall 调用。返回值在 a0 中。
输出
| 意图 | RARS a7 |
Linux a7 |
a0 |
a1 |
a2 |
说明 |
|---|---|---|---|---|---|---|
| 打印字符串 | 4 |
64 |
1 (stdout 文件描述符) |
缓冲区地址 | 长度 | Linux 不会在空字符处停止 — 需传递字节长度 |
| 打印字符 | 11 |
64 |
1 |
字符缓冲区 | 1 |
将字符存入内存,传递其地址 |
| 打印整数 | 1 |
— | 整数 | — | — | 无 Linux 等效项;需先转换为字符串 |
| 打印十六进制整数 | 34 |
— | 整数 | — | — | 无 Linux 等效项;需手动转换 |
| 打印无符号整数 | 36 |
— | 整数 | — | — | 无 Linux 等效项;需手动转换 |
输入
| 意图 | RARS a7 |
Linux a7 |
a0 |
a1 |
a2 |
说明 |
|---|---|---|---|---|---|---|
| 读取字符串 | 8 |
63 |
0 (stdin 文件描述符) |
缓冲区地址 | 最大字节数 | Linux 返回原始字节,包括换行符 |
| 读取字符 | 12 |
63 |
0 |
缓冲区地址 | 1 |
a0 返回读取的字节数;字符在缓冲区中 |
| 读取整数 | 5 |
— | — | — | — | 无 Linux 等效项;读取字符串,解析它 |
进程
| 意图 | RARS a7 |
Linux a7 |
a0 |
说明 |
|---|---|---|---|---|
| 退出 | 10 |
93 |
— | RARS 忽略 a0;Linux 将其作为退出状态读取 |
| 退出(代码) | 17 |
93 |
退出代码 | Linux exit_group |
内存
| 意图 | RARS a7 |
Linux a7 |
a0 |
返回 | 说明 |
|---|---|---|---|---|---|
| 分配 (sbrk) | 9 |
214 |
字节数 (RARS) / 新 brk 地址 (Linux) | a0 = 分配块的起始地址 |
Linux brk 工作方式不同 — 你设置新的顶部,而不是大小 |
文件 I/O
| 意图 | RARS a7 |
Linux a7 |
a0 |
a1 |
a2 |
a3 |
说明 |
|---|---|---|---|---|---|---|---|
| 打开文件 | 13 |
56 |
目录文件描述符 (-100=当前工作目录) |
文件名地址 | 标志 | 模式 | Linux 使用 openat |
| 读取文件 | 14 |
63 |
文件描述符 | 缓冲区地址 | 最大字节数 | — | 返回读取的字节数 |
| 写入文件 | 15 |
64 |
文件描述符 | 缓冲区地址 | 字节数 | — | 返回写入的字节数 |
| 关闭文件 | 16 |
57 |
文件描述符 | — | — | — | 成功时返回 0 |
时间
| 意图 | RARS a7 |
Linux a7 |
a0 |
a1 |
说明 |
|---|---|---|---|---|---|
| 时间 | 30 |
113 |
时钟 ID (1=实时时钟) |
timespec* 缓冲区 |
RARS 在 a0/a1 中返回分离的低/高位;Linux 将结构体写入缓冲区 |
| 睡眠 | 32 |
115 |
时钟 ID | timespec* |
RARS 在 a0 中接收毫秒数;Linux 使用带结构体的 clock_nanosleep |
添加内容
- 新的对话字符串放在
src/dialogue/中。 - 新的章节放在
src/chapters/中,并通过.include引入到需要它们的章节文件中。 - Makefile 自动发现
src/下所有.s文件(排除src/include/)。
