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