mirror of
https://github.com/JetSetIlly/Gopher2600.git
synced 2025-04-02 11:02:17 -04:00
o cpu
- result sub-package renamed to execution - renamed Instruction type therein to Instruction o disassembly - reworked structure of pacakge - better grep. scope of grep can now be specified - display sub-package added - disassemblies now store instance of display.DisasmInstruction instead of a formatted string o debugger - ammende GREP to support new disassembly features
This commit is contained in:
parent
0f5a258482
commit
6616bc2b88
25 changed files with 510 additions and 509 deletions
|
@ -1,15 +1,16 @@
|
|||
package debugger
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"gopher2600/cartridgeloader"
|
||||
"gopher2600/debugger/script"
|
||||
"gopher2600/debugger/terminal"
|
||||
"gopher2600/debugger/terminal/commandline"
|
||||
"gopher2600/disassembly"
|
||||
"gopher2600/errors"
|
||||
"gopher2600/gui"
|
||||
"gopher2600/hardware/cpu/registers"
|
||||
"gopher2600/hardware/cpu/result"
|
||||
"gopher2600/hardware/memory/addresses"
|
||||
"gopher2600/hardware/memory/memorymap"
|
||||
"gopher2600/hardware/riot/input"
|
||||
|
@ -79,10 +80,10 @@ var commandTemplate = []string{
|
|||
cmdCartridge + " (ANALYSIS|BANK %N)",
|
||||
cmdClear + " [BREAKS|TRAPS|WATCHES|ALL]",
|
||||
cmdDebuggerState,
|
||||
cmdDisassembly,
|
||||
cmdDisassembly + " (BYTECODE)",
|
||||
cmdDisplay + " (ON|OFF|DEBUG (ON|OFF)|SCALE [%P]|ALT (ON|OFF)|OVERLAY (ON|OFF))", // see notes
|
||||
cmdDrop + " [BREAK|TRAP|WATCH] %N",
|
||||
cmdGrep + " %S",
|
||||
cmdGrep + " (MNEMONIC|OPERAND) %S",
|
||||
cmdHexLoad + " %N %N {%N}",
|
||||
cmdInsert + " %F",
|
||||
cmdLast + " (DEFN|BYTECODE)",
|
||||
|
@ -319,10 +320,6 @@ func (dbg *Debugger) enactCommand(tokens *commandline.Tokens, interactive bool)
|
|||
defer func() {
|
||||
dbg.scriptScribe.EndPlayback()
|
||||
}()
|
||||
|
||||
// !!TODO: provide a recording option to allow insertion of
|
||||
// the actual script commands rather than the call to the
|
||||
// script itself
|
||||
}
|
||||
|
||||
err = dbg.inputLoop(scr, false)
|
||||
|
@ -332,12 +329,29 @@ func (dbg *Debugger) enactCommand(tokens *commandline.Tokens, interactive bool)
|
|||
}
|
||||
|
||||
case cmdDisassembly:
|
||||
dbg.disasm.Dump(dbg.printStyle(terminal.StyleFeedback))
|
||||
option, _ := tokens.Get()
|
||||
bytecode := option == "BYTECODE"
|
||||
|
||||
s := &bytes.Buffer{}
|
||||
dbg.disasm.Write(s, bytecode)
|
||||
dbg.print(terminal.StyleFeedback, s.String())
|
||||
|
||||
case cmdGrep:
|
||||
scope := disassembly.GrepAll
|
||||
|
||||
s, _ := tokens.Get()
|
||||
switch strings.ToUpper(s) {
|
||||
case "MNEMONIC":
|
||||
scope = disassembly.GrepMnemonic
|
||||
case "OPERAND":
|
||||
scope = disassembly.GrepOperand
|
||||
default:
|
||||
tokens.Unget()
|
||||
}
|
||||
|
||||
search, _ := tokens.Get()
|
||||
output := strings.Builder{}
|
||||
dbg.disasm.Grep(&output, search, false, 3)
|
||||
dbg.disasm.Grep(&output, scope, search, false)
|
||||
if output.Len() == 0 {
|
||||
dbg.print(terminal.StyleError, "%s not found in disassembly", search)
|
||||
} else {
|
||||
|
@ -590,35 +604,39 @@ func (dbg *Debugger) enactCommand(tokens *commandline.Tokens, interactive bool)
|
|||
return doNothing, nil
|
||||
|
||||
case cmdLast:
|
||||
if dbg.vcs.CPU.LastResult.Defn == nil {
|
||||
// special condition for when LAST is called before any execution
|
||||
// has taken place
|
||||
dbg.print(terminal.StyleFeedback, "no instruction decoded yet")
|
||||
s := strings.Builder{}
|
||||
|
||||
d, err := dbg.disasm.FormatResult(dbg.vcs.CPU.LastResult)
|
||||
if err != nil {
|
||||
return doNothing, err
|
||||
}
|
||||
|
||||
option, ok := tokens.Get()
|
||||
if ok {
|
||||
switch strings.ToUpper(option) {
|
||||
case "DEFN":
|
||||
dbg.print(terminal.StyleFeedback, "%s", dbg.vcs.CPU.LastResult.Defn)
|
||||
break
|
||||
|
||||
case "BYTECODE":
|
||||
s.WriteString(fmt.Sprintf(dbg.disasm.Columns.Fmt.Bytecode, d.Bytecode))
|
||||
}
|
||||
}
|
||||
|
||||
s.WriteString(fmt.Sprintf(dbg.disasm.Columns.Fmt.Address, d.Address))
|
||||
s.WriteString(" ")
|
||||
s.WriteString(fmt.Sprintf(dbg.disasm.Columns.Fmt.Mnemonic, d.Mnemonic))
|
||||
s.WriteString(" ")
|
||||
s.WriteString(fmt.Sprintf(dbg.disasm.Columns.Fmt.Operand, d.Operand))
|
||||
s.WriteString(" ")
|
||||
s.WriteString(fmt.Sprintf(dbg.disasm.Columns.Fmt.Cycles, d.Cycles))
|
||||
s.WriteString(" ")
|
||||
s.WriteString(fmt.Sprintf(dbg.disasm.Columns.Fmt.Notes, d.Notes))
|
||||
|
||||
if dbg.vcs.CPU.LastResult.Final {
|
||||
dbg.print(terminal.StyleCPUStep, s.String())
|
||||
} else {
|
||||
done := false
|
||||
resultStyle := result.StyleExecution
|
||||
|
||||
option, ok := tokens.Get()
|
||||
if ok {
|
||||
switch strings.ToUpper(option) {
|
||||
case "DEFN":
|
||||
dbg.print(terminal.StyleFeedback, "%s", dbg.vcs.CPU.LastResult.Defn)
|
||||
done = true
|
||||
|
||||
case "BYTECODE":
|
||||
resultStyle = result.StyleDisasm
|
||||
}
|
||||
}
|
||||
|
||||
if !done {
|
||||
var printTag terminal.Style
|
||||
if dbg.vcs.CPU.LastResult.Final {
|
||||
printTag = terminal.StyleCPUStep
|
||||
} else {
|
||||
printTag = terminal.StyleVideoStep
|
||||
}
|
||||
dbg.print(printTag, "%s", dbg.vcs.CPU.LastResult.GetString(dbg.disasm.Symtable, resultStyle))
|
||||
}
|
||||
dbg.print(terminal.StyleVideoStep, s.String())
|
||||
}
|
||||
|
||||
case cmdMemMap:
|
||||
|
|
|
@ -14,15 +14,9 @@ import (
|
|||
// function. output will be normalised and sent to the attached terminal as
|
||||
// required.
|
||||
func (dbg *Debugger) print(sty terminal.Style, s string, a ...interface{}) {
|
||||
// resolve string placeholders and return if the resulting string is empty
|
||||
if sty != terminal.StyleHelp {
|
||||
s = fmt.Sprintf(s, a...)
|
||||
if len(s) == 0 {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// trim *all* trailing newlines - UserPrint() will add newlines if required
|
||||
// resolve string placeholders, remove all trailing newlines, and return if
|
||||
// the resulting string is empty
|
||||
s = fmt.Sprintf(s, a...)
|
||||
s = strings.TrimRight(s, "\n")
|
||||
if len(s) == 0 {
|
||||
return
|
||||
|
|
|
@ -36,10 +36,14 @@ func (dbg *Debugger) buildPrompt(videoCycle bool) terminal.Prompt {
|
|||
// prompt address doesn't seem to be pointing to the cartridge, prepare
|
||||
// "non-cart" prompt
|
||||
prompt.WriteString(fmt.Sprintf(" %#04x non-cart space ]", promptAddress))
|
||||
} else if entry, ok := dbg.disasm.Get(promptBank, promptAddress); ok {
|
||||
} else if d, ok := dbg.disasm.Get(promptBank, promptAddress); ok {
|
||||
// because we're using the raw disassmebly the reported address
|
||||
// in that disassembly may be misleading.
|
||||
prompt.WriteString(fmt.Sprintf(" %#04x %s ]", promptAddress, entry.String()))
|
||||
prompt.WriteString(fmt.Sprintf(" %#04x %s", promptAddress, d.Mnemonic))
|
||||
if d.Operand != "" {
|
||||
prompt.WriteString(fmt.Sprintf(" %s", d.Operand))
|
||||
}
|
||||
prompt.WriteString(" ]")
|
||||
} else {
|
||||
// incomplete disassembly, prepare "no disasm" prompt
|
||||
prompt.WriteString(fmt.Sprintf(" %#04x (%d) no disasm ]", promptAddress, promptBank))
|
||||
|
|
|
@ -30,7 +30,6 @@ func (ct *ColorTerminal) TermPrint(style terminal.Style, s string, a ...interfac
|
|||
ct.EasyTerm.TermPrint("* ")
|
||||
case terminal.StyleHelp:
|
||||
ct.EasyTerm.TermPrint(ansi.DimPens["white"])
|
||||
ct.EasyTerm.TermPrint(" ")
|
||||
case terminal.StyleFeedback:
|
||||
ct.EasyTerm.TermPrint(ansi.DimPens["white"])
|
||||
case terminal.StylePromptCPUStep:
|
||||
|
|
|
@ -33,7 +33,8 @@
|
|||
// that we know have passed the validation. For example, using the above
|
||||
// template, we can implement a switch very consisely:
|
||||
//
|
||||
// switch toks.Get() {
|
||||
// option, _ := toks.Get()
|
||||
// switch strings.ToUpper(option) {
|
||||
// case "LIST:
|
||||
// list()
|
||||
// case "PRINT:
|
||||
|
|
|
@ -43,8 +43,6 @@ func (pt PlainTerminal) TermPrint(style terminal.Style, s string, a ...interface
|
|||
switch style {
|
||||
case terminal.StyleError:
|
||||
s = fmt.Sprintf("* %s", s)
|
||||
case terminal.StyleHelp:
|
||||
s = fmt.Sprintf(" %s", s)
|
||||
}
|
||||
|
||||
s = fmt.Sprintf(s, a...)
|
||||
|
|
|
@ -138,10 +138,7 @@ func (wtc *watches) parseWatch(tokens *commandline.Tokens, dbgmem *memoryDebug)
|
|||
var event watchEvent
|
||||
|
||||
// read mode
|
||||
mode, present := tokens.Get()
|
||||
if !present {
|
||||
return errors.New(errors.CommandError, "watch address required")
|
||||
}
|
||||
mode, _ := tokens.Get()
|
||||
mode = strings.ToUpper(mode)
|
||||
switch mode {
|
||||
case "READ":
|
||||
|
|
|
@ -3,18 +3,19 @@ package disassembly
|
|||
import (
|
||||
"fmt"
|
||||
"gopher2600/cartridgeloader"
|
||||
"gopher2600/disassembly/display"
|
||||
"gopher2600/errors"
|
||||
"gopher2600/hardware/cpu"
|
||||
"gopher2600/hardware/cpu/execution"
|
||||
"gopher2600/hardware/memory/addresses"
|
||||
"gopher2600/hardware/memory/cartridge"
|
||||
"gopher2600/symbols"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const disasmMask = 0x0fff
|
||||
|
||||
type bank [disasmMask + 1]Entry
|
||||
type bank [disasmMask + 1]*display.Instruction
|
||||
|
||||
// Disassembly represents the annotated disassembly of a 6507 binary
|
||||
type Disassembly struct {
|
||||
|
@ -25,7 +26,7 @@ type Disassembly struct {
|
|||
interrupts bool
|
||||
forcedRTS bool
|
||||
|
||||
// symbols used to build disassembly output
|
||||
// symbols used to format disassembly output
|
||||
Symtable *symbols.Table
|
||||
|
||||
// linear is the decoding of every possible address in the cartridge
|
||||
|
@ -34,6 +35,11 @@ type Disassembly struct {
|
|||
// flow is the decoding of cartridge addresses that follow the flow from
|
||||
// the start address
|
||||
flow []bank
|
||||
|
||||
// formatting information for all entries in the flow disassembly.
|
||||
// excluding the linear disassembly because false positives entries might
|
||||
// upset the formatting.
|
||||
Columns display.Columns
|
||||
}
|
||||
|
||||
// Analysis returns a summary of anything interesting found during disassembly.
|
||||
|
@ -45,27 +51,20 @@ func (dsm Disassembly) Analysis() string {
|
|||
return s.String()
|
||||
}
|
||||
|
||||
// Get returns the disassembled entry at the specified bank/address. This
|
||||
// Get returns the disassembly at the specified bank/address
|
||||
//
|
||||
// function works best when the address definitely points to a valid
|
||||
// instruction. This probably means during the execution of a the cartridge
|
||||
// with proper flow control.
|
||||
func (dsm Disassembly) Get(bank int, address uint16) (Entry, bool) {
|
||||
entry := dsm.linear[bank][address&disasmMask]
|
||||
return entry, entry.IsInstruction()
|
||||
func (dsm Disassembly) Get(bank int, address uint16) (*display.Instruction, bool) {
|
||||
col := dsm.linear[bank][address&disasmMask]
|
||||
return col, col != nil
|
||||
}
|
||||
|
||||
// Dump writes the entire disassembly to the write interface
|
||||
func (dsm *Disassembly) Dump(output io.Writer) {
|
||||
for bank := 0; bank < len(dsm.flow); bank++ {
|
||||
output.Write([]byte(fmt.Sprintf("--- bank %d ---\n", bank)))
|
||||
|
||||
for i := range dsm.flow[bank] {
|
||||
if entry := dsm.flow[bank][i]; entry.instructionDefinition != nil {
|
||||
output.Write([]byte(entry.instruction))
|
||||
output.Write([]byte("\n"))
|
||||
}
|
||||
}
|
||||
}
|
||||
// FormatResult is a wrapper for the display.Format() function using the
|
||||
// current symbol table
|
||||
func (dsm Disassembly) FormatResult(result execution.Result) (*display.Instruction, error) {
|
||||
return display.Format(result, dsm.Symtable)
|
||||
}
|
||||
|
||||
// FromCartridge initialises a new partial emulation and returns a
|
||||
|
|
63
disassembly/display/columns.go
Normal file
63
disassembly/display/columns.go
Normal file
|
@ -0,0 +1,63 @@
|
|||
package display
|
||||
|
||||
import "fmt"
|
||||
|
||||
// Widths of DisasmInstuction entry fields.
|
||||
type Widths struct {
|
||||
Location int
|
||||
Bytecode int
|
||||
Address int
|
||||
Mnemonic int
|
||||
Operand int
|
||||
Cycles int
|
||||
Notes int
|
||||
}
|
||||
|
||||
// Fmt strings for Instruction fields. For use with fmt.Printf() and fmt.Sprintf()
|
||||
type Fmt struct {
|
||||
Location string
|
||||
Bytecode string
|
||||
Address string
|
||||
Mnemonic string
|
||||
Operand string
|
||||
Cycles string
|
||||
Notes string
|
||||
}
|
||||
|
||||
// Columns information for groups of Instructions
|
||||
type Columns struct {
|
||||
Widths Widths
|
||||
Fmt Fmt
|
||||
}
|
||||
|
||||
// Update width and formatting information
|
||||
func (col *Columns) Update(d *Instruction) {
|
||||
if len(d.Location) > col.Widths.Location {
|
||||
col.Widths.Location = len(d.Location)
|
||||
col.Fmt.Location = fmt.Sprintf("%%%ds", col.Widths.Location)
|
||||
}
|
||||
if len(d.Bytecode) > col.Widths.Bytecode {
|
||||
col.Widths.Bytecode = len(d.Bytecode)
|
||||
col.Fmt.Bytecode = fmt.Sprintf("%%%ds", col.Widths.Bytecode)
|
||||
}
|
||||
if len(d.Address) > col.Widths.Address {
|
||||
col.Widths.Address = len(d.Address)
|
||||
col.Fmt.Address = fmt.Sprintf("%%%ds", col.Widths.Address)
|
||||
}
|
||||
if len(d.Mnemonic) > col.Widths.Mnemonic {
|
||||
col.Widths.Mnemonic = len(d.Mnemonic)
|
||||
col.Fmt.Mnemonic = fmt.Sprintf("%%%ds", col.Widths.Mnemonic)
|
||||
}
|
||||
if len(d.Operand) > col.Widths.Operand {
|
||||
col.Widths.Operand = len(d.Operand)
|
||||
col.Fmt.Operand = fmt.Sprintf("%%%ds", col.Widths.Operand)
|
||||
}
|
||||
if len(d.Cycles) > col.Widths.Cycles {
|
||||
col.Widths.Cycles = len(d.Cycles)
|
||||
col.Fmt.Cycles = fmt.Sprintf("%%%ds", col.Widths.Cycles)
|
||||
}
|
||||
if len(d.Notes) > col.Widths.Notes {
|
||||
col.Widths.Notes = len(d.Notes)
|
||||
col.Fmt.Notes = fmt.Sprintf("%%%ds", col.Widths.Notes)
|
||||
}
|
||||
}
|
174
disassembly/display/display.go
Normal file
174
disassembly/display/display.go
Normal file
|
@ -0,0 +1,174 @@
|
|||
package display
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gopher2600/hardware/cpu/execution"
|
||||
"gopher2600/hardware/cpu/instructions"
|
||||
"gopher2600/hardware/cpu/registers"
|
||||
"gopher2600/symbols"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Instruction is the fully annotated, columnular representation of
|
||||
// a instance of execution.Instruction
|
||||
type Instruction struct {
|
||||
Location string
|
||||
Bytecode string
|
||||
Address string
|
||||
Mnemonic string
|
||||
Operand string
|
||||
Cycles string
|
||||
Notes string
|
||||
}
|
||||
|
||||
// Format execution.Result and create a new instance of Instruction
|
||||
func Format(result execution.Result, symtable *symbols.Table) (*Instruction, error) {
|
||||
if symtable == nil {
|
||||
symtable = &symbols.Table{}
|
||||
}
|
||||
|
||||
d := &Instruction{}
|
||||
|
||||
// if the operator hasn't been decoded yet then use placeholder strings in
|
||||
// key columns
|
||||
if result.Defn == nil {
|
||||
d.Mnemonic = "???"
|
||||
d.Operand = "?"
|
||||
return d, nil
|
||||
}
|
||||
|
||||
d.Address = fmt.Sprintf("0x%04x", result.Address)
|
||||
|
||||
if v, ok := symtable.Locations.Symbols[result.Address]; ok {
|
||||
d.Location = v
|
||||
}
|
||||
|
||||
d.Mnemonic = result.Defn.Mnemonic
|
||||
|
||||
// operands
|
||||
var operand uint16
|
||||
|
||||
switch result.InstructionData.(type) {
|
||||
case uint8:
|
||||
operand = uint16(result.InstructionData.(uint8))
|
||||
d.Operand = fmt.Sprintf("$%02x", operand)
|
||||
case uint16:
|
||||
operand = uint16(result.InstructionData.(uint16))
|
||||
d.Operand = fmt.Sprintf("$%04x", operand)
|
||||
case nil:
|
||||
if result.Defn.Bytes == 2 {
|
||||
d.Operand = "??"
|
||||
} else if result.Defn.Bytes == 3 {
|
||||
d.Operand = "????"
|
||||
}
|
||||
}
|
||||
|
||||
// Bytecode
|
||||
if result.Final {
|
||||
switch result.Defn.Bytes {
|
||||
case 3:
|
||||
d.Bytecode = fmt.Sprintf("%02x", operand&0xff00>>8)
|
||||
fallthrough
|
||||
case 2:
|
||||
d.Bytecode = fmt.Sprintf("%02x %s", operand&0x00ff, d.Bytecode)
|
||||
fallthrough
|
||||
case 1:
|
||||
d.Bytecode = fmt.Sprintf("%02x %s", result.Defn.OpCode, d.Bytecode)
|
||||
default:
|
||||
d.Bytecode = fmt.Sprintf("(%d bytes) %s", result.Defn.Bytes, d.Bytecode)
|
||||
}
|
||||
|
||||
d.Bytecode = strings.TrimSpace(d.Bytecode)
|
||||
}
|
||||
|
||||
// ... and use assembler symbol for the operand if available/appropriate
|
||||
if result.InstructionData != nil && (d.Operand == "" || d.Operand[0] != '?') {
|
||||
if result.Defn.AddressingMode != instructions.Immediate {
|
||||
|
||||
switch result.Defn.Effect {
|
||||
case instructions.Flow:
|
||||
if result.Defn.AddressingMode == instructions.Relative {
|
||||
// relative labels. to get the correct label we have to
|
||||
// simulate what a successful branch instruction would do:
|
||||
|
||||
// -- we create a mock register with the instruction's
|
||||
// address as the initial value
|
||||
pc := registers.NewProgramCounter(result.Address)
|
||||
|
||||
// -- add the number of instruction bytes to get the PC as
|
||||
// it would be at the end of the instruction
|
||||
pc.Add(uint16(result.Defn.Bytes))
|
||||
|
||||
// -- 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)
|
||||
|
||||
// -- look up mock program counter value in symbol table
|
||||
if v, ok := symtable.Locations.Symbols[pc.Address()]; ok {
|
||||
d.Operand = v
|
||||
}
|
||||
|
||||
} else {
|
||||
if v, ok := symtable.Locations.Symbols[operand]; ok {
|
||||
d.Operand = v
|
||||
}
|
||||
}
|
||||
case instructions.Read:
|
||||
if v, ok := symtable.Read.Symbols[operand]; ok {
|
||||
d.Operand = v
|
||||
}
|
||||
case instructions.Write:
|
||||
fallthrough
|
||||
case instructions.RMW:
|
||||
if v, ok := symtable.Write.Symbols[operand]; ok {
|
||||
d.Operand = v
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// decorate operand with addressing mode indicators
|
||||
switch result.Defn.AddressingMode {
|
||||
case instructions.Implied:
|
||||
case instructions.Immediate:
|
||||
d.Operand = fmt.Sprintf("#%s", d.Operand)
|
||||
case instructions.Relative:
|
||||
case instructions.Absolute:
|
||||
case instructions.ZeroPage:
|
||||
case instructions.Indirect:
|
||||
d.Operand = fmt.Sprintf("(%s)", d.Operand)
|
||||
case instructions.PreIndexedIndirect:
|
||||
d.Operand = fmt.Sprintf("(%s,X)", d.Operand)
|
||||
case instructions.PostIndexedIndirect:
|
||||
d.Operand = fmt.Sprintf("(%s),Y", d.Operand)
|
||||
case instructions.AbsoluteIndexedX:
|
||||
d.Operand = fmt.Sprintf("%s,X", d.Operand)
|
||||
case instructions.AbsoluteIndexedY:
|
||||
d.Operand = fmt.Sprintf("%s,Y", d.Operand)
|
||||
case instructions.IndexedZeroPageX:
|
||||
d.Operand = fmt.Sprintf("%s,X", d.Operand)
|
||||
case instructions.IndexedZeroPageY:
|
||||
d.Operand = fmt.Sprintf("%s,Y", d.Operand)
|
||||
default:
|
||||
}
|
||||
|
||||
// cycles
|
||||
d.Cycles = fmt.Sprintf("%d", result.ActualCycles)
|
||||
|
||||
// notes
|
||||
if result.PageFault {
|
||||
d.Notes = fmt.Sprintf("%s page-fault", d.Notes)
|
||||
}
|
||||
if result.Bug != "" {
|
||||
d.Notes = fmt.Sprintf("%s * %s *", d.Notes, result.Bug)
|
||||
}
|
||||
|
||||
return d, nil
|
||||
}
|
12
disassembly/display/doc.go
Normal file
12
disassembly/display/doc.go
Normal file
|
@ -0,0 +1,12 @@
|
|||
// Package display facilitates the presentation of disassembled ROMs.
|
||||
//
|
||||
// The Instruction type stores the formatted parts of an individual
|
||||
// disassembled instruction. Instruction should be instantiated with the
|
||||
// Format command(). The Format() command takes an instance of execution.Result
|
||||
// and annotates it for easy reading.
|
||||
//
|
||||
// The actual presentation of formatted results to the user is outside of the
|
||||
// scope of this package but the Columns type is intended to help. The Update()
|
||||
// function should be used to ensure that column widths are enough for all
|
||||
// instances in a group of Instructions.
|
||||
package display
|
|
@ -1,11 +1,12 @@
|
|||
// Package disassembly coordinates the disassembly of cartridge memory. For
|
||||
// simple presentations of a cartridge the FromCartridge() function can be
|
||||
// used. Many debuggers will probably find it more useful to disassemble from
|
||||
// the memory of an already instantiated VCS.
|
||||
// Package disassembly coordinates the disassembly Atari2600 (6507) cartridges.
|
||||
//
|
||||
// For quick disassemblies the FromCartridge() function can be used. Many
|
||||
// debuggers will probably find it more useful however, to disassemble from the
|
||||
// memory of an already instantiated VCS.
|
||||
//
|
||||
// disasm, _ := disassembly.FromMemory(cartMem, symbols.NewTable())
|
||||
//
|
||||
// The FromMemory() function requires a valid instance of a symbols.Table. In
|
||||
// The FromMemory() function takes an instance of a symbols.Table, or nil. In
|
||||
// the example above, we've used the result of NewTable(); which is fine but
|
||||
// limits the potential of the disassembly package. For best results, the
|
||||
// symbols.ReadSymbolsFile() function should be used (see symbols package for
|
||||
|
@ -17,15 +18,15 @@
|
|||
// information from cartridge memory. In a nutshell:
|
||||
//
|
||||
// Linear disassembly decodes every possible address in the cartridge. if the
|
||||
// "execution" of the address succeeds it is stored in the linear table. Flow
|
||||
// disassembly on the other hand decodes only those cartridge addresses that
|
||||
// flow from the start adddress as the executable program unfolds.
|
||||
// "execution" of the address succeeds the result is stored. Flow disassembly
|
||||
// on the other hand decodes only those addresses that flow from the reset
|
||||
// adddress as the program unfolds.
|
||||
//
|
||||
// In flow disassembly it is hoped that every branch and subroutine is
|
||||
// considered. This is done by turning "flow control" off for the CPU and
|
||||
// handling branches manually in the disassembly package. However, it maybe
|
||||
// possible for correct CPU execution of the ROM to reach places not reachable
|
||||
// by the flow. For example:
|
||||
// In flow disassembly every branch and subroutine is considered. This is done
|
||||
// by turning the CPU's "flow control" off and handling each and every the
|
||||
// branch manually. Even with this method however, it is possible for a program
|
||||
// to expect to be taken somewhere (when executed normally) not reachable. For
|
||||
// example:
|
||||
//
|
||||
// - Addresses stuffed into the stack and RTS being called, without an explicit
|
||||
// JSR.
|
||||
|
@ -33,24 +34,27 @@
|
|||
// - Branching or jumping to non-cartridge memory. (ie. RAM) and executing code
|
||||
// there.
|
||||
//
|
||||
// The Analysis() function summarises any oddities like these that have been
|
||||
// detected.
|
||||
// The flow disassembly collates any possible oddities it encounters and the
|
||||
// Analysis() function can be used to summarise them.
|
||||
//
|
||||
// Compared to flow disassembly, linear disassembly looks at every memory
|
||||
// As already mentionied, linear disassembly looks at every possible memory
|
||||
// location. The downside of this is that a lot of what is found will be
|
||||
// nonsense (data segments never intended for execution, for instance). This
|
||||
// make linear disassembly unsuitable for presentation of the entire ROM.
|
||||
// Where linear disassembly *is* useful is a quick reference for an address
|
||||
// that you know contains a valid instruction.
|
||||
// make linear disassembly unsuitable for some applications. For example,
|
||||
// presenting a disassembly of an entire cartridge.
|
||||
//
|
||||
// Where linear disassembly *is* useful is for referencing an address that you
|
||||
// *know* contains a valid instruction - compare to flow disassembly where the
|
||||
// address might not have been reached during the disassembly process.
|
||||
//
|
||||
// Note that linear cannot do anything about the posibility of executing code
|
||||
// from area outside of cartridge space (ie. RAM).
|
||||
//
|
||||
// The flow/linear difference is invisible to the user of the disassembly
|
||||
// package. Instead, the functions Get(), Dump() and Grep() are used. These
|
||||
// functions use the most appropriate disassembly for the use case.
|
||||
// All that said, the flow/linear difference is invisible to the user of the
|
||||
// disassembly package. Instead, the functions Get(), Write() and Grep() are
|
||||
// used. These functions use the most appropriate disassembly for the use case.
|
||||
//
|
||||
// Dump() --> flow
|
||||
// Get() --> linear
|
||||
// Grep() --> flow
|
||||
// Write() --> flow
|
||||
// Get() --> linear
|
||||
// Grep() --> flow
|
||||
package disassembly
|
||||
|
|
45
disassembly/dump.go
Normal file
45
disassembly/dump.go
Normal file
|
@ -0,0 +1,45 @@
|
|||
package disassembly
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gopher2600/disassembly/display"
|
||||
"io"
|
||||
)
|
||||
|
||||
// Write writes the entire disassembly to io.Writer
|
||||
func (dsm *Disassembly) Write(output io.Writer, byteCode bool) {
|
||||
for bank := 0; bank < len(dsm.flow); bank++ {
|
||||
output.Write([]byte(fmt.Sprintf("--- bank %d ---\n", bank)))
|
||||
|
||||
for i := range dsm.flow[bank] {
|
||||
if d := dsm.flow[bank][i]; d != nil {
|
||||
dsm.WriteLine(output, byteCode, d)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WriteLine writes a single Instruction to io.Writer
|
||||
func (dsm *Disassembly) WriteLine(output io.Writer, byteCode bool, d *display.Instruction) {
|
||||
if d.Location != "" {
|
||||
output.Write([]byte(fmt.Sprintf(dsm.Columns.Fmt.Location, d.Location)))
|
||||
output.Write([]byte("\n"))
|
||||
}
|
||||
|
||||
if byteCode {
|
||||
output.Write([]byte(fmt.Sprintf(dsm.Columns.Fmt.Bytecode, d.Bytecode)))
|
||||
output.Write([]byte(" "))
|
||||
}
|
||||
|
||||
output.Write([]byte(fmt.Sprintf(dsm.Columns.Fmt.Address, d.Address)))
|
||||
output.Write([]byte(" "))
|
||||
output.Write([]byte(fmt.Sprintf(dsm.Columns.Fmt.Mnemonic, d.Mnemonic)))
|
||||
output.Write([]byte(" "))
|
||||
output.Write([]byte(fmt.Sprintf(dsm.Columns.Fmt.Operand, d.Operand)))
|
||||
output.Write([]byte(" "))
|
||||
output.Write([]byte(fmt.Sprintf(dsm.Columns.Fmt.Cycles, d.Cycles)))
|
||||
output.Write([]byte(" "))
|
||||
output.Write([]byte(fmt.Sprintf(dsm.Columns.Fmt.Notes, d.Notes)))
|
||||
|
||||
output.Write([]byte("\n"))
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
package disassembly
|
||||
|
||||
import (
|
||||
"gopher2600/hardware/cpu/instructions"
|
||||
"gopher2600/hardware/cpu/result"
|
||||
)
|
||||
|
||||
// Entry for every address in the cartridge
|
||||
type Entry struct {
|
||||
// if the type of entry is, or appears to be a, a valid instruction then
|
||||
// instructionDefinition will be non-null
|
||||
instructionDefinition *instructions.Definition
|
||||
|
||||
// to keep things simple, we're only keeping a string representation of the
|
||||
// disassembly. we used to keep a instance of result.verbose but after
|
||||
// some consideration, I don't like that - the result was obtained with an
|
||||
// incomplete or misleading context, and so cannot be relied upon to give
|
||||
// accurate information with regards to pagefaults etc. the simplest way
|
||||
// around this is to record a string representation, containing only the
|
||||
// information that's required.
|
||||
//
|
||||
// undefined if instructionDefinition == nil
|
||||
instruction string
|
||||
|
||||
// the styling used to format the instruction member above
|
||||
style result.Style
|
||||
}
|
||||
|
||||
func (ent Entry) String() string {
|
||||
if ent.instructionDefinition == nil {
|
||||
return ""
|
||||
}
|
||||
return ent.instruction
|
||||
}
|
||||
|
||||
// IsInstruction returns false if the entry does not represent an instruction
|
||||
func (ent Entry) IsInstruction() bool {
|
||||
return ent.instructionDefinition != nil
|
||||
}
|
|
@ -4,7 +4,6 @@ import (
|
|||
"gopher2600/errors"
|
||||
"gopher2600/hardware/cpu"
|
||||
"gopher2600/hardware/cpu/instructions"
|
||||
"gopher2600/hardware/cpu/result"
|
||||
"gopher2600/hardware/memory/memorymap"
|
||||
)
|
||||
|
||||
|
@ -32,23 +31,25 @@ func (dsm *Disassembly) flowDisassembly(mc *cpu.CPU) error {
|
|||
}
|
||||
}
|
||||
|
||||
// check validity of instruction result
|
||||
err = mc.LastResult.IsValid()
|
||||
if err != nil {
|
||||
// fail on invalid results
|
||||
if err := mc.LastResult.IsValid(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bank := dsm.cart.GetBank(mc.LastResult.Address)
|
||||
|
||||
// if we've seen this before then finish the disassembly
|
||||
if dsm.flow[bank][mc.LastResult.Address&disasmMask].IsInstruction() {
|
||||
if dsm.flow[bank][mc.LastResult.Address&disasmMask] != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
dsm.flow[bank][mc.LastResult.Address&disasmMask] = Entry{
|
||||
style: result.StyleDisasm,
|
||||
instruction: mc.LastResult.GetString(dsm.Symtable, result.StyleDisasm),
|
||||
instructionDefinition: mc.LastResult.Defn}
|
||||
d, err := dsm.FormatResult(mc.LastResult)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dsm.Columns.Update(d)
|
||||
|
||||
dsm.flow[bank][mc.LastResult.Address&disasmMask] = d
|
||||
|
||||
// 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
|
||||
|
|
|
@ -1,16 +1,25 @@
|
|||
package disassembly
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Grep searches the disassembly for the specified search string.
|
||||
func (dsm *Disassembly) Grep(output io.Writer, search string, caseSensitive bool, contextLines uint) {
|
||||
var s, m string
|
||||
// GrepScope limits the scope of the search
|
||||
type GrepScope int
|
||||
|
||||
ctx := make([]string, contextLines)
|
||||
// List of available scopes
|
||||
const (
|
||||
GrepMnemonic GrepScope = iota
|
||||
GrepOperand
|
||||
GrepAll
|
||||
)
|
||||
|
||||
// Grep searches the disassembly for the specified search string.
|
||||
func (dsm *Disassembly) Grep(output io.Writer, scope GrepScope, search string, caseSensitive bool) {
|
||||
var s, m string
|
||||
|
||||
if !caseSensitive {
|
||||
search = strings.ToUpper(search)
|
||||
|
@ -19,10 +28,25 @@ func (dsm *Disassembly) Grep(output io.Writer, search string, caseSensitive bool
|
|||
for bank := 0; bank < len(dsm.flow); bank++ {
|
||||
bankHeader := false
|
||||
for a := 0; a < len(dsm.flow[bank]); a++ {
|
||||
entry := dsm.flow[bank][a]
|
||||
d := dsm.flow[bank][a]
|
||||
|
||||
if d != nil {
|
||||
|
||||
// line representation of Instruction. we'll print this
|
||||
// in case of a match
|
||||
line := &bytes.Buffer{}
|
||||
dsm.WriteLine(line, false, d)
|
||||
|
||||
// limit scope of grep to the correct Instruction field
|
||||
switch scope {
|
||||
case GrepMnemonic:
|
||||
s = d.Mnemonic
|
||||
case GrepOperand:
|
||||
s = d.Operand
|
||||
case GrepAll:
|
||||
s = line.String()
|
||||
}
|
||||
|
||||
if entry.instructionDefinition != nil {
|
||||
s = entry.instruction
|
||||
if !caseSensitive {
|
||||
m = strings.ToUpper(s)
|
||||
} else {
|
||||
|
@ -30,6 +54,9 @@ func (dsm *Disassembly) Grep(output io.Writer, search string, caseSensitive bool
|
|||
}
|
||||
|
||||
if strings.Contains(m, search) {
|
||||
|
||||
// if we've not yet printed head for the current bank then
|
||||
// print it now
|
||||
if !bankHeader {
|
||||
if bank > 0 {
|
||||
output.Write([]byte("\n"))
|
||||
|
@ -37,28 +64,10 @@ func (dsm *Disassembly) Grep(output io.Writer, search string, caseSensitive bool
|
|||
|
||||
output.Write([]byte(fmt.Sprintf("--- bank %d ---\n", bank)))
|
||||
bankHeader = true
|
||||
} else if contextLines > 0 {
|
||||
output.Write([]byte("\n"))
|
||||
}
|
||||
|
||||
// print context
|
||||
for c := 0; c < len(ctx); c++ {
|
||||
// only write actual content. note that there is more often
|
||||
// than not, valid context. the main reason as far I can
|
||||
// see, for empty context are mistakes in disassembly
|
||||
if ctx[c] != "" {
|
||||
output.Write([]byte(ctx[c]))
|
||||
output.Write([]byte("\n"))
|
||||
}
|
||||
}
|
||||
|
||||
// print match
|
||||
output.Write([]byte(s))
|
||||
output.Write([]byte("\n"))
|
||||
|
||||
ctx = make([]string, contextLines)
|
||||
} else if contextLines > 0 {
|
||||
ctx = append(ctx[1:], s)
|
||||
// we've matched so print entire line
|
||||
output.Write(line.Bytes())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@ package disassembly
|
|||
|
||||
import (
|
||||
"gopher2600/hardware/cpu"
|
||||
"gopher2600/hardware/cpu/result"
|
||||
"gopher2600/hardware/memory/memorymap"
|
||||
)
|
||||
|
||||
|
@ -18,14 +17,20 @@ func (dsm *Disassembly) linearDisassembly(mc *cpu.CPU) error {
|
|||
// deliberately ignoring errors
|
||||
_ = mc.ExecuteInstruction(nil)
|
||||
|
||||
// check validity of instruction result and add if it "executed"
|
||||
// correctly
|
||||
if mc.LastResult.IsValid() == nil {
|
||||
dsm.linear[bank][address&disasmMask] = Entry{
|
||||
style: result.StyleBrief,
|
||||
instruction: mc.LastResult.GetString(dsm.Symtable, result.StyleBrief),
|
||||
instructionDefinition: mc.LastResult.Defn}
|
||||
// continue for loop on invalid results. we don't want to be as
|
||||
// discerning as in flowDisassembly(). the nature of
|
||||
// linearDisassembly() means that we're likely to try executing
|
||||
// invalid instructions. best just to ignore such errors.
|
||||
if mc.LastResult.IsValid() != nil {
|
||||
continue // for loop
|
||||
}
|
||||
|
||||
ent, err := dsm.FormatResult(mc.LastResult)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dsm.linear[bank][address&disasmMask] = ent
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -217,6 +217,7 @@ func disasm(md *modalflag.Modes) error {
|
|||
md.NewMode()
|
||||
|
||||
cartFormat := md.AddString("cartformat", "AUTO", "force use of cartridge format")
|
||||
bytecode := md.AddBool("bytecode", false, "include bytecode in disassembly")
|
||||
|
||||
p, err := md.Parse()
|
||||
if p != modalflag.ParseContinue {
|
||||
|
@ -235,12 +236,12 @@ func disasm(md *modalflag.Modes) error {
|
|||
if err != nil {
|
||||
// print what disassembly output we do have
|
||||
if dsm != nil {
|
||||
dsm.Dump(md.Output)
|
||||
dsm.Write(md.Output, *bytecode)
|
||||
}
|
||||
|
||||
return errors.New(errors.DisassemblyError, err)
|
||||
}
|
||||
dsm.Dump(md.Output)
|
||||
dsm.Write(md.Output, *bytecode)
|
||||
default:
|
||||
return fmt.Errorf("too many arguments for %s mode", md)
|
||||
}
|
||||
|
|
|
@ -3,9 +3,9 @@ package cpu
|
|||
import (
|
||||
"fmt"
|
||||
"gopher2600/errors"
|
||||
"gopher2600/hardware/cpu/execution"
|
||||
"gopher2600/hardware/cpu/instructions"
|
||||
"gopher2600/hardware/cpu/registers"
|
||||
"gopher2600/hardware/cpu/result"
|
||||
"gopher2600/hardware/memory/addresses"
|
||||
"gopher2600/hardware/memory/bus"
|
||||
"log"
|
||||
|
@ -41,7 +41,7 @@ type CPU struct {
|
|||
RdyFlg bool
|
||||
|
||||
// last result
|
||||
LastResult result.Instruction
|
||||
LastResult execution.Result
|
||||
|
||||
// silently ignore addressing errors unless StrictAddressing is true
|
||||
StrictAddressing bool
|
||||
|
@ -72,8 +72,6 @@ func NewCPU(mem bus.CPUBus) (*CPU, error) {
|
|||
mc.acc16 = registers.NewProgramCounter(0)
|
||||
|
||||
// set Final flag in LastResult to true because logically we can say it is.
|
||||
// this fixes what might be considered a bug when building the debugger
|
||||
// prompt. this is the best way of fixing it.
|
||||
mc.LastResult.Final = true
|
||||
|
||||
var err error
|
||||
|
@ -355,12 +353,13 @@ func (mc *CPU) ExecuteInstruction(cycleCallback func() error) error {
|
|||
}
|
||||
|
||||
// prepare new round of results
|
||||
mc.LastResult.Address = mc.PC.Address()
|
||||
mc.LastResult.Defn = nil
|
||||
mc.LastResult.Final = false
|
||||
mc.LastResult.Address = mc.PC.Address()
|
||||
mc.LastResult.InstructionData = nil
|
||||
mc.LastResult.ActualCycles = 0
|
||||
mc.LastResult.PageFault = false
|
||||
mc.LastResult.Bug = ""
|
||||
mc.LastResult.Final = false
|
||||
|
||||
// register end cycle callback
|
||||
defer func() {
|
||||
|
|
11
hardware/cpu/execution/doc.go
Normal file
11
hardware/cpu/execution/doc.go
Normal file
|
@ -0,0 +1,11 @@
|
|||
// Package execution tracks the result of instruction execution on the CPU.
|
||||
// The Result type stores detailed information about each instruction
|
||||
// encountered during a program execution on the CPU. A Result can then be used
|
||||
// to produce output for disassemblers and debuggers with the help of the
|
||||
// disassembly package.
|
||||
//
|
||||
// The Result.IsValid() function can be used to check whether results are
|
||||
// consistent with the instruction definition. The CPU pcakage doesn't call
|
||||
// this function because it would introduce unwanted performance penalties, but
|
||||
// it's probably okay to use in a debugging context.
|
||||
package execution
|
|
@ -1,24 +1,25 @@
|
|||
package result
|
||||
package execution
|
||||
|
||||
import (
|
||||
"gopher2600/hardware/cpu/instructions"
|
||||
)
|
||||
|
||||
// Instruction contains all the interesting information about a CPU
|
||||
// instruction. Including the address it was read from, the data that was used
|
||||
// during the execution of the instruction and a reference to the instruction
|
||||
// definition.
|
||||
// Result records the state/result of each instruction executed on the CPU.
|
||||
// Including the address it was read from, a reference to the instruction
|
||||
// definition, and other execution details.
|
||||
//
|
||||
// Other fields record inforamtion about the last execution of this specific
|
||||
// instruction, whether it caused a page fault, the actual number of cycles it
|
||||
// took and any information about known bugs that might have been triggered in
|
||||
// the CPU.
|
||||
// The Result type is updated every cycle during the execution of the emulated
|
||||
// CPU. As the execution continues, more information is acquired and detail
|
||||
// added to the Result.
|
||||
//
|
||||
// The Instruction type is update every cycle during execution in the emulated
|
||||
// CPU. As more information is known about the instuction it is added. The
|
||||
// final field indicates whether the last cycle has been executed and the
|
||||
// instruction decoding is complete.
|
||||
type Instruction struct {
|
||||
// The Final field indicates whether the last cycle of the instruction has been
|
||||
// executed. An instance of Result with a Final value of false can still be
|
||||
// used but with the caveat that the information is incomplete. Note that a
|
||||
// Defn of nil means the opcode hasn't even been decoded.
|
||||
type Result struct {
|
||||
// a reference to the instruction definition
|
||||
Defn *instructions.Definition
|
||||
|
||||
Address uint16
|
||||
|
||||
// it would be lovely to have a note of which cartridge bank the address is
|
||||
|
@ -26,17 +27,10 @@ type Instruction struct {
|
|||
// if you need to know the cartridge bank then you need to get it somehow
|
||||
// else.
|
||||
|
||||
// a reference to the instruction definition
|
||||
Defn *instructions.Definition
|
||||
|
||||
// instruction data is the actual instruction data. so, for example, in the
|
||||
// case of ranch instruction, instruction data is the offset value.
|
||||
// case of branch instruction, instruction data is the offset value.
|
||||
InstructionData interface{}
|
||||
|
||||
// whether this data has been finalised - some fields in this struct will
|
||||
// be undefined if Final is false
|
||||
Final bool
|
||||
|
||||
// the actual number of cycles taken by the instruction - usually the same
|
||||
// as Defn.Cycles but in the case of PageFaults and branches, this value
|
||||
// may be different
|
||||
|
@ -47,4 +41,8 @@ type Instruction struct {
|
|||
|
||||
// whether a known buggy code path (in the emulated CPU) was triggered
|
||||
Bug string
|
||||
|
||||
// whether this data has been finalised - some fields in this struct will
|
||||
// be undefined if Final is false
|
||||
Final bool
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package result
|
||||
package execution
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
@ -7,11 +7,9 @@ import (
|
|||
"reflect"
|
||||
)
|
||||
|
||||
// IsValid checks whether the instance of StepResult contains consistent data.
|
||||
//
|
||||
// Intended to be used during development of the CPU pacakge, to make sure
|
||||
// implementation hasn't gone off the rails.
|
||||
func (result Instruction) IsValid() error {
|
||||
// IsValid checks whether the instance of Result contains information
|
||||
// consistent with the instruction definition.
|
||||
func (result Result) IsValid() error {
|
||||
if !result.Final {
|
||||
return errors.New(errors.InvalidResult, "not checking an unfinalised InstructionResult")
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
// Package result handles storage and presentation of CPU instruction results.
|
||||
// The main product of this package is the Instruction type.
|
||||
//
|
||||
// The Instruction type is used by the CPU to store detailed information about
|
||||
// each instruction executed. This type can then be used to produce output for
|
||||
// disassemblers and debuggers with the GetString() function. The Style type
|
||||
// helps the user of the package to control how the string is constructed.
|
||||
//
|
||||
// The Instruction.IsValid() function can be used to check whether the results
|
||||
// CPU execution, as stored in the Insruction type, is consistent with the
|
||||
// instruction definition. For example, the PageFault field is set to true:
|
||||
// does the instruction definition suggest that this instruction can even cause
|
||||
// page faults under certain conditions.
|
||||
package result
|
|
@ -1,232 +0,0 @@
|
|||
package result
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gopher2600/hardware/cpu/instructions"
|
||||
"gopher2600/hardware/cpu/registers"
|
||||
"gopher2600/symbols"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// GetString returns a human readable version of InstructionResult, addresses
|
||||
// replaced with symbols if supplied symbols argument is not null.
|
||||
func (result Instruction) GetString(symtable *symbols.Table, style Style) string {
|
||||
if symtable == nil {
|
||||
panic(fmt.Sprintf("Instruction.GetString() requires a non-nil instance of symbols.Table"))
|
||||
}
|
||||
|
||||
// columns
|
||||
var hex string
|
||||
var label, programCounter string
|
||||
var operator, operand string
|
||||
var notes string
|
||||
|
||||
// include instruction address (and label) if this is the final result for
|
||||
// this particular instruction
|
||||
if result.Final && style.Has(StyleFlagAddress) {
|
||||
programCounter = fmt.Sprintf("0x%04x", result.Address)
|
||||
if style.Has(StyleFlagLocation) {
|
||||
if v, ok := symtable.Locations.Symbols[result.Address]; ok {
|
||||
label = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// use question marks where instruction hasn't been decoded yet
|
||||
|
||||
if result.Defn == nil {
|
||||
// nothing has been decoded yet
|
||||
operator = "???"
|
||||
|
||||
} else {
|
||||
// use mnemonic if specified in instruciton result
|
||||
operator = result.Defn.Mnemonic
|
||||
|
||||
// parse instruction result data ...
|
||||
var idx uint16
|
||||
switch result.InstructionData.(type) {
|
||||
case uint8:
|
||||
idx = uint16(result.InstructionData.(uint8))
|
||||
operand = fmt.Sprintf("$%02x", idx)
|
||||
case uint16:
|
||||
idx = uint16(result.InstructionData.(uint16))
|
||||
operand = fmt.Sprintf("$%04x", idx)
|
||||
case nil:
|
||||
if result.Defn.Bytes == 2 {
|
||||
operand = "??"
|
||||
} else if result.Defn.Bytes == 3 {
|
||||
operand = "????"
|
||||
}
|
||||
}
|
||||
|
||||
// (include byte code in output)
|
||||
if result.Final && style.Has(StyleFlagByteCode) {
|
||||
switch result.Defn.Bytes {
|
||||
case 3:
|
||||
hex = fmt.Sprintf("%02x", idx&0xff00>>8)
|
||||
fallthrough
|
||||
case 2:
|
||||
hex = fmt.Sprintf("%02x %s", idx&0x00ff, hex)
|
||||
fallthrough
|
||||
case 1:
|
||||
hex = fmt.Sprintf("%02x %s", result.Defn.OpCode, hex)
|
||||
default:
|
||||
hex = fmt.Sprintf("(%d bytes) %s", result.Defn.Bytes, hex)
|
||||
}
|
||||
}
|
||||
|
||||
// ... and use assembler symbol for the operand if available/appropriate
|
||||
if style.Has(StyleFlagSymbols) && result.InstructionData != nil && (operand == "" || operand[0] != '?') {
|
||||
if result.Defn.AddressingMode != instructions.Immediate {
|
||||
|
||||
switch result.Defn.Effect {
|
||||
case instructions.Flow:
|
||||
if result.Defn.AddressingMode == instructions.Relative {
|
||||
// relative labels. to get the correct label we have to
|
||||
// simulate what a successful branch instruction would do:
|
||||
|
||||
// -- we create a mock register with the instruction's
|
||||
// address as the initial value
|
||||
pc := registers.NewProgramCounter(result.Address)
|
||||
|
||||
// -- add the number of instruction bytes to get the PC as
|
||||
// it would be at the end of the instruction
|
||||
pc.Add(uint16(result.Defn.Bytes))
|
||||
|
||||
// -- 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 idx&0x0080 == 0x0080 {
|
||||
idx |= 0xff00
|
||||
}
|
||||
|
||||
// -- add the 2s-complement value to the mock program
|
||||
// counter
|
||||
pc.Add(idx)
|
||||
|
||||
// -- look up mock program counter value in symbol table
|
||||
if v, ok := symtable.Locations.Symbols[pc.Address()]; ok {
|
||||
operand = v
|
||||
}
|
||||
|
||||
} else {
|
||||
if v, ok := symtable.Locations.Symbols[idx]; ok {
|
||||
operand = v
|
||||
}
|
||||
}
|
||||
case instructions.Read:
|
||||
if v, ok := symtable.Read.Symbols[idx]; ok {
|
||||
operand = v
|
||||
}
|
||||
case instructions.Write:
|
||||
fallthrough
|
||||
case instructions.RMW:
|
||||
if v, ok := symtable.Write.Symbols[idx]; ok {
|
||||
operand = v
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// decorate operand with addressing mode indicators
|
||||
switch result.Defn.AddressingMode {
|
||||
case instructions.Implied:
|
||||
case instructions.Immediate:
|
||||
operand = fmt.Sprintf("#%s", operand)
|
||||
case instructions.Relative:
|
||||
case instructions.Absolute:
|
||||
case instructions.ZeroPage:
|
||||
case instructions.Indirect:
|
||||
operand = fmt.Sprintf("(%s)", operand)
|
||||
case instructions.PreIndexedIndirect:
|
||||
operand = fmt.Sprintf("(%s,X)", operand)
|
||||
case instructions.PostIndexedIndirect:
|
||||
operand = fmt.Sprintf("(%s),Y", operand)
|
||||
case instructions.AbsoluteIndexedX:
|
||||
operand = fmt.Sprintf("%s,X", operand)
|
||||
case instructions.AbsoluteIndexedY:
|
||||
operand = fmt.Sprintf("%s,Y", operand)
|
||||
case instructions.IndexedZeroPageX:
|
||||
operand = fmt.Sprintf("%s,X", operand)
|
||||
case instructions.IndexedZeroPageY:
|
||||
operand = fmt.Sprintf("%s,Y", operand)
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
if style.Has(StyleFlagCycles) {
|
||||
if result.Final {
|
||||
// result is of a complete instruction - add number of cycles it
|
||||
// actually took to execute
|
||||
notes = fmt.Sprintf("[%d]", result.ActualCycles)
|
||||
} else {
|
||||
// result is an interim result - indicate with [v], which means
|
||||
// video cycle
|
||||
notes = "[v]"
|
||||
}
|
||||
}
|
||||
|
||||
// add annotation
|
||||
if style.Has(StyleFlagNotes) {
|
||||
// add annotation for page-faults and known CPU bugs - these can occur
|
||||
// whether or not the result is not yet 'final'
|
||||
if result.PageFault {
|
||||
notes += " page-fault"
|
||||
}
|
||||
if result.Bug != "" {
|
||||
notes += fmt.Sprintf(" * %s *", result.Bug)
|
||||
}
|
||||
}
|
||||
|
||||
// force column widths
|
||||
if style.Has(StyleFlagColumns) {
|
||||
if style.Has(StyleFlagByteCode) {
|
||||
hex = columnise(hex, 8)
|
||||
}
|
||||
programCounter = columnise(programCounter, 6)
|
||||
operator = columnise(operator, 3)
|
||||
if symtable.MaxLocationWidth > 0 {
|
||||
label = columnise(label, symtable.MaxLocationWidth)
|
||||
} else {
|
||||
label = columnise(label, 0)
|
||||
}
|
||||
|
||||
if symtable.MaxSymbolWidth > 0 {
|
||||
// +3 to MaxSymbolWidth so that additional notation (parenthesis,
|
||||
// etc.) isn't cropped off.
|
||||
operand = columnise(operand, symtable.MaxSymbolWidth+3)
|
||||
} else {
|
||||
operand = columnise(operand, 7)
|
||||
}
|
||||
}
|
||||
|
||||
// build final string
|
||||
s := fmt.Sprintf("%s %s %s %s %s %s",
|
||||
hex,
|
||||
label,
|
||||
programCounter,
|
||||
operator,
|
||||
operand,
|
||||
notes)
|
||||
|
||||
if style.Has(StyleFlagCompact) {
|
||||
return strings.Trim(s, " ")
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// columnise forces the string into the given width. used for outputting
|
||||
// disassembly into columns
|
||||
func columnise(s string, width int) string {
|
||||
if width > len(s) {
|
||||
t := make([]byte, width-len(s))
|
||||
for i := 0; i < len(t); i++ {
|
||||
t[i] = ' '
|
||||
}
|
||||
s = fmt.Sprintf("%s%s", s, t)
|
||||
} else if width < len(s) {
|
||||
s = s[:width]
|
||||
}
|
||||
return s
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
package result
|
||||
|
||||
// Style specifies the elements to include in strings constructed by
|
||||
// Instruction.GetString(). Different styles can be ORed together to produce
|
||||
// custom styles.
|
||||
type Style int
|
||||
|
||||
// List of valid Style flags.
|
||||
const (
|
||||
// ByteCode flag causes the program data to be printed verbatim before
|
||||
// the disassembly
|
||||
StyleFlagByteCode Style = 0x01 << iota
|
||||
|
||||
// specifying StyleFlagSymbols or StyleFlagLocation has no effect if no
|
||||
// symbols type instance is available
|
||||
StyleFlagAddress
|
||||
StyleFlagSymbols
|
||||
StyleFlagLocation
|
||||
|
||||
// the number of cycles consumed by the instruction
|
||||
StyleFlagCycles
|
||||
|
||||
// include any useful notes about the disassembly. for example, whether a
|
||||
// page-fault occurred
|
||||
StyleFlagNotes
|
||||
|
||||
// force output into columns of suitable width
|
||||
StyleFlagColumns
|
||||
|
||||
// remove leading/trailing whitespace
|
||||
StyleFlagCompact
|
||||
)
|
||||
|
||||
// List of compound styles. For convenience.
|
||||
const (
|
||||
StyleExecution = StyleFlagAddress | StyleFlagSymbols | StyleFlagLocation | StyleFlagCycles | StyleFlagNotes | StyleFlagColumns
|
||||
StyleDisasm = StyleFlagByteCode | StyleFlagAddress | StyleFlagSymbols | StyleFlagLocation | StyleFlagColumns
|
||||
StyleBrief = StyleFlagSymbols | StyleFlagCompact
|
||||
)
|
||||
|
||||
// Has tests to see if style has the supplied flag in its definition
|
||||
func (style Style) Has(flag Style) bool {
|
||||
return style&flag == flag
|
||||
}
|
Loading…
Add table
Reference in a new issue