mirror of
https://github.com/JetSetIlly/Gopher2600.git
synced 2025-04-02 11:02:17 -04:00
- RAM command now has CART option. displays any additional RAM the cartridge may have o cartridge - implemented RAM() command. returns copy of RAM array - save/restore banks functions are now save/restore state and deal with cartridge RAM in addition to bank information o debugger/memory - better error messages for peek and poke
187 lines
5 KiB
Go
187 lines
5 KiB
Go
package disassembly
|
|
|
|
import (
|
|
"gopher2600/errors"
|
|
"gopher2600/hardware/cpu"
|
|
"gopher2600/hardware/cpu/definitions"
|
|
"gopher2600/hardware/cpu/result"
|
|
)
|
|
|
|
// flowDisassembly decodes those cartridge addresses that follow the flow from
|
|
// the address pointed to by the reset address of the cartridge.
|
|
//
|
|
// every branch and subroutine is considered. however, it is possible for real
|
|
// execution of the ROM to reach places not considered by the flow disassembly.
|
|
// for example:
|
|
//
|
|
// o addresses stuffed into the stack and RTS being called, without an
|
|
// explicit JSR
|
|
// o branching of jumping to non-cartridge memory. (ie. RAM) and executing
|
|
// code there. self-modifying code.
|
|
|
|
func (dsm *Disassembly) flowDisassembly(mc *cpu.CPU) error {
|
|
for {
|
|
r, err := mc.ExecuteInstruction(func(*result.Instruction) error { return nil })
|
|
|
|
// filter out the predictable errors
|
|
if err != nil {
|
|
switch err := err.(type) {
|
|
case errors.FormattedError:
|
|
switch err.Errno {
|
|
case errors.ProgramCounterCycled:
|
|
// originally, a cycled program counter caused the
|
|
// disassembly to end but thinking about it a bit more,
|
|
// we can see that simply continuing with the loop makes
|
|
// more sense
|
|
continue // for loop
|
|
case errors.UnimplementedInstruction:
|
|
continue // for loop
|
|
case errors.InvalidOpcode:
|
|
continue // for loop
|
|
default:
|
|
return err
|
|
}
|
|
default:
|
|
return err
|
|
}
|
|
}
|
|
|
|
// check validity of instruction result
|
|
err = r.IsValid()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
bank := dsm.Cart.GetBank(r.Address)
|
|
|
|
// if we've seen this before then finish the disassembly
|
|
if dsm.flow[bank][r.Address&disasmMask].IsInstruction() {
|
|
return nil
|
|
}
|
|
|
|
dsm.flow[bank][r.Address&disasmMask] = Entry{
|
|
style: result.StyleDisasm,
|
|
instruction: r.GetString(dsm.Symtable, result.StyleDisasm),
|
|
instructionDefinition: r.Defn}
|
|
|
|
// we've disabled flow-control in the cpu but we still need to pay
|
|
// attention to what's going on or we won't get to see all the areas of
|
|
// the ROM.
|
|
switch r.Defn.Effect {
|
|
|
|
case definitions.Flow:
|
|
if r.Defn.Mnemonic == "JMP" {
|
|
if r.Defn.AddressingMode == definitions.Indirect {
|
|
if r.InstructionData.(uint16) > dsm.Cart.Origin() {
|
|
// note current location
|
|
state := dsm.Cart.SaveState()
|
|
retPC := mc.PC.ToUint16()
|
|
|
|
// adjust program counter
|
|
mc.LoadPCIndirect(r.InstructionData.(uint16))
|
|
|
|
// recurse
|
|
err = dsm.flowDisassembly(mc)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// resume from where we left off
|
|
dsm.Cart.RestoreState(state)
|
|
mc.PC.Load(retPC)
|
|
} else {
|
|
// it's entirely possible for the program to jump
|
|
// outside of cartridge space and run inside RIOT RAM
|
|
// (for instance, test-ane.bin does this).
|
|
//
|
|
// it's difficult to see what we can do in these cases
|
|
// without actually running the program for real (with
|
|
// actual side-effects)
|
|
//
|
|
// for now, we'll just tolerate it
|
|
dsm.selfModifyingCode = true
|
|
}
|
|
} else {
|
|
// absolute JMP addressing
|
|
|
|
// note current location
|
|
state := dsm.Cart.SaveState()
|
|
retPC := mc.PC.ToUint16()
|
|
|
|
// adjust program counter
|
|
mc.PC.Load(r.InstructionData.(uint16))
|
|
|
|
// recurse
|
|
err = dsm.flowDisassembly(mc)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// resume from where we left off
|
|
dsm.Cart.RestoreState(state)
|
|
mc.PC.Load(retPC)
|
|
}
|
|
} else {
|
|
// branch instructions
|
|
|
|
// note current location
|
|
state := dsm.Cart.SaveState()
|
|
retPC := mc.PC.ToUint16()
|
|
|
|
// sign extend address and add to program counter
|
|
address := uint16(r.InstructionData.(uint8))
|
|
if address&0x0080 == 0x0080 {
|
|
address |= 0xff00
|
|
}
|
|
mc.PC.Add(address, false)
|
|
|
|
// recurse
|
|
err = dsm.flowDisassembly(mc)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// resume from where we left off
|
|
dsm.Cart.RestoreState(state)
|
|
mc.PC.Load(retPC)
|
|
}
|
|
|
|
case definitions.Subroutine:
|
|
if r.Defn.Mnemonic == "RTS" {
|
|
// sometimes, a ROM will call RTS despite never having called
|
|
// JSR. in these instances, the ROM has probably stuffed the
|
|
// stack manually with a return address. this disassembly
|
|
// routine currently doesn't handle these instances.
|
|
//
|
|
// Krull does this. one of the very first things it does at
|
|
// address 0xb038 (bank 0) is load the stack with a return
|
|
// address. the first time the "extra" RTS occurs is at 0xb0ad
|
|
dsm.forcedRTS = true
|
|
return nil
|
|
}
|
|
|
|
// note current location
|
|
retPC := mc.PC.ToUint16()
|
|
|
|
// adjust program counter
|
|
mc.PC.Load(r.InstructionData.(uint16))
|
|
|
|
// recurse
|
|
err = dsm.flowDisassembly(mc)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// resume from where we left off
|
|
mc.PC.Load(retPC)
|
|
|
|
// subroutines don't care about cartridge banks
|
|
// -- if we JSR in bank 0 and RTS in bank 1 then that execution
|
|
// will continue in bank 1. that's expected VCS behaviour.
|
|
|
|
case definitions.Interrupt:
|
|
// do nothing with interrupts
|
|
dsm.interrupts = true
|
|
}
|
|
}
|
|
}
|