我目前正在编写 Gameboy 模拟器,我注意到一些对我来说似乎很奇怪的事情。
我的模拟器正在执行跳转指令
0xCD
,例如CD B6 FF
,但我的理解是跳转只能跳转到盒式ROM中的地址(0x7FFF
最大),因为我假设CPU可以只执行 ROM 中的指令,而不执行 RAM 中的指令。有问题的 ROM 是马里奥博士,我希望它只会执行有效的操作。 0xFFB6
处于高 RAM 中,这对我来说似乎很奇怪。
我的想法正确吗?如果是的话,大概意味着我的程序计数器以某种方式结束在错误的地址,并且
CB
实际上是另一条指令数据的一部分,而不是指令本身?
如果能得到一些澄清,我将不胜感激,谢谢。
作为参考,我一直在使用 Gameboy Opcodes 和 CPU docs 来实现指令。我知道它们包含一些错误,并且我想我已经解释了它们(例如,0xE2 被列为两字节指令,而它只有一个)
OpenAI 可以吃掉我的泥土。我拒绝将我的贡献提供给 OpenAI。
这是我的分析:
在原始ROM中寻找
CD B6 FF
,我只能在内存的一个位置找到它,即0x01C0
(十进制448)。所以我决定拆解ROM,看看它是否是有效的指令。
我使用gb-disasm来反汇编ROM。以下是从
0x150
(ROM 起始)到地址 0x201
的值。
[0x00000100] 0x00 NOP
[0x00000101] 0xC3 0x50 0x01 JP $0150
[0x00000150] 0xC3 0xE8 0x01 JP $01E8
[0x00000153] 0x01 0x0E 0xD0 LD BC,$D00E
[0x00000156] 0x0A LD A,[BC]
[0x00000157] 0xA7 AND A
[0x00000158] 0x20 0x0D JR NZ,$0D ; 0x167
[0x0000015A] 0xF0 0xCF LDH A,[$CF] ; HIMEM
[0x0000015C] 0xFE 0xFE CP $FE
[0x0000015E] 0x20 0x04 JR NZ,$04 ; 0x164
[0x00000160] 0x3E 0x01 LD A,$01
[0x00000162] 0x18 0x01 JR $01 ; 0x165
[0x00000164] 0xAF XOR A
[0x00000165] 0x02 LD [BC],A
[0x00000166] 0xC9 RET
[0x00000167] 0xFA 0x46 0xD0 LD A,[$D046]
[0x0000016A] 0xE0 0x01 LDH [$01],A ; SB
[0x0000016C] 0x18 0xF6 JR $F6 ; 0x164
[0x000001E8] 0xAF XOR A
[0x000001E9] 0x21 0xFF 0xDF LD HL,$DFFF
[0x000001EC] 0x0E 0x10 LD C,$10
[0x000001EE] 0x06 0x00 LD B,$00
[0x000001F0] 0x32 LD [HLD],A
[0x000001F1] 0x05 DEC B
[0x000001F2] 0x20 0xFC JR NZ,$FC ; 0x1F0
[0x000001F4] 0x0D DEC C
[0x000001F5] 0x20 0xF9 JR NZ,$F9 ; 0x1F0
[0x000001F7] 0x3E 0x0D LD A,$0D
[0x000001F9] 0xF3 DI
[0x000001FA] 0xE0 0x0F LDH [$0F],A ; IF
[0x000001FC] 0xE0 0xFF LDH [$FF],A ; IE
[0x000001FE] 0xAF XOR A
[0x000001FF] 0xE0 0x42 LDH [$42],A ; SCY
[0x00000201] 0xE0 0x43 LDH [$43],A ; SCX
0x150
开始。所以我们应该从那里开始拆解。然后我们按照指令进行操作,直到遇到任何 JUMP
指令(JP
、JR
、CALL
、RET
等)。从那一刻起,程序流程一分为二,我们应该沿着两条路径进行反汇编。这里要理解的是,如果我向你展示 ROM 中的随机内存位置,你无法告诉我它是数据还是指令。找出答案的唯一方法是遵循程序流程。我们需要定义从跳转目标开始并以另一个跳转指令结束的代码块。
gb-disasm 会跳过不在代码块内的任何内存位置。
0x16C
标记块的结束。
[0x0000016C] 0x18 0xF6 JR $F6 ; 0x164
下一个区块从
0x1E8
开始。我们知道这是因为它是位于 0x150
上的跳转的目标地址。
[0x00000150] 0xC3 0xE8 0x01 JP $01E8
0x16E
到 0x1E8
的内存块不被视为代码块。这就是为什么您看不到内存位置 0x01C0
作为指令列出。所以,您很可能以错误的方式解释了这些说明。如果你想百分百确定,你可以拆开整个房间,检查是否有任何指令指向
0x16E-0x1E8
并将其读取为原始数据,例如瓷砖或其他东西。
如果您同意分析,请发表评论。