3个版本
0.1.2 | 2024年1月29日 |
---|---|
0.1.1 | 2024年1月25日 |
0.1.0 | 2024年1月25日 |
#64 在 模拟器
每月 22次下载
2MB
13K SLoC
梯形
梯形 是一个从头开始构建的 PSX/PS1 模拟器,使用 Rust 构建。
这是一个为了娱乐并体验模拟硬件以及将它们连接在一起的个人项目。
展示
构建和安装
安装
您可以使用 trapezoid
从 crates.io
使用 cargo
安装。
cargo install trapezoid
构建
如果您想体验最新的开发版本,您可以自己构建 trapezoid
。
cargo build --release
没有优化,模拟器会运行得很慢,这就是为什么我们在
debug
配置文件中有opt-level = 2
。
模拟器核心
模拟器核心作为库实现,位于 trapezoid-core
,这个库是模拟器核心,包含所有组件。您可以将核心提取出来,围绕它构建前端,或者将其用作服务器。
有关更多信息,请查看 trapezoid-core
的文档。
前端
控制
前端实现有自己的控制映射,如果您决定直接使用 trapezoid-core
,则可以配置它。
键盘
键盘 | PSX控制器 |
---|---|
Enter | Start |
Backspace | Select |
Num1 | L1 |
Num2 | L2 |
Num3 | L3 |
Num0 | R1 |
Num9 | R2 |
Num8 | R3 |
W | Up |
S | Down |
D | Right |
A | Left |
I | Triangle |
K | X |
L | Circle |
J | Square |
调试
trapezoid
内置了一个强大的调试器,可以帮助调试游戏并访问数据。
这是一个基于CLI的调试器,可以通过按下 / (正斜杠)
键激活,它将暂停模拟并激活调试器。
您将得到一个提示
CPU>
调试器使用 rustyline
并具有自动完成功能
调试器寻址和变量
在任何使用术语 <addr>
的地方,它可以是十六进制地址,也可以是变量名。
有两种变量类型
- 以
$
开头的是寄存器,例如$t0
是寄存器t0
等... - 以
@
开头的是特殊硬件寄存器,如@TIMER0_TARGET
,它是定时器 0 目标寄存器。
您可以使用缩写补全了解这些寄存器。只需开始输入 $
或 @
然后按 tab 键。
调试器命令
h
打印帮助信息
CPU> h
h - help
r - print registers
c - continue
s - step
so - step-over
su - step-out
tt - enable trace
tf - disbale trace
stack [0xn] - print stack [n entries in hex]
bt/[limit] - print backtrace [top `limit` entries]
b <addr> - set breakpoint
rb <addr> - remove breakpoint
bw <addr> - set write breakpoint
rbw <addr> - remove write breakpoint
br <addr> - set read breakpoint
rbr <addr> - remove read breakpoint
lb - list breakpoints
m[32/16/8] <addr> - print content of memory (default u32)
p <addr>/<$reg> - print address or register value
i/[n] [addr] - disassemble instructions
hook_add <cmd[;cmd]> - add hook/s commands
hook_clear - clear all hooks
hook_list - list all hooks
hook_setting [<break_type>[=true/false]] - change when the hooks are executed
r
打印寄存器(例如来自随机游戏中的随机点)
CPU> r
Registers:
pc: 8004A648 at: 80060000
hi: 00000000 lo: 009941F4
v0: 00003178 s0: 54042275
v1: FFFFFFFF s1: 0000015B
a0: 00003179 s2: 0000008F
a1: 00008000 s3: 00000000
a2: 00000000 s4: 00000002
a3: 00000000 s5: 00000000
t0: 39937A40 s6: 00000000
t1: 00000000 s7: 00000000
t2: 00000000 t8: 00000000
t3: F9A700FE t9: 801FFEE0
t4: 0000F159 k0: 8004A600
t5: 801A1D9C k1: 00006418
t6: 00000001 gp: 8005F17C
t7: 00000003 sp: 801FFE78
fp: 801FFFF8 ra: 8004A540
c
继续仿真
s
执行一条指令然后停止
so
执行一条指令然后停止,如果指令是函数调用,它将执行函数并在调用后的下一条指令处停止。
例如,如果代码是这样的
0x1000: jal 0x8004A648
0x1004: _nop ; delay slot
0x1008: nop
并且程序计数器(PC)位于 0x1000
,那么 so
将执行 jal
并在 0x1008
处停止。
su
将仿真继续到当前函数返回。
它将在函数调用后的下一条指令处停止。
tt
启用跟踪,这将打印执行指令,由于它打印所有指令,因此会减慢仿真速度。
示例输出
CPU> tt
Instruction trace: true
CPU> c
80000080: lui k0, 0x0000
80000084: addiu k0, k0, 0x0C80
80000088: jr k0
8000008C: _nop
00000C80: nop
00000C84: nop
00000C88: addiu k0, zero, 0x0100
00000C8C: lw k0, 0x0008(k0)
00000C90: nop
00000C94: lw k0, 0x0000(k0)
00000C98: nop
...
tf
禁用跟踪
stack
打印栈内容,您可以指定要打印的条目数,默认为 10
CPU> stack
Stack: SP=0x801FFC90
8001273C
8001273C
00000002
00000000
00000000
800C82AC
00000012
00000001
00000000
800143AC
bt
打印回溯,您可以指定要打印的条目数,默认为整个回溯
例如,我们目前在回溯的 59
级别,但我们只打印前 10 个条目
CPU> bt/10
#59: 80012E24
#58: 000019B8
#57: 00000E28
#56: 8004AAB0
#55: 8004A888
#54: 8004AAB0
#53: 8004A888
#52: 000019B8
#51: 00000E28
#50: 000019B8
这里显示的地址是返回地址,例如,查看第一个 80012E24
,让我们打印它之前的 2 条指令。
我们将获得调用指令,延迟槽,以及回溯中的返回地址。
CPU> i 80012E1C
0x80012E1C: jal 0x0004AF1 => 0x80012BC4
0x80012E20: _addiu a0, zero, 0xFFFF
0x80012E24: lui v0, 0x8006
这意味着我们现在位于函数 0x80012BC4
内。
b
在地址上设置断点,地址为十六进制,0x
前缀是可选的。这将触发当执行地址时。
CPU> b 80012E24
Breakpoint added: 0x80012E24
rb
移除断点
CPU> rb 80012E24
Breakpoint removed: 0x80012E24
bw
在地址上设置写入断点,地址为十六进制,0x
前缀是可选的。这将触发当写入地址时。
CPU> bw 80012E24
Write breakpoint added: 0x80012E24
rbw
移除写入断点
CPU> rbw 80012E24
Write breakpoint removed: 0x80012E24
br
在地址上设置读取断点,地址为十六进制,0x
前缀是可选的。这将触发当从地址读取时(也执行,因为我们正在从该地址读取)
CPU> br 80012E24
Read breakpoint added: 0x80012E24
rbr
移除读取断点
CPU> rbr 80012E24
Read breakpoint removed: 0x80012E24
lb
列出所有断点
CPU> lb
Breakpoint: 0x80012E24
Write Breakpoint: 0x80012E24
Read Breakpoint: 0x80012E24
m
打印内存内容,您可以指定读取的大小和读取次数,默认为 1 u32
CPU> m 80012E24
0x80012E24: 0x3C028006
CPU> m32 80012E24
0x80012E24: 0x3C028006
CPU> m32/4 80012E24
0x80012E24: 0x3C028006
0x80012E28: 0x8C427FB0
0x80012E2C: 0x00000000
0x80012E30: 0x1440FFED
CPU> m8/4 80012E24
0x80012E24: 0x06
0x80012E25: 0x80
0x80012E26: 0x02
0x80012E27: 0x3C
CPU> m16/4 80012E24
0x80012E24: 0x8006
0x80012E26: 0x3C02
0x80012E28: 0x7FB0
0x80012E2A: 0x8C42
CPU> m @GPU_STAT ; reading gpu status register easily
0x1F801814: 0x5404220A
p
打印寄存器或内存地址的值
这仅对 CPU 寄存器有用,至少目前是这样,没有表达式求值
CPU> p $t0
0x00005688
CPU> p @GPU_STAT
0x1F801814
CPU> p 12345678
0x12345678
i
反汇编指令,您可以指定要反汇编的指令数,默认为 1 条在当前程序计数器(PC)位置
CPU> i
0x80000084: addiu k0, k0, 0x0C80
CPU> i/10
0x80000084: addiu k0, k0, 0x0C80
0x80000088: jr k0
0x8000008C: _nop
0x80000090: nop
0x80000094: nop
0x80000098: nop
0x8000009C: nop
0x800000A0: lui t0, 0x0000
0x800000A4: addiu t0, t0, 0x05C4
0x800000A8: jr t0
CPU> i 800000A0
0x800000A0: lui t0, 0x0000
钩子
调试器允许创建 钩子
,这些是命令,任何上述命令都可以在特定事件上执行。可以通过使用 hook_setting
命令来配置这些事件。
CPU> hook_setting
Hooks will be executed on the following breakpoints:
step: false
step_over: false
step_out: false
instruction_breakpoint: false
read_breakpoint: false
write_breakpoint: false
默认情况下,钩子没有绑定到任何事件。
但是可以使用 hook_setting
来设置它们何时执行。
CPU> hook_setting step,instruction_breakpoint=true,step_out=false
Hooks will be executed on the following breakpoints:
step: true
step_over: false
step_out: false
instruction_breakpoint: true
read_breakpoint: false
write_breakpoint: false
这将使钩子在 step
和 instruction_breakpoint
事件上启用,并在 step_out
事件上禁用,其余保持不变。
hook_add
我们可以通过 hook_add
添加钩子,它将在事件触发时执行。
CPU> hook_add r;i/20
Hook added: r
Hook added: i/20
这将在事件上添加两个要执行的命令,r
和 i/20
,r
将打印寄存器,而 i/20
将从 PC
汇编 20 条指令。
hook_clear
清除所有钩子
hook_list
列出所有钩子
CPU> hook_list
r
i/20
VRAM
我们可以查看原始 VRAM 状态,可以将其视为 1024x512 像素的图像。
这可以通过键盘按钮 v
触发。
贡献和待办事项
请查阅 trapezoid-core
获取有关仿真器相关待办事项的更多信息。
还可以查看 问题。
非常感谢任何贡献。谢谢!
许可证
本项目采用 MIT 许可证。
NES 是任天堂公司的一个产品和/或商标。任天堂公司与 Plastic 或其作者没有任何关联。
参考文献
PSX 组件的大部分文档可以在 consoledev 网站上找到。
依赖项
~47–79MB
~1.5M SLoC