mirror of
https://github.com/JetSetIlly/Gopher2600.git
synced 2024-05-20 05:40:49 -04:00
310 lines
9.3 KiB
Go
310 lines
9.3 KiB
Go
// This file is part of Gopher2600.
|
|
//
|
|
// Gopher2600 is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// Gopher2600 is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with Gopher2600. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
package disassembly
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/jetsetilly/gopher2600/hardware/cpu/execution"
|
|
"github.com/jetsetilly/gopher2600/hardware/cpu/instructions"
|
|
"github.com/jetsetilly/gopher2600/hardware/cpu/registers"
|
|
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/mapper"
|
|
"github.com/jetsetilly/gopher2600/hardware/memory/memorymap"
|
|
)
|
|
|
|
// EntryLevel describes the level of the Entry.
|
|
type EntryLevel int
|
|
|
|
// List of valid EntryLevel in increasing reliability.
|
|
//
|
|
// Decoded entries have been decoded as though every byte point is a valid
|
|
// instruction. Blessed entries meanwhile take into consideration the preceding
|
|
// instruction and the number of bytes it would have consumed.
|
|
//
|
|
// Decoded entries are useful in the event of the CPU landing on an address that
|
|
// didn't look like an instruction at disassembly time.
|
|
//
|
|
// Blessed instructions are deemed to be more accurate because they have been
|
|
// reached according to the flow of the instructions from the start address.
|
|
//
|
|
// For normal debugging operations there is no need to use EntryLevelUnmappable
|
|
// outside of the disassembly package. It used for the unusual case where a bank
|
|
// is not able to be referenced from the Entry address. See M-Network for an
|
|
// example of this, where Bank 7 cannot be mapped to the lower segment.
|
|
const (
|
|
EntryLevelUnmappable EntryLevel = iota
|
|
EntryLevelDecoded
|
|
EntryLevelBlessed
|
|
EntryLevelExecuted
|
|
)
|
|
|
|
// Entry is a disassambled instruction. The constituent parts of the
|
|
// disassembly. It is a representation of execution.Instruction.
|
|
type Entry struct {
|
|
dsm *Disassembly
|
|
|
|
// the bank this entry belongs to. note that this is just the bank number;
|
|
// we're not storing a copy of mapper.BankInfo. that's not needed for
|
|
// disassembly purposes
|
|
Bank int
|
|
|
|
// the level of reliability of the information in the Entry.
|
|
//
|
|
// note that it is possible for EntryLevelExecuted entries to be partially
|
|
// executed. check Result.Final if required.
|
|
Level EntryLevel
|
|
|
|
// copy of the CPU execution. must not be updated except through
|
|
// updateExecutionEntry() function.
|
|
//
|
|
// not that the the Final field of execution.Result may be false is the
|
|
// emulation is stopped mid-execution.
|
|
Result execution.Result
|
|
|
|
// the entries below are not defined if Level == EntryLevelUnused
|
|
|
|
// string representations of information in execution.Result
|
|
// entry.GetField() will apply white spacing padding suitable for columnation
|
|
Label Label
|
|
Bytecode string
|
|
Address string
|
|
Operator string
|
|
Operand Operand
|
|
}
|
|
|
|
// some fields in the disassembly entry are updated on every execution.
|
|
func (e *Entry) updateExecutionEntry(result execution.Result) {
|
|
// update result instance
|
|
e.Result = result
|
|
|
|
// update result instance in Label. we probably don't need to do this but it
|
|
// might be useful to know what the *actual* address of the instruction. ie.
|
|
// which mirror is used by the program at that point in the execution.
|
|
e.Label.result = e.Result
|
|
|
|
// update result instance in Operand
|
|
e.Operand.result = e.Result
|
|
|
|
// indicate that entry has been executed
|
|
e.Level = EntryLevelExecuted
|
|
}
|
|
|
|
// Cycles returns the number of cycles annotated if actual cycles differs from
|
|
// the number of cycles in the definition. for executed branch instructions this
|
|
// will always be the case.
|
|
func (e *Entry) Cycles() string {
|
|
// the Defn field may be unassigned
|
|
if e.Result.Defn == nil {
|
|
return "?"
|
|
}
|
|
|
|
if e.Level < EntryLevelExecuted {
|
|
return e.Result.Defn.Cycles.Formatted
|
|
}
|
|
|
|
if e.Result.Final {
|
|
return fmt.Sprintf("%d", e.Result.Cycles)
|
|
}
|
|
|
|
// if entry hasn't been executed yet or if actual cycles is different to
|
|
// the cycles defined for the entry then return an annotated string
|
|
return fmt.Sprintf("%d of %s", e.Result.Cycles, e.Result.Defn.Cycles.Formatted)
|
|
}
|
|
|
|
// Notes returns a string returning notes about the most recent execution. The
|
|
// information is made up of the BranchSuccess, PageFault and CPUBug fields.
|
|
func (e *Entry) Notes() string {
|
|
if e.Level < EntryLevelExecuted {
|
|
return ""
|
|
}
|
|
|
|
if !e.Result.Final {
|
|
return ""
|
|
}
|
|
|
|
s := strings.Builder{}
|
|
|
|
if e.Result.Defn != nil && e.Result.Defn.IsBranch() {
|
|
if e.Result.BranchSuccess {
|
|
s.WriteString("branch succeeded ")
|
|
} else {
|
|
s.WriteString("branch failed ")
|
|
}
|
|
|
|
if e.Result.PageFault {
|
|
s.WriteString("with page-fault ")
|
|
}
|
|
} else {
|
|
if e.Result.PageFault {
|
|
s.WriteString("page-fault ")
|
|
}
|
|
}
|
|
|
|
if e.Result.CPUBug != "" {
|
|
s.WriteString(e.Result.CPUBug)
|
|
}
|
|
|
|
return s.String()
|
|
}
|
|
|
|
// add decoration to operand according to the addressing mode of the entry.
|
|
// operand taken as an argument because it is called from two different contexts.
|
|
func addrModeDecoration(operand string, mode instructions.AddressingMode) string {
|
|
s := operand
|
|
|
|
switch mode {
|
|
case instructions.Implied:
|
|
case instructions.Immediate:
|
|
s = fmt.Sprintf("#%s", operand)
|
|
case instructions.Relative:
|
|
case instructions.Absolute:
|
|
case instructions.ZeroPage:
|
|
case instructions.Indirect:
|
|
s = fmt.Sprintf("(%s)", operand)
|
|
case instructions.IndexedIndirect:
|
|
s = fmt.Sprintf("(%s,X)", operand)
|
|
case instructions.IndirectIndexed:
|
|
s = fmt.Sprintf("(%s),Y", operand)
|
|
case instructions.AbsoluteIndexedX:
|
|
s = fmt.Sprintf("%s,X", operand)
|
|
case instructions.AbsoluteIndexedY:
|
|
s = fmt.Sprintf("%s,Y", operand)
|
|
case instructions.ZeroPageIndexedX:
|
|
s = fmt.Sprintf("%s,X", operand)
|
|
case instructions.ZeroPageIndexedY:
|
|
s = fmt.Sprintf("%s,Y", operand)
|
|
}
|
|
|
|
return s
|
|
}
|
|
|
|
// absolute branch destination returns the branch operand as the address of the
|
|
// branched PC, rather than an offset value.
|
|
func absoluteBranchDestination(addr uint16, operand uint16) uint16 {
|
|
// create a mock register with the instruction's address as the initial value
|
|
pc := registers.NewProgramCounter(addr)
|
|
|
|
// all 6502 branch instructions are 2 bytes in length
|
|
pc.Add(2)
|
|
|
|
// because we're doing 16 bit arithmetic with an 8bit value, we need to
|
|
// make sure the sign bit has been propogated to the more-significant bits
|
|
if operand&0x0080 == 0x0080 {
|
|
operand |= 0xff00
|
|
}
|
|
|
|
// add the 2s-complement value to the mock program counter
|
|
pc.Add(operand)
|
|
|
|
return pc.Address()
|
|
}
|
|
|
|
// Label implements the Stringer interface. The String() implementation
|
|
// returns any address label for the entry. Use GetField() function for
|
|
// a white-space padded label.
|
|
type Label struct {
|
|
dsm *Disassembly
|
|
result execution.Result
|
|
bank mapper.BankInfo
|
|
}
|
|
|
|
// Resolve returns the address label as a symbol (if a symbol is available)
|
|
// if a symbol is not available then the the bool return value will be false.
|
|
func (l Label) Resolve() string {
|
|
if l.dsm.Prefs.Symbols.Get().(bool) {
|
|
ma, _ := memorymap.MapAddress(l.result.Address, true)
|
|
if e, ok := l.dsm.Sym.GetLabel(l.bank.Number, ma); ok {
|
|
return e.Symbol
|
|
}
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
// Operand implements the Stringer interface. The String() implementation
|
|
// returns the operand (with symbols if appropriate). Use GetField function for
|
|
// white-space padded operand string.
|
|
type Operand struct {
|
|
dsm *Disassembly
|
|
result execution.Result
|
|
bank mapper.BankInfo
|
|
|
|
// partial is the operand that will be used as the result from Resolve()
|
|
// when the execution result is not complete (ie. when not enough bytes have
|
|
// been read)
|
|
partial string
|
|
}
|
|
|
|
// Resolve returns the operand as a symbol (if a symbol is available) if a symbol
|
|
// is not available then the returned value will be be numeric possibly with
|
|
// placeholders for unknown bytes
|
|
func (op Operand) Resolve() string {
|
|
if op.result.Defn == nil {
|
|
return op.partial
|
|
}
|
|
|
|
if op.dsm == nil || !op.dsm.Prefs.Symbols.Get().(bool) {
|
|
return op.partial
|
|
}
|
|
|
|
if op.result.ByteCount != op.result.Defn.Bytes {
|
|
return op.partial
|
|
}
|
|
|
|
res := op.partial
|
|
|
|
// use symbol for the operand if available/appropriate. we should only do
|
|
// this if at least part of an operand has been decoded
|
|
if op.result.Defn.Bytes > 1 {
|
|
data := op.result.InstructionData
|
|
|
|
switch op.result.Defn.Effect {
|
|
case instructions.Flow:
|
|
if op.result.Defn.IsBranch() {
|
|
data = absoluteBranchDestination(op.result.Address, data)
|
|
|
|
// look up mock program counter value in symbol table
|
|
if e, ok := op.dsm.Sym.GetLabel(op.bank.Number, data); ok {
|
|
res = e.Symbol
|
|
}
|
|
} else if e, ok := op.dsm.Sym.GetLabel(op.bank.Number, data); ok {
|
|
res = addrModeDecoration(e.Symbol, op.result.Defn.AddressingMode)
|
|
}
|
|
|
|
case instructions.Subroutine:
|
|
if e, ok := op.dsm.Sym.GetLabel(op.bank.Number, data); ok {
|
|
res = e.Symbol
|
|
}
|
|
|
|
case instructions.Read:
|
|
if e, ok := op.dsm.Sym.GetSymbol(data, true); ok {
|
|
res = addrModeDecoration(e.Symbol, op.result.Defn.AddressingMode)
|
|
}
|
|
|
|
case instructions.Write:
|
|
fallthrough
|
|
|
|
case instructions.RMW:
|
|
if e, ok := op.dsm.Sym.GetSymbol(data, false); ok {
|
|
res = addrModeDecoration(e.Symbol, op.result.Defn.AddressingMode)
|
|
}
|
|
}
|
|
}
|
|
|
|
return res
|
|
}
|