Gopher2600/disassembly/flow.go
steve c59a4a2dde o debugger
- 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
2020-01-05 18:58:36 +00:00

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
}
}
}