mirror of
https://github.com/JetSetIlly/Gopher2600.git
synced 2024-06-02 12:08:01 -04:00
improved disassembly of non-4k bank sizes
clarified disassembly memory model corrected decision making in blessSequence() function there will still be instances when executed entries won't/can't be detected statically fixes #14
This commit is contained in:
parent
b1bace8ebf
commit
af1fc91bbb
|
@ -26,19 +26,14 @@ import (
|
|||
)
|
||||
|
||||
func (dsm *Disassembly) disassemble(mc *cpu.CPU, mem *disasmMemory) error {
|
||||
copiedBanks, err := dsm.vcs.Mem.Cart.CopyBanks()
|
||||
if err != nil {
|
||||
return curated.Errorf("decode: %v", err)
|
||||
}
|
||||
|
||||
// basic decoding pass
|
||||
err = dsm.decode(mc, mem, copiedBanks)
|
||||
err := dsm.decode(mc, mem)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// bless those entries which we're reasonably sure are real instructions
|
||||
err = dsm.bless(mc, copiedBanks)
|
||||
err = dsm.bless(mc, mem)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -49,24 +44,19 @@ func (dsm *Disassembly) disassemble(mc *cpu.CPU, mem *disasmMemory) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (dsm *Disassembly) bless(mc *cpu.CPU, copiedBanks []mapper.BankContent) error {
|
||||
// we can be sure the machine reset address is a starting point for a blessing
|
||||
err := mc.LoadPCIndirect(addresses.Reset)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resetAddress, resetArea := memorymap.MapAddress(mc.PC.Value(), true)
|
||||
if resetArea != memorymap.Cartridge {
|
||||
logger.Log("disassembly", "cartridge does not reset to an address in the cartridge area.")
|
||||
}
|
||||
|
||||
// bless from reset address for every bank. note that we do not test to see
|
||||
// if the blessSequence is "correct" before committal because execution
|
||||
// from the reset address is likely
|
||||
func (dsm *Disassembly) bless(mc *cpu.CPU, mem *disasmMemory) error {
|
||||
// bless from reset address for every bank
|
||||
for b := range dsm.entries {
|
||||
mc.PC.Load(resetAddress)
|
||||
_ = dsm.blessSequence(b, mc.PC.Value(), true)
|
||||
// get reset address from starting bank, taking into account the
|
||||
// possibility that bank is smalled thank 4096 bytes
|
||||
resetVector := addresses.Reset & (uint16(len(mem.banks[b].Data) - 1))
|
||||
resetAddr := (uint16(mem.banks[b].Data[resetVector+1]) << 8) | uint16(mem.banks[b].Data[resetVector])
|
||||
|
||||
// make sure reset address is valid
|
||||
_, area := memorymap.MapAddress(resetAddr, true)
|
||||
if area == memorymap.Cartridge {
|
||||
_ = dsm.blessSequence(mem.startingBank, resetAddr, true)
|
||||
}
|
||||
}
|
||||
|
||||
moreBlessing := true
|
||||
|
@ -75,10 +65,10 @@ func (dsm *Disassembly) bless(mc *cpu.CPU, copiedBanks []mapper.BankContent) err
|
|||
for moreBlessing {
|
||||
moreBlessing = false
|
||||
|
||||
// make sure we're not going bezerk. a limit of 100 is probably far too high
|
||||
// make sure we're not going bezerk. a limit of 10 is probably too high
|
||||
attempts++
|
||||
if attempts > 100 {
|
||||
return curated.Errorf("bless: cannot discover a finite bless path through the program")
|
||||
if attempts > 10 {
|
||||
break
|
||||
}
|
||||
|
||||
// loop through every bank in the cartridge and collate a list of blessings
|
||||
|
@ -104,7 +94,7 @@ func (dsm *Disassembly) bless(mc *cpu.CPU, copiedBanks []mapper.BankContent) err
|
|||
jmpAddress, area := memorymap.MapAddress(e.Result.InstructionData, true)
|
||||
|
||||
if area == memorymap.Cartridge {
|
||||
l := jmpTargets(copiedBanks, jmpAddress)
|
||||
l := jmpTargets(mem.banks, jmpAddress)
|
||||
|
||||
for _, jb := range l {
|
||||
if dsm.blessSequence(jb, jmpAddress, false) {
|
||||
|
@ -132,6 +122,7 @@ func (dsm *Disassembly) bless(mc *cpu.CPU, copiedBanks []mapper.BankContent) err
|
|||
mc.PC.Add(operand)
|
||||
|
||||
pcAddress, area := memorymap.MapAddress(mc.PC.Value(), true)
|
||||
|
||||
if area == memorymap.Cartridge {
|
||||
if dsm.blessSequence(b, pcAddress, false) {
|
||||
if dsm.blessSequence(b, pcAddress, true) {
|
||||
|
@ -229,10 +220,9 @@ func (dsm *Disassembly) blessSequence(bank int, addr uint16, commit bool) bool {
|
|||
|
||||
// end run if entry has already been blessed
|
||||
if instruction.Level == EntryLevelBlessed {
|
||||
if hasCommitted {
|
||||
logger.Logf("disassembly", "blessSequence has blessed an instruction in a false sequence. discovered at bank %d: %s", bank, instruction.String())
|
||||
}
|
||||
return false
|
||||
// this is okay and expected so return true to indicate that the
|
||||
// blessing should continue
|
||||
return true
|
||||
}
|
||||
|
||||
// if operator is unknown than end the sequence.
|
||||
|
@ -276,6 +266,7 @@ func (dsm *Disassembly) blessSequence(bank int, addr uint16, commit bool) bool {
|
|||
// promote the entry
|
||||
if commit {
|
||||
hasCommitted = true
|
||||
|
||||
instruction.Level = EntryLevelBlessed
|
||||
}
|
||||
|
||||
|
@ -295,15 +286,14 @@ func (dsm *Disassembly) blessSequence(bank int, addr uint16, commit bool) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
func (dsm *Disassembly) decode(mc *cpu.CPU, mem *disasmMemory, copiedBanks []mapper.BankContent) error {
|
||||
func (dsm *Disassembly) decode(mc *cpu.CPU, mem *disasmMemory) error {
|
||||
// decoding can happen at the same time as iteration which is probably
|
||||
// being run from a different goroutine. acknowledge the critical section
|
||||
dsm.crit.Lock()
|
||||
defer dsm.crit.Unlock()
|
||||
|
||||
for _, bank := range copiedBanks {
|
||||
// point memory implementation to iterated bank
|
||||
mem.bank = bank
|
||||
for _, bank := range mem.banks {
|
||||
mem.currentBank = bank.Number
|
||||
|
||||
// try each bank in each possible segment - banks that are smaller than
|
||||
// the cartridge addressing range can often be mapped into different
|
||||
|
|
|
@ -27,6 +27,7 @@ import (
|
|||
"github.com/jetsetilly/gopher2600/hardware"
|
||||
"github.com/jetsetilly/gopher2600/hardware/cpu"
|
||||
"github.com/jetsetilly/gopher2600/hardware/cpu/execution"
|
||||
"github.com/jetsetilly/gopher2600/hardware/memory/addresses"
|
||||
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/mapper"
|
||||
"github.com/jetsetilly/gopher2600/hardware/memory/memorymap"
|
||||
"github.com/jetsetilly/gopher2600/hardware/television"
|
||||
|
@ -128,16 +129,31 @@ func FromCartridge(cartload cartridgeloader.Loader) (*Disassembly, error) {
|
|||
|
||||
// FromMemory disassembles an existing instance of cartridge memory using a
|
||||
// cpu with no flow control. Unlike the FromCartridge() function this function
|
||||
// requires an existing instance of Disassembly
|
||||
// requires an existing instance of Disassembly.
|
||||
//
|
||||
// cartridge will finish in its initialised state.
|
||||
// Disassembly will start/assume the cartridge is in the correct starting bank.
|
||||
func (dsm *Disassembly) FromMemory() error {
|
||||
dsm.crit.Lock()
|
||||
// unlocking manually before we call the disassmeble() function. this means
|
||||
// we have to be careful to manually unlock before returning an error.
|
||||
dsm.crit.Lock()
|
||||
|
||||
// create new memory
|
||||
copiedBanks, err := dsm.vcs.Mem.Cart.CopyBanks()
|
||||
if err != nil {
|
||||
dsm.crit.Unlock()
|
||||
return curated.Errorf("disassembly: %v", err)
|
||||
}
|
||||
|
||||
startingBank := dsm.vcs.Mem.Cart.GetBank(addresses.Reset).Number
|
||||
|
||||
mem := newDisasmMemory(startingBank, copiedBanks)
|
||||
if mem == nil {
|
||||
dsm.crit.Unlock()
|
||||
return curated.Errorf("disassembly: %s", "could not create memory for disassembly")
|
||||
}
|
||||
|
||||
// read symbols file
|
||||
err := dsm.Sym.ReadSymbolsFile(dsm.vcs.Mem.Cart)
|
||||
err = dsm.Sym.ReadSymbolsFile(dsm.vcs.Mem.Cart)
|
||||
if err != nil {
|
||||
dsm.crit.Unlock()
|
||||
return err
|
||||
|
@ -156,9 +172,6 @@ func (dsm *Disassembly) FromMemory() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// create new memory
|
||||
mem := &disasmMemory{}
|
||||
|
||||
// create a new NoFlowControl CPU to help disassemble memory
|
||||
mc := cpu.NewCPU(nil, mem)
|
||||
mc.NoFlowControl = true
|
||||
|
|
|
@ -47,4 +47,13 @@
|
|||
// to the Symbol tables. This reference will be valid throughout the lifetime
|
||||
// of the Disassembly instance and will "survive" calls to the FromMemory() and
|
||||
// FromCartridge() functions.
|
||||
//
|
||||
// Segmented Cartridges
|
||||
//
|
||||
// The disassembly package treats small bank sized (those less than 4k) by
|
||||
// performing the disassembly with the cartridge rooted at each origin point -
|
||||
// in each possible segment allowed by the mapper.
|
||||
//
|
||||
// Origin information is held in the mappers.BankContent type returned by the
|
||||
// cartridge.CopyBanks() function.
|
||||
package disassembly
|
||||
|
|
|
@ -23,22 +23,57 @@ import (
|
|||
// disasmMemory is a simplified memory model that allows the emulated CPU to
|
||||
// read cartridge memory without touching the actual cartridge.
|
||||
type disasmMemory struct {
|
||||
// if bank is not nil then the bank is read directly
|
||||
bank mapper.BankContent
|
||||
// the bank which the cartridge starts on
|
||||
startingBank int
|
||||
|
||||
// current bank to index the banks array
|
||||
currentBank int
|
||||
banks []mapper.BankContent
|
||||
|
||||
// the current origin for the mapped bank
|
||||
origin uint16
|
||||
}
|
||||
|
||||
func newDisasmMemory(startingBank int, copiedBanks []mapper.BankContent) *disasmMemory {
|
||||
dismem := &disasmMemory{
|
||||
startingBank: startingBank,
|
||||
currentBank: startingBank,
|
||||
banks: copiedBanks,
|
||||
}
|
||||
return dismem
|
||||
}
|
||||
|
||||
func (dismem *disasmMemory) reset() {
|
||||
dismem.currentBank = dismem.startingBank
|
||||
dismem.origin = dismem.banks[dismem.currentBank].Origins[0]
|
||||
}
|
||||
|
||||
func (dismem *disasmMemory) Read(address uint16) (uint8, error) {
|
||||
// map address
|
||||
address, area := memorymap.MapAddress(address, true)
|
||||
if area == memorymap.Cartridge {
|
||||
// bank field is set so we bypass the cartridge mapper's usual read
|
||||
// logic and access the bank directly
|
||||
// bring address into range. if it's still outside of range return a
|
||||
// zero byte (with no error)
|
||||
//
|
||||
// the alternative to this strategy is to use a mask based on the
|
||||
// actual length of the bank. in most cases this will be the same as
|
||||
// memorymap.CartridgeBits but some cartridge mappers use banks that
|
||||
// are smaller than that:
|
||||
//
|
||||
// address = address & uint16(len(dismem.banks[0].Data)-1)
|
||||
//
|
||||
// the problem with this is that the Read() function will return a byte
|
||||
// which may be misleading to the disassembler. using the strategy
|
||||
// below a value of zero is returned.
|
||||
//
|
||||
// no error is returned, even though it might seem we should, because
|
||||
// it would only cause the CPU to halt mid-instruction, which would
|
||||
// only cause other complications.
|
||||
address = (address - dismem.origin) & memorymap.CartridgeBits
|
||||
if address >= uint16(len(dismem.bank.Data)) {
|
||||
if address >= uint16(len(dismem.banks[dismem.currentBank].Data)) {
|
||||
return 0, nil
|
||||
}
|
||||
return dismem.bank.Data[address], nil
|
||||
return dismem.banks[dismem.currentBank].Data[address], nil
|
||||
}
|
||||
|
||||
// address outside of cartridge range return nothing
|
||||
|
|
Loading…
Reference in a new issue