From 6616bc2b887b9a653e35e0aab6723a0a63ce4f8e Mon Sep 17 00:00:00 2001 From: steve Date: Mon, 23 Dec 2019 08:54:02 +0000 Subject: [PATCH] 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 --- debugger/commands.go | 92 ++++--- debugger/print.go | 12 +- debugger/prompt.go | 8 +- debugger/terminal/colorterm/output.go | 1 - debugger/terminal/commandline/doc.go | 3 +- debugger/terminal/plainterm/plainterm.go | 2 - debugger/watches.go | 5 +- disassembly/disassembly.go | 37 ++- disassembly/display/columns.go | 63 +++++ disassembly/display/display.go | 174 +++++++++++++ disassembly/display/doc.go | 12 + disassembly/doc.go | 54 ++-- disassembly/dump.go | 45 ++++ disassembly/entry.go | 39 --- disassembly/flow.go | 19 +- disassembly/grep.go | 63 +++-- disassembly/linear.go | 21 +- gopher2600.go | 5 +- hardware/cpu/cpu.go | 11 +- hardware/cpu/execution/doc.go | 11 + .../instruction.go => execution/result.go} | 42 ++-- .../cpu/{result => execution}/validity.go | 10 +- hardware/cpu/result/doc.go | 14 -- hardware/cpu/result/string.go | 232 ------------------ hardware/cpu/result/style.go | 44 ---- 25 files changed, 510 insertions(+), 509 deletions(-) create mode 100644 disassembly/display/columns.go create mode 100644 disassembly/display/display.go create mode 100644 disassembly/display/doc.go create mode 100644 disassembly/dump.go delete mode 100644 disassembly/entry.go create mode 100644 hardware/cpu/execution/doc.go rename hardware/cpu/{result/instruction.go => execution/result.go} (53%) rename hardware/cpu/{result => execution}/validity.go (88%) delete mode 100644 hardware/cpu/result/doc.go delete mode 100644 hardware/cpu/result/string.go delete mode 100644 hardware/cpu/result/style.go diff --git a/debugger/commands.go b/debugger/commands.go index c2cccb9d..cb26330e 100644 --- a/debugger/commands.go +++ b/debugger/commands.go @@ -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: diff --git a/debugger/print.go b/debugger/print.go index 07f11c73..df443be4 100644 --- a/debugger/print.go +++ b/debugger/print.go @@ -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 diff --git a/debugger/prompt.go b/debugger/prompt.go index a0a648f2..ebc9c72c 100644 --- a/debugger/prompt.go +++ b/debugger/prompt.go @@ -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)) diff --git a/debugger/terminal/colorterm/output.go b/debugger/terminal/colorterm/output.go index 9acb6b35..2f001380 100644 --- a/debugger/terminal/colorterm/output.go +++ b/debugger/terminal/colorterm/output.go @@ -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: diff --git a/debugger/terminal/commandline/doc.go b/debugger/terminal/commandline/doc.go index 990ec559..711b7ca8 100644 --- a/debugger/terminal/commandline/doc.go +++ b/debugger/terminal/commandline/doc.go @@ -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: diff --git a/debugger/terminal/plainterm/plainterm.go b/debugger/terminal/plainterm/plainterm.go index 51f4f8b5..49a5f401 100644 --- a/debugger/terminal/plainterm/plainterm.go +++ b/debugger/terminal/plainterm/plainterm.go @@ -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...) diff --git a/debugger/watches.go b/debugger/watches.go index a284b325..95de1919 100644 --- a/debugger/watches.go +++ b/debugger/watches.go @@ -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": diff --git a/disassembly/disassembly.go b/disassembly/disassembly.go index 90e85764..974c8369 100644 --- a/disassembly/disassembly.go +++ b/disassembly/disassembly.go @@ -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 diff --git a/disassembly/display/columns.go b/disassembly/display/columns.go new file mode 100644 index 00000000..7ef880a7 --- /dev/null +++ b/disassembly/display/columns.go @@ -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) + } +} diff --git a/disassembly/display/display.go b/disassembly/display/display.go new file mode 100644 index 00000000..fc3b2e1f --- /dev/null +++ b/disassembly/display/display.go @@ -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 +} diff --git a/disassembly/display/doc.go b/disassembly/display/doc.go new file mode 100644 index 00000000..37fc8b02 --- /dev/null +++ b/disassembly/display/doc.go @@ -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 diff --git a/disassembly/doc.go b/disassembly/doc.go index 3b8509c4..210a7120 100644 --- a/disassembly/doc.go +++ b/disassembly/doc.go @@ -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 diff --git a/disassembly/dump.go b/disassembly/dump.go new file mode 100644 index 00000000..515b7cb4 --- /dev/null +++ b/disassembly/dump.go @@ -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")) +} diff --git a/disassembly/entry.go b/disassembly/entry.go deleted file mode 100644 index 78662240..00000000 --- a/disassembly/entry.go +++ /dev/null @@ -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 -} diff --git a/disassembly/flow.go b/disassembly/flow.go index e03d5a5f..b20e256b 100644 --- a/disassembly/flow.go +++ b/disassembly/flow.go @@ -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 diff --git a/disassembly/grep.go b/disassembly/grep.go index 66e70cc3..28f9d0ef 100644 --- a/disassembly/grep.go +++ b/disassembly/grep.go @@ -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()) } } } diff --git a/disassembly/linear.go b/disassembly/linear.go index 94a3b4ea..348a5937 100644 --- a/disassembly/linear.go +++ b/disassembly/linear.go @@ -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 } } diff --git a/gopher2600.go b/gopher2600.go index b0b1191f..55bedf41 100644 --- a/gopher2600.go +++ b/gopher2600.go @@ -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) } diff --git a/hardware/cpu/cpu.go b/hardware/cpu/cpu.go index 5367ecc2..90acb25f 100644 --- a/hardware/cpu/cpu.go +++ b/hardware/cpu/cpu.go @@ -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() { diff --git a/hardware/cpu/execution/doc.go b/hardware/cpu/execution/doc.go new file mode 100644 index 00000000..5e77c28b --- /dev/null +++ b/hardware/cpu/execution/doc.go @@ -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 diff --git a/hardware/cpu/result/instruction.go b/hardware/cpu/execution/result.go similarity index 53% rename from hardware/cpu/result/instruction.go rename to hardware/cpu/execution/result.go index 79927e5e..6b47dff5 100644 --- a/hardware/cpu/result/instruction.go +++ b/hardware/cpu/execution/result.go @@ -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 } diff --git a/hardware/cpu/result/validity.go b/hardware/cpu/execution/validity.go similarity index 88% rename from hardware/cpu/result/validity.go rename to hardware/cpu/execution/validity.go index f4f9bb41..487b1fc5 100644 --- a/hardware/cpu/result/validity.go +++ b/hardware/cpu/execution/validity.go @@ -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") } diff --git a/hardware/cpu/result/doc.go b/hardware/cpu/result/doc.go deleted file mode 100644 index 413c3711..00000000 --- a/hardware/cpu/result/doc.go +++ /dev/null @@ -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 diff --git a/hardware/cpu/result/string.go b/hardware/cpu/result/string.go deleted file mode 100644 index cc44fbfa..00000000 --- a/hardware/cpu/result/string.go +++ /dev/null @@ -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 -} diff --git a/hardware/cpu/result/style.go b/hardware/cpu/result/style.go deleted file mode 100644 index 53841a52..00000000 --- a/hardware/cpu/result/style.go +++ /dev/null @@ -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 -}