Gopher2600/disassembly/entry.go
2024-01-16 10:54:54 +00:00

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
}