mirror of
https://github.com/JetSetIlly/Gopher2600.git
synced 2024-05-20 13:48:02 -04:00
c9ee3fc20f
mem profile saved on ctrl+alt+m memory profiles can be used with "go tool pprof"
2152 lines
58 KiB
Go
2152 lines
58 KiB
Go
// This file is part of Gopher2600.
|
|
//
|
|
// Gopher2600 is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// Gopher2600 is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with Gopher2600. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
package debugger
|
|
|
|
import (
|
|
"encoding/hex"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"runtime"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/jetsetilly/gopher2600/coprocessor"
|
|
coproc_breakpoints "github.com/jetsetilly/gopher2600/coprocessor/developer/breakpoints"
|
|
"github.com/jetsetilly/gopher2600/coprocessor/developer/callstack"
|
|
"github.com/jetsetilly/gopher2600/coprocessor/developer/dwarf"
|
|
"github.com/jetsetilly/gopher2600/coprocessor/developer/faults"
|
|
"github.com/jetsetilly/gopher2600/coprocessor/developer/yield"
|
|
"github.com/jetsetilly/gopher2600/debugger/dbgmem"
|
|
"github.com/jetsetilly/gopher2600/debugger/govern"
|
|
"github.com/jetsetilly/gopher2600/debugger/script"
|
|
"github.com/jetsetilly/gopher2600/debugger/terminal"
|
|
"github.com/jetsetilly/gopher2600/debugger/terminal/commandline"
|
|
"github.com/jetsetilly/gopher2600/disassembly"
|
|
"github.com/jetsetilly/gopher2600/disassembly/symbols"
|
|
"github.com/jetsetilly/gopher2600/gui"
|
|
"github.com/jetsetilly/gopher2600/hardware/cpu/registers"
|
|
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/plusrom"
|
|
"github.com/jetsetilly/gopher2600/hardware/memory/memorymap"
|
|
"github.com/jetsetilly/gopher2600/hardware/peripherals/atarivox"
|
|
"github.com/jetsetilly/gopher2600/hardware/peripherals/controllers"
|
|
"github.com/jetsetilly/gopher2600/hardware/peripherals/savekey"
|
|
"github.com/jetsetilly/gopher2600/hardware/riot/ports"
|
|
"github.com/jetsetilly/gopher2600/hardware/riot/ports/plugging"
|
|
"github.com/jetsetilly/gopher2600/hardware/television"
|
|
"github.com/jetsetilly/gopher2600/hardware/television/coords"
|
|
"github.com/jetsetilly/gopher2600/logger"
|
|
"github.com/jetsetilly/gopher2600/patch"
|
|
"github.com/jetsetilly/gopher2600/resources/unique"
|
|
"github.com/jetsetilly/gopher2600/version"
|
|
)
|
|
|
|
var debuggerCommands *commandline.Commands
|
|
var scriptUnsafeCommands *commandline.Commands
|
|
|
|
// this init() function "compiles" the commandTemplate above into a more
|
|
// usuable form. It will cause the program to fail if the template is invalid.
|
|
func init() {
|
|
var err error
|
|
|
|
// parse command template
|
|
debuggerCommands, err = commandline.ParseCommandTemplate(commandTemplate)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
err = debuggerCommands.AddHelp(cmdHelp, helps)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
sort.Stable(debuggerCommands)
|
|
|
|
scriptUnsafeCommands, err = commandline.ParseCommandTemplate(scriptUnsafeTemplate)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
sort.Stable(scriptUnsafeCommands)
|
|
}
|
|
|
|
// parseCommand tokenises the input and processes the tokens.
|
|
func (dbg *Debugger) parseCommand(cmd string, scribe bool, echo bool) error {
|
|
tokens, err := dbg.tokeniseCommand(cmd, scribe, echo)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return dbg.processTokens(tokens)
|
|
}
|
|
|
|
// return tokenised command.
|
|
func (dbg *Debugger) tokeniseCommand(cmd string, scribe bool, echo bool) (*commandline.Tokens, error) {
|
|
// tokenise input
|
|
tokens := commandline.TokeniseInput(cmd)
|
|
|
|
// if there are no tokens in the input then continue with a default action
|
|
if tokens.Remaining() == 0 {
|
|
return dbg.tokeniseCommand("STEP", true, false)
|
|
}
|
|
|
|
// check validity of tokenised input
|
|
err := debuggerCommands.ValidateTokens(tokens)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// print normalised input if this is command from an interactive source
|
|
// and not an auto-command
|
|
if echo {
|
|
dbg.printLine(terminal.StyleEcho, tokens.String())
|
|
}
|
|
|
|
// test to see if command is allowed when recording/playing a script
|
|
if dbg.scriptScribe.IsActive() && scribe {
|
|
tokens.Reset()
|
|
|
|
err := scriptUnsafeCommands.ValidateTokens(tokens)
|
|
|
|
// fail when the tokens DO match the scriptUnsafe template (ie. when
|
|
// there is no err from the validate function)
|
|
if err == nil {
|
|
return nil, fmt.Errorf("'%s' is unsafe to use in scripts", tokens.String())
|
|
}
|
|
|
|
// record command if it auto is false (is not a result of an "auto" command
|
|
// eg. ONHALT). if there's an error then the script will be rolled back and
|
|
// the write removed.
|
|
dbg.scriptScribe.WriteInput(tokens.String())
|
|
}
|
|
|
|
return tokens, nil
|
|
}
|
|
|
|
// processTokensList call processTokens for each entry in the array of tokens.
|
|
// this is useful when we have already parsed and tokenised command input and
|
|
// simply want to rerun those commands.
|
|
//
|
|
// used by the ONSTEP, ONHALT and ONTRACE features.
|
|
func (dbg *Debugger) processTokensList(tokenGrp []*commandline.Tokens) error {
|
|
var err error
|
|
|
|
for _, t := range tokenGrp {
|
|
err = dbg.processTokens(t)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// process a single command (with arguments).
|
|
func (dbg *Debugger) processTokens(tokens *commandline.Tokens) error {
|
|
// check first token. if this token makes sense then we will consume the
|
|
// rest of the tokens appropriately
|
|
tokens.Reset()
|
|
command, _ := tokens.Get()
|
|
|
|
switch command {
|
|
default:
|
|
return fmt.Errorf("%s is not yet implemented", command)
|
|
|
|
case cmdHelp:
|
|
keyword, ok := tokens.Get()
|
|
if ok {
|
|
dbg.printLine(terminal.StyleHelp, debuggerCommands.Help(keyword))
|
|
} else {
|
|
dbg.printLine(terminal.StyleHelp, debuggerCommands.HelpOverview())
|
|
}
|
|
|
|
// we don't want the HELP command to appear in the script
|
|
dbg.scriptScribe.Rollback()
|
|
|
|
return nil
|
|
|
|
case cmdQuit:
|
|
if dbg.scriptScribe.IsActive() {
|
|
dbg.printLine(terminal.StyleFeedback, "ending script recording")
|
|
|
|
// we don't want the QUIT command to appear in the script
|
|
dbg.scriptScribe.Rollback()
|
|
|
|
return dbg.scriptScribe.EndSession()
|
|
} else {
|
|
dbg.running = false
|
|
}
|
|
|
|
case cmdReset:
|
|
// resetting in the middle of a CPU instruction requires the input loop
|
|
// to be unwound before continuing
|
|
dbg.unwindLoop(func() error {
|
|
// don't reset breakpoints, etc.
|
|
err := dbg.reset(false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
dbg.printLine(terminal.StyleFeedback, "machine reset")
|
|
return nil
|
|
})
|
|
|
|
case cmdRun:
|
|
dbg.runUntilHalt = true
|
|
dbg.continueEmulation = true
|
|
return nil
|
|
|
|
case cmdHalt:
|
|
dbg.haltImmediately = true
|
|
|
|
case cmdStep:
|
|
adjAmount := 1
|
|
back := false
|
|
|
|
if tk, ok := tokens.Get(); ok {
|
|
switch tk {
|
|
case "BACK":
|
|
back = true
|
|
adjAmount *= -1
|
|
|
|
case "OVER":
|
|
// if next expected opcode is JSR then add a volatile breakpoint to the return address
|
|
//
|
|
// at the time of writing this comment, if execution is currently in RAM then
|
|
// GetEntryByAddress() will return nil. this means that if "JSR" is encounterd during
|
|
// that time, the STEP OVER command will not work as expected
|
|
e := dbg.Disasm.GetEntryByAddress(dbg.vcs.CPU.PC.Address())
|
|
if e != nil && e.Operator == "jsr" {
|
|
brk := commandline.TokeniseInput(fmt.Sprintf("%#4x", dbg.vcs.CPU.PC.Address()+3))
|
|
dbg.halting.volatileBreakpoints.parseCommand(brk)
|
|
|
|
// breakpoing will take many cycles to trigger so we need to run the emulation
|
|
dbg.runUntilHalt = true
|
|
}
|
|
|
|
default:
|
|
tokens.Unget()
|
|
}
|
|
}
|
|
|
|
// get mode
|
|
mode, _ := tokens.Get()
|
|
mode = strings.ToUpper(mode)
|
|
|
|
if back {
|
|
// step backwards
|
|
|
|
var coords coords.TelevisionCoords
|
|
|
|
switch mode {
|
|
case "":
|
|
// use current quantum state
|
|
switch dbg.Quantum() {
|
|
case govern.QuantumInstruction:
|
|
coords = dbg.cpuBoundaryLastInstruction
|
|
case govern.QuantumCycle:
|
|
coords = dbg.vcs.TV.AdjCoords(television.AdjCycle, adjAmount)
|
|
case govern.QuantumClock:
|
|
coords = dbg.vcs.TV.AdjCoords(television.AdjClock, adjAmount)
|
|
}
|
|
|
|
case "INSTRUCTION":
|
|
dbg.setQuantum(govern.QuantumInstruction)
|
|
coords = dbg.cpuBoundaryLastInstruction
|
|
case "CYCLE":
|
|
dbg.setQuantum(govern.QuantumCycle)
|
|
coords = dbg.vcs.TV.AdjCoords(television.AdjCycle, adjAmount)
|
|
case "CLOCK":
|
|
dbg.setQuantum(govern.QuantumClock)
|
|
coords = dbg.vcs.TV.AdjCoords(television.AdjClock, adjAmount)
|
|
case "SCANLINE":
|
|
coords = dbg.vcs.TV.AdjCoords(television.AdjScanline, adjAmount)
|
|
case "FRAME":
|
|
coords = dbg.vcs.TV.AdjCoords(television.AdjFrame, adjAmount)
|
|
default:
|
|
return fmt.Errorf("unknown STEP BACK mode (%s)", mode)
|
|
}
|
|
|
|
dbg.setState(govern.Rewinding, govern.RewindingBackwards)
|
|
dbg.unwindLoop(func() error {
|
|
dbg.catchupContext = catchupStepBack
|
|
return dbg.Rewind.GotoCoords(coords)
|
|
})
|
|
|
|
} else {
|
|
// step forwards
|
|
|
|
switch mode {
|
|
case "":
|
|
// continue with current quantum state
|
|
|
|
// if quantum is not the QuantumClock and CPU is not RDY then STEP
|
|
// is best implemented as TRAP RDY. this means that the emulation
|
|
// will stop on the next instruction boundary and will also skip
|
|
// over instructions that trigger WSYNC
|
|
//
|
|
// this behaviour is more intuitive to the user because it means
|
|
// they don't have to step over every cycle during the WSYNC state
|
|
if dbg.Quantum() != govern.QuantumClock && !dbg.vcs.CPU.RdyFlg {
|
|
// create volatile RDY trap
|
|
_ = dbg.halting.volatileTraps.parseCommand(commandline.TokeniseInput("RDY"))
|
|
dbg.runUntilHalt = true
|
|
|
|
// when the RDY flag changes the input loop will think it's
|
|
// inside a video step. we need to force the loop to return
|
|
// to the non-video step loop
|
|
dbg.stepOutOfVideoStepInputLoop = true
|
|
}
|
|
case "INSTRUCTION":
|
|
dbg.setQuantum(govern.QuantumInstruction)
|
|
case "CYCLE":
|
|
dbg.setQuantum(govern.QuantumCycle)
|
|
case "CLOCK":
|
|
dbg.setQuantum(govern.QuantumClock)
|
|
default:
|
|
// token not recognised so forward rest of tokens to the volatile
|
|
// traps parser
|
|
tokens.Unget()
|
|
_ = dbg.halting.volatileTraps.parseCommand(tokens)
|
|
|
|
// trap may take many cycles to trigger
|
|
dbg.runUntilHalt = true
|
|
}
|
|
|
|
// continue emulation. note that we don't set runUntilHalt except in the
|
|
// specific cases above in the above switch. this is because we do no
|
|
// always set a volatile trap. without a trap the emulation will just
|
|
// run until it receives a HALT instruction.
|
|
dbg.continueEmulation = true
|
|
}
|
|
|
|
case cmdQuantum:
|
|
mode, _ := tokens.Get()
|
|
mode = strings.ToUpper(mode)
|
|
switch mode {
|
|
case "INSTRUCTION":
|
|
dbg.setQuantum(govern.QuantumInstruction)
|
|
case "CYCLE":
|
|
dbg.setQuantum(govern.QuantumCycle)
|
|
case "CLOCK":
|
|
dbg.setQuantum(govern.QuantumClock)
|
|
default:
|
|
dbg.printLine(terminal.StyleFeedback, "set to %s", strings.ToUpper(dbg.Quantum().String()))
|
|
}
|
|
|
|
case cmdScript:
|
|
option, _ := tokens.Get()
|
|
switch strings.ToUpper(option) {
|
|
case "RECORD":
|
|
var err error
|
|
saveFile, _ := tokens.Get()
|
|
err = dbg.scriptScribe.StartSession(saveFile)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// we don't want SCRIPT RECORD command to appear in the script
|
|
dbg.scriptScribe.Rollback()
|
|
|
|
return nil
|
|
|
|
case "END":
|
|
// we don't want SCRIPT END command to appear in the script
|
|
dbg.scriptScribe.Rollback()
|
|
|
|
return dbg.scriptScribe.EndSession()
|
|
|
|
default:
|
|
// run a script
|
|
scr, err := script.RescribeScript(option)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if dbg.scriptScribe.IsActive() {
|
|
// if we're currently recording a script we want to write this
|
|
// command to the new script file but indicate that we'll be
|
|
// entering a new script and so don't want to repeat the
|
|
// commands from that script
|
|
err := dbg.scriptScribe.StartPlayback()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
defer dbg.scriptScribe.EndPlayback()
|
|
}
|
|
|
|
err = dbg.inputLoop(scr, false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
case cmdRewind:
|
|
// note that we calling the rewind.GotoFrame() functions directly and not
|
|
// using the debugger.PushRewind() function.
|
|
arg, ok := tokens.Get()
|
|
if ok {
|
|
// stop emulation on rewind
|
|
dbg.runUntilHalt = false
|
|
|
|
if arg == "LAST" {
|
|
dbg.setState(govern.Rewinding, govern.RewindingForwards)
|
|
dbg.unwindLoop(dbg.Rewind.GotoLast)
|
|
} else if arg == "SUMMARY" {
|
|
dbg.printLine(terminal.StyleInstrument, dbg.Rewind.Peephole())
|
|
} else {
|
|
frame, _ := strconv.Atoi(arg)
|
|
coords := dbg.TV().GetCoords()
|
|
if frame != coords.Frame {
|
|
if frame < coords.Frame {
|
|
dbg.setState(govern.Rewinding, govern.RewindingBackwards)
|
|
} else {
|
|
dbg.setState(govern.Rewinding, govern.RewindingForwards)
|
|
}
|
|
dbg.unwindLoop(func() error {
|
|
err := dbg.Rewind.GotoFrame(frame)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
case cmdComparison:
|
|
arg, ok := tokens.Get()
|
|
if ok {
|
|
switch arg {
|
|
case "LOCK":
|
|
dbg.Rewind.LockComparison(true)
|
|
case "UNLOCK":
|
|
dbg.Rewind.LockComparison(false)
|
|
if dbg.State() == govern.Running {
|
|
dbg.Rewind.UpdateComparison()
|
|
}
|
|
default:
|
|
frame, _ := strconv.Atoi(arg)
|
|
dbg.Rewind.SetComparison(frame)
|
|
}
|
|
}
|
|
|
|
case cmdGoto:
|
|
fromCoords := dbg.vcs.TV.GetCoords()
|
|
toCoords := fromCoords
|
|
|
|
if s, ok := tokens.Get(); ok {
|
|
toCoords.Clock, _ = strconv.Atoi(s)
|
|
if s, ok := tokens.Get(); ok {
|
|
toCoords.Scanline, _ = strconv.Atoi(s)
|
|
if s, ok := tokens.Get(); ok {
|
|
toCoords.Frame, _ = strconv.Atoi(s)
|
|
}
|
|
}
|
|
}
|
|
|
|
if coords.GreaterThan(toCoords, fromCoords) {
|
|
dbg.setState(govern.Rewinding, govern.RewindingForwards)
|
|
} else {
|
|
dbg.setState(govern.Rewinding, govern.RewindingBackwards)
|
|
}
|
|
dbg.unwindLoop(func() error {
|
|
err := dbg.Rewind.GotoCoords(toCoords)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
})
|
|
|
|
case cmdInsert:
|
|
dbg.unwindLoop(func() error {
|
|
filename, _ := tokens.Get()
|
|
err := dbg.insertCartridge(filename)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
})
|
|
|
|
case cmdCartridge:
|
|
arg, ok := tokens.Get()
|
|
if ok {
|
|
switch arg {
|
|
case "PATH":
|
|
dbg.printLine(
|
|
terminal.StyleInstrument,
|
|
dbg.vcs.Mem.Cart.Filename,
|
|
)
|
|
|
|
case "NAME":
|
|
dbg.printLine(
|
|
terminal.StyleInstrument,
|
|
dbg.vcs.Mem.Cart.ShortName,
|
|
)
|
|
|
|
case "MAPPER":
|
|
dbg.printLine(
|
|
terminal.StyleInstrument,
|
|
dbg.vcs.Mem.Cart.ID(),
|
|
)
|
|
|
|
case "CONTAINER":
|
|
dbg.printLine(
|
|
terminal.StyleInstrument,
|
|
dbg.vcs.Mem.Cart.ContainerID(),
|
|
)
|
|
|
|
case "MAPPEDBANKS":
|
|
dbg.printLine(
|
|
terminal.StyleInstrument,
|
|
dbg.vcs.Mem.Cart.MappedBanks(),
|
|
)
|
|
|
|
case "HASH":
|
|
dbg.printLine(
|
|
terminal.StyleFeedback,
|
|
dbg.vcs.Mem.Cart.Hash,
|
|
)
|
|
|
|
case "STATIC":
|
|
// !!TODO: poke/peek static cartridge static data areas
|
|
if bus := dbg.vcs.Mem.Cart.GetStaticBus(); bus != nil {
|
|
static := bus.GetStatic()
|
|
if static != nil {
|
|
dbg.printLine(terminal.StyleFeedback, "cartridge has a static data area")
|
|
} else {
|
|
dbg.printLine(terminal.StyleFeedback, "cartridge has no static data area")
|
|
}
|
|
} else {
|
|
dbg.printLine(terminal.StyleFeedback, "cartridge has no static data area")
|
|
}
|
|
case "REGISTERS":
|
|
// !!TODO: poke/peek cartridge registers
|
|
if bus := dbg.vcs.Mem.Cart.GetRegistersBus(); bus != nil {
|
|
dbg.printLine(terminal.StyleInstrument, bus.GetRegisters().String())
|
|
} else {
|
|
dbg.printLine(terminal.StyleFeedback, "cartridge has no registers")
|
|
}
|
|
|
|
case "RAM":
|
|
// cartridge RAM is accessible through the normal VCS buses so
|
|
// the normal peek/poke commands will work
|
|
if bus := dbg.vcs.Mem.Cart.GetRAMbus(); bus != nil {
|
|
ram := bus.GetRAM()
|
|
if ram != nil {
|
|
s := &strings.Builder{}
|
|
for b := 0; b < len(ram); b++ {
|
|
s.WriteString(ram[b].Label + "\n")
|
|
s.WriteString(strings.Repeat("-", len(ram[b].Label)))
|
|
s.WriteString("\n")
|
|
s.WriteString(hex.Dump(ram[b].Data))
|
|
s.WriteString("\n\n")
|
|
}
|
|
|
|
dbg.printLine(terminal.StyleInstrument, s.String())
|
|
} else {
|
|
dbg.printLine(terminal.StyleFeedback, "cartridge has no RAM")
|
|
}
|
|
} else {
|
|
dbg.printLine(terminal.StyleFeedback, "cartridge has no RAM")
|
|
}
|
|
|
|
case "DUMP":
|
|
romdump, err := dbg.vcs.Mem.Cart.ROMDump()
|
|
if err != nil {
|
|
dbg.printLine(terminal.StyleFeedback, err.Error())
|
|
} else {
|
|
dbg.printLine(terminal.StyleFeedback, fmt.Sprintf("rom dumped to %s", romdump))
|
|
}
|
|
}
|
|
} else {
|
|
dbg.printLine(terminal.StyleInstrument, dbg.vcs.Mem.Cart.String())
|
|
}
|
|
|
|
case cmdPatch:
|
|
f, _ := tokens.Get()
|
|
patched, err := patch.CartridgeMemory(dbg.vcs.Mem.Cart, f)
|
|
if err != nil {
|
|
dbg.printLine(terminal.StyleError, "%v", err)
|
|
if patched {
|
|
dbg.printLine(terminal.StyleError, "error during patching. cartridge might be unusable.")
|
|
}
|
|
return nil
|
|
}
|
|
if patched {
|
|
dbg.printLine(terminal.StyleFeedback, "cartridge patched")
|
|
}
|
|
|
|
case cmdDisasm:
|
|
bytecode := false
|
|
|
|
arg, ok := tokens.Get()
|
|
if ok {
|
|
switch arg {
|
|
case "REDUX":
|
|
err := dbg.Disasm.FromMemory()
|
|
if err != nil {
|
|
dbg.printLine(terminal.StyleFeedback, err.Error())
|
|
}
|
|
return nil
|
|
case "BYTECODE":
|
|
bytecode = true
|
|
}
|
|
}
|
|
|
|
attr := disassembly.ColumnAttr{
|
|
ByteCode: bytecode,
|
|
Label: true,
|
|
Cycles: true,
|
|
}
|
|
|
|
s := strings.Builder{}
|
|
err := dbg.Disasm.Write(&s, attr)
|
|
if err != nil {
|
|
dbg.printLine(terminal.StyleFeedback, err.Error())
|
|
}
|
|
|
|
dbg.printLine(terminal.StyleFeedback, s.String())
|
|
|
|
case cmdGrep:
|
|
scope := disassembly.GrepAll
|
|
output := &strings.Builder{}
|
|
|
|
s, _ := tokens.Get()
|
|
switch strings.ToUpper(s) {
|
|
case "COPROC":
|
|
search, _ := tokens.Get()
|
|
|
|
addr, err := strconv.ParseUint(search, 0, 32)
|
|
if err != nil {
|
|
dbg.printLine(terminal.StyleError, "search term for COPROC must be a 32bit address")
|
|
return nil
|
|
}
|
|
|
|
var ln *dwarf.SourceLine
|
|
|
|
dbg.CoProcDev.BorrowSource(func(src *dwarf.Source) {
|
|
if src == nil {
|
|
dbg.printLine(terminal.StyleError, "no source files found")
|
|
return
|
|
}
|
|
|
|
var ok bool
|
|
|
|
ln, ok = src.LinesByAddress[uint64(addr)]
|
|
if !ok {
|
|
dbg.printLine(terminal.StyleError, fmt.Sprintf("address %x does not correspond to a source line", addr))
|
|
} else {
|
|
dbg.printLine(terminal.StyleFeedback, ln.String())
|
|
}
|
|
})
|
|
|
|
_ = dbg.gui.SetFeature(gui.ReqCoProcSourceLine, ln)
|
|
return nil
|
|
|
|
case "OPERATOR":
|
|
scope = disassembly.GrepOperator
|
|
case "OPERAND":
|
|
scope = disassembly.GrepOperand
|
|
default:
|
|
tokens.Unget()
|
|
}
|
|
|
|
search, _ := tokens.Get()
|
|
err := dbg.Disasm.Grep(output, scope, search, false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if output.Len() == 0 {
|
|
dbg.printLine(terminal.StyleError, "%s not found in disassembly", search)
|
|
} else {
|
|
dbg.printLine(terminal.StyleFeedback, output.String())
|
|
}
|
|
|
|
case cmdSymbol:
|
|
tok, _ := tokens.Get()
|
|
switch strings.ToUpper(tok) {
|
|
case "LIST":
|
|
option, ok := tokens.Get()
|
|
if ok {
|
|
switch strings.ToUpper(option) {
|
|
default:
|
|
// already caught by command line ValidateTokens()
|
|
|
|
case "LABELS":
|
|
dbg.dbgmem.Sym.ListLabels(dbg.writerInStyle(terminal.StyleFeedback))
|
|
|
|
case "READ":
|
|
dbg.dbgmem.Sym.ListReadSymbols(dbg.writerInStyle(terminal.StyleFeedback))
|
|
|
|
case "WRITE":
|
|
dbg.dbgmem.Sym.ListWriteSymbols(dbg.writerInStyle(terminal.StyleFeedback))
|
|
}
|
|
} else {
|
|
dbg.dbgmem.Sym.ListSymbols(dbg.writerInStyle(terminal.StyleFeedback))
|
|
}
|
|
|
|
default:
|
|
symbol := tok
|
|
|
|
symSearch := dbg.Disasm.Sym.SearchBySymbol(symbol, symbols.SearchLabel)
|
|
if symSearch != nil {
|
|
ai := dbg.dbgmem.GetAddressInfo(symSearch.Address, true)
|
|
if ai != nil {
|
|
dbg.printLine(terminal.StyleFeedback, "%s [LABEL]", ai.String())
|
|
} else {
|
|
symSearch = nil
|
|
}
|
|
}
|
|
|
|
aiRead := dbg.dbgmem.GetAddressInfo(symbol, true)
|
|
if aiRead != nil {
|
|
dbg.printLine(terminal.StyleFeedback, "%s [READ]", aiRead.String())
|
|
}
|
|
|
|
aiWrite := dbg.dbgmem.GetAddressInfo(symbol, false)
|
|
if aiWrite != nil {
|
|
dbg.printLine(terminal.StyleFeedback, "%s [WRITE]", aiWrite.String())
|
|
}
|
|
|
|
if symSearch == nil && aiRead == nil && aiWrite == nil {
|
|
dbg.printLine(terminal.StyleFeedback, "%s not found in any symbol table", symbol)
|
|
}
|
|
}
|
|
|
|
case cmdOnHalt:
|
|
if tokens.Remaining() == 0 {
|
|
if len(dbg.commandOnHalt) == 0 {
|
|
dbg.printLine(terminal.StyleFeedback, "auto-command on halt: OFF")
|
|
} else {
|
|
s := strings.Builder{}
|
|
for _, c := range dbg.commandOnHalt {
|
|
s.WriteString(c.String())
|
|
s.WriteString("; ")
|
|
}
|
|
dbg.printLine(terminal.StyleFeedback, "command on halt: %s", strings.TrimSuffix(s.String(), "; "))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
var input string
|
|
|
|
option, _ := tokens.Get()
|
|
switch strings.ToUpper(option) {
|
|
case "OFF":
|
|
dbg.commandOnHalt = dbg.commandOnHalt[:0]
|
|
dbg.printLine(terminal.StyleFeedback, "no command on halt")
|
|
return nil
|
|
|
|
case "ON":
|
|
dbg.commandOnHalt = dbg.commandOnHaltStored
|
|
for _, c := range dbg.commandOnHalt {
|
|
dbg.printLine(terminal.StyleFeedback, "auto-command on halt: %s", c)
|
|
}
|
|
return nil
|
|
|
|
default:
|
|
// token isn't one we recognise so push it back onto the token queue
|
|
tokens.Unget()
|
|
|
|
// use remaininder of command line to form the ONHALT command sequence
|
|
input = strings.TrimSpace(tokens.Remainder())
|
|
tokens.End()
|
|
}
|
|
|
|
// empty list of tokens. taking note of existing command - not the same
|
|
// as commandOnHaltStored because ONHALT might be OFF
|
|
existingOnHalt := dbg.commandOnHalt
|
|
dbg.commandOnHalt = dbg.commandOnHalt[:0]
|
|
|
|
// tokenise commands to check for integrity
|
|
for _, s := range strings.Split(input, ",") {
|
|
toks, err := dbg.tokeniseCommand(s, false, false)
|
|
if err != nil {
|
|
dbg.commandOnHalt = existingOnHalt
|
|
return err
|
|
}
|
|
dbg.commandOnHalt = append(dbg.commandOnHalt, toks)
|
|
}
|
|
|
|
// make a copy of
|
|
dbg.commandOnHaltStored = dbg.commandOnHalt
|
|
|
|
// display the new ONHALT command(s)
|
|
s := strings.Builder{}
|
|
for _, c := range dbg.commandOnHalt {
|
|
s.WriteString(c.String())
|
|
s.WriteString("; ")
|
|
}
|
|
dbg.printLine(terminal.StyleFeedback, "command on halt: %s", strings.TrimSuffix(s.String(), "; "))
|
|
|
|
return nil
|
|
|
|
case cmdOnStep:
|
|
if tokens.Remaining() == 0 {
|
|
if len(dbg.commandOnStep) == 0 {
|
|
dbg.printLine(terminal.StyleFeedback, "no command on step")
|
|
} else {
|
|
s := strings.Builder{}
|
|
for _, c := range dbg.commandOnStep {
|
|
s.WriteString(c.String())
|
|
s.WriteString("; ")
|
|
}
|
|
dbg.printLine(terminal.StyleFeedback, "command on step: %s", strings.TrimSuffix(s.String(), "; "))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
var input string
|
|
|
|
option, _ := tokens.Get()
|
|
switch strings.ToUpper(option) {
|
|
case "OFF":
|
|
dbg.commandOnStep = dbg.commandOnStep[:0]
|
|
dbg.printLine(terminal.StyleFeedback, "auto-command on step: OFF")
|
|
return nil
|
|
|
|
case "ON":
|
|
dbg.commandOnStep = dbg.commandOnStepStored
|
|
for _, c := range dbg.commandOnStep {
|
|
dbg.printLine(terminal.StyleFeedback, "auto-command on step: %s", c)
|
|
}
|
|
return nil
|
|
|
|
default:
|
|
// token isn't one we recognise so push it back onto the token queue
|
|
tokens.Unget()
|
|
|
|
// use remaininder of command line to form the ONSTEP command sequence
|
|
input = strings.TrimSpace(tokens.Remainder())
|
|
tokens.End()
|
|
}
|
|
|
|
// empty list of tokens. taking note of existing command - not the same
|
|
// as commandOnStepStored because ONSTEP might be OFF
|
|
existingOnStep := dbg.commandOnStep
|
|
dbg.commandOnStep = dbg.commandOnStep[:0]
|
|
|
|
// tokenise commands to check for integrity
|
|
for _, s := range strings.Split(input, ",") {
|
|
toks, err := dbg.tokeniseCommand(s, false, false)
|
|
if err != nil {
|
|
dbg.commandOnStep = existingOnStep
|
|
return err
|
|
}
|
|
dbg.commandOnStep = append(dbg.commandOnStep, toks)
|
|
}
|
|
|
|
// store new commandOnStep
|
|
dbg.commandOnStepStored = dbg.commandOnStep
|
|
|
|
// display the new ONSTEP command(s)
|
|
s := strings.Builder{}
|
|
for _, c := range dbg.commandOnStep {
|
|
s.WriteString(c.String())
|
|
s.WriteString("; ")
|
|
}
|
|
dbg.printLine(terminal.StyleFeedback, "command on step: %s", strings.TrimSuffix(s.String(), "; "))
|
|
|
|
return nil
|
|
|
|
case cmdOnTrace:
|
|
if tokens.Remaining() == 0 {
|
|
if len(dbg.commandOnTrace) == 0 {
|
|
dbg.printLine(terminal.StyleFeedback, "no command on trace")
|
|
} else {
|
|
s := strings.Builder{}
|
|
for _, c := range dbg.commandOnTrace {
|
|
s.WriteString(c.String())
|
|
s.WriteString("; ")
|
|
}
|
|
dbg.printLine(terminal.StyleFeedback, "command on trace: %s", strings.TrimSuffix(s.String(), "; "))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
var input string
|
|
|
|
option, _ := tokens.Get()
|
|
switch strings.ToUpper(option) {
|
|
case "OFF":
|
|
dbg.commandOnTrace = dbg.commandOnTrace[:0]
|
|
dbg.printLine(terminal.StyleFeedback, "auto-command on trace: OFF")
|
|
return nil
|
|
|
|
case "ON":
|
|
dbg.commandOnTrace = dbg.commandOnTraceStored
|
|
for _, c := range dbg.commandOnTrace {
|
|
dbg.printLine(terminal.StyleFeedback, "auto-command on trace: %s", c)
|
|
}
|
|
return nil
|
|
|
|
default:
|
|
// token isn't one we recognise so push it back onto the token queue
|
|
tokens.Unget()
|
|
|
|
// use remaininder of command line to form the ONTRACE command sequence
|
|
input = strings.TrimSpace(tokens.Remainder())
|
|
tokens.End()
|
|
}
|
|
|
|
// empty list of tokens. taking note of existing command
|
|
existingOnTrace := dbg.commandOnTrace
|
|
dbg.commandOnTrace = dbg.commandOnTrace[:0]
|
|
|
|
// tokenise commands to check for integrity
|
|
for _, s := range strings.Split(input, ",") {
|
|
toks, err := dbg.tokeniseCommand(s, false, false)
|
|
if err != nil {
|
|
dbg.commandOnTrace = existingOnTrace
|
|
return err
|
|
}
|
|
dbg.commandOnTrace = append(dbg.commandOnTrace, toks)
|
|
}
|
|
|
|
// store new commandOnTrace
|
|
dbg.commandOnTraceStored = dbg.commandOnTrace
|
|
|
|
// display the new ONTRACE command(s)
|
|
s := strings.Builder{}
|
|
for _, c := range dbg.commandOnTrace {
|
|
s.WriteString(c.String())
|
|
s.WriteString("; ")
|
|
}
|
|
dbg.printLine(terminal.StyleFeedback, "command on trace: %s", strings.TrimSuffix(s.String(), "; "))
|
|
|
|
return nil
|
|
|
|
case cmdLast:
|
|
// if debugger is running in a non-instruction quantum then the live disasm
|
|
// information will not have been updated. for the purposes of the last
|
|
// instruction however, we definitely do want that information to be
|
|
// current
|
|
if dbg.running && dbg.quantum.Load() != govern.QuantumInstruction {
|
|
dbg.liveBankInfo = dbg.vcs.Mem.Cart.GetBank(dbg.vcs.CPU.PC.Address())
|
|
dbg.liveDisasmEntry = dbg.Disasm.ExecutedEntry(dbg.liveBankInfo, dbg.vcs.CPU.LastResult, true, dbg.vcs.CPU.PC.Value())
|
|
}
|
|
|
|
if dbg.liveDisasmEntry == nil || dbg.liveDisasmEntry.Result.Defn == nil {
|
|
dbg.printLine(terminal.StyleFeedback, "no instruction decoded yet")
|
|
return nil
|
|
}
|
|
|
|
// whether to show bytecode
|
|
bytecode := false
|
|
|
|
option, ok := tokens.Get()
|
|
if ok {
|
|
switch strings.ToUpper(option) {
|
|
case "DEFN":
|
|
if dbg.vcs.CPU.LastResult.Defn == nil {
|
|
dbg.printLine(terminal.StyleFeedback, "no instruction decoded yet")
|
|
} else {
|
|
dbg.printLine(terminal.StyleFeedback, "%s", dbg.vcs.CPU.LastResult.Defn)
|
|
}
|
|
return nil
|
|
|
|
case "BYTECODE":
|
|
bytecode = true
|
|
}
|
|
}
|
|
|
|
s := strings.Builder{}
|
|
|
|
if dbg.vcs.Mem.Cart.NumBanks() > 1 {
|
|
s.WriteString(fmt.Sprintf("[%s] ", dbg.liveBankInfo))
|
|
}
|
|
s.WriteString(dbg.liveDisasmEntry.GetField(disassembly.FldAddress))
|
|
s.WriteString(" ")
|
|
if bytecode {
|
|
s.WriteString(dbg.liveDisasmEntry.GetField(disassembly.FldBytecode))
|
|
s.WriteString(" ")
|
|
}
|
|
s.WriteString(dbg.liveDisasmEntry.GetField(disassembly.FldOperator))
|
|
s.WriteString(" ")
|
|
s.WriteString(dbg.liveDisasmEntry.GetField(disassembly.FldOperand))
|
|
s.WriteString(" ")
|
|
s.WriteString(dbg.liveDisasmEntry.GetField(disassembly.FldCycles))
|
|
s.WriteString(" ")
|
|
s.WriteString(dbg.liveDisasmEntry.GetField(disassembly.FldNotes))
|
|
|
|
// change terminal output style depending on condition of last CPU result
|
|
if dbg.liveDisasmEntry.Result.Final {
|
|
dbg.printLine(terminal.StyleInstructionStep, s.String())
|
|
} else {
|
|
dbg.printLine(terminal.StyleSubStep, s.String())
|
|
}
|
|
|
|
case cmdMemMap:
|
|
address, ok := tokens.Get()
|
|
if ok {
|
|
// if an address argument has been specified then map the address
|
|
// in a read and write context and display the information
|
|
|
|
// if hasMapped is false after the read/write mappings then the
|
|
// address could no be resolved and we print an appropriate notice
|
|
// to the user
|
|
hasMapped := false
|
|
|
|
s := strings.Builder{}
|
|
|
|
ai := dbg.dbgmem.GetAddressInfo(address, true)
|
|
if ai != nil {
|
|
hasMapped = true
|
|
s.WriteString("Read:\n")
|
|
if ai.Address != ai.MappedAddress {
|
|
s.WriteString(fmt.Sprintf(" %#04x maps to %#04x ", ai.Address, ai.MappedAddress))
|
|
} else {
|
|
s.WriteString(fmt.Sprintf(" %#04x ", ai.Address))
|
|
}
|
|
s.WriteString(fmt.Sprintf("in area %s\n", ai.Area.String()))
|
|
if ai.Symbol != "" {
|
|
s.WriteString(fmt.Sprintf(" labelled as %s\n", ai.Symbol))
|
|
}
|
|
}
|
|
ai = dbg.dbgmem.GetAddressInfo(address, false)
|
|
if ai != nil {
|
|
hasMapped = true
|
|
s.WriteString("Write:\n")
|
|
if ai.Address != ai.MappedAddress {
|
|
s.WriteString(fmt.Sprintf(" %#04x maps to %#04x ", ai.Address, ai.MappedAddress))
|
|
} else {
|
|
s.WriteString(fmt.Sprintf(" %#04x ", ai.Address))
|
|
}
|
|
s.WriteString(fmt.Sprintf("in area %s\n", ai.Area.String()))
|
|
if ai.Symbol != "" {
|
|
s.WriteString(fmt.Sprintf(" labelled as %s\n", ai.Symbol))
|
|
}
|
|
}
|
|
|
|
// print results
|
|
if hasMapped {
|
|
dbg.printLine(terminal.StyleInstrument, "%s", s.String())
|
|
} else {
|
|
dbg.printLine(terminal.StyleFeedback, fmt.Sprintf("%v is not a mappable address", address))
|
|
}
|
|
} else {
|
|
// without an address argument print the memorymap summary table
|
|
dbg.printLine(terminal.StyleInstrument, "%v", memorymap.Summary())
|
|
}
|
|
|
|
case cmdCPU:
|
|
action, ok := tokens.Get()
|
|
if ok {
|
|
switch strings.ToUpper(action) {
|
|
case "STATUS":
|
|
action, ok = tokens.Get()
|
|
if ok {
|
|
target, _ := tokens.Get()
|
|
var targetVal *bool
|
|
switch target {
|
|
case "S":
|
|
targetVal = &dbg.vcs.CPU.Status.Sign
|
|
case "O":
|
|
targetVal = &dbg.vcs.CPU.Status.Overflow
|
|
case "B":
|
|
targetVal = &dbg.vcs.CPU.Status.Break
|
|
case "D":
|
|
targetVal = &dbg.vcs.CPU.Status.DecimalMode
|
|
case "I":
|
|
targetVal = &dbg.vcs.CPU.Status.InterruptDisable
|
|
case "Z":
|
|
targetVal = &dbg.vcs.CPU.Status.Zero
|
|
case "C":
|
|
targetVal = &dbg.vcs.CPU.Status.Carry
|
|
}
|
|
|
|
switch action {
|
|
case "SET":
|
|
*targetVal = true
|
|
case "UNSET":
|
|
*targetVal = false
|
|
case "TOGGLE":
|
|
*targetVal = !*targetVal
|
|
}
|
|
} else {
|
|
dbg.printLine(terminal.StyleInstrument, dbg.vcs.CPU.Status.String())
|
|
}
|
|
|
|
case "SET":
|
|
target, _ := tokens.Get()
|
|
value, _ := tokens.Get()
|
|
|
|
target = strings.ToUpper(target)
|
|
if target == "PC" {
|
|
// program counter is a 16 bit number
|
|
v, err := strconv.ParseUint(value, 0, 16)
|
|
if err != nil {
|
|
dbg.printLine(terminal.StyleError, "value must be a positive 16 bit number")
|
|
}
|
|
|
|
dbg.vcs.CPU.PC.Load(uint16(v))
|
|
} else {
|
|
// 6507 registers are 8 bit
|
|
v, err := strconv.ParseUint(value, 0, 8)
|
|
if err != nil {
|
|
dbg.printLine(terminal.StyleError, "value must be a positive 8 bit number")
|
|
}
|
|
|
|
var reg *registers.Register
|
|
switch strings.ToUpper(target) {
|
|
case "A":
|
|
reg = &dbg.vcs.CPU.A
|
|
case "X":
|
|
reg = &dbg.vcs.CPU.X
|
|
case "Y":
|
|
reg = &dbg.vcs.CPU.Y
|
|
case "SP":
|
|
reg = &dbg.vcs.CPU.SP.Register
|
|
}
|
|
|
|
reg.Load(uint8(v))
|
|
}
|
|
|
|
default:
|
|
// already caught by command line ValidateTokens()
|
|
}
|
|
} else {
|
|
dbg.printLine(terminal.StyleInstrument, dbg.vcs.CPU.String())
|
|
}
|
|
|
|
case cmdBus:
|
|
dbg.printLine(terminal.StyleInstrument, dbg.vcs.Mem.String())
|
|
action, ok := tokens.Get()
|
|
if ok {
|
|
switch strings.ToUpper(action) {
|
|
case "DETAIL":
|
|
_, area := memorymap.MapAddress(dbg.vcs.Mem.AddressBus, !dbg.vcs.Mem.LastCPUWrite)
|
|
access := "reading"
|
|
if dbg.vcs.Mem.LastCPUWrite {
|
|
access = "writing"
|
|
}
|
|
dbg.printLine(terminal.StyleInstrument, fmt.Sprintf("%s (%s)", area.String(), access))
|
|
default:
|
|
// already caught by command line ValidateTokens()
|
|
}
|
|
}
|
|
|
|
case cmdPeek:
|
|
// get first address token
|
|
a, ok := tokens.Get()
|
|
|
|
for ok {
|
|
// perform peek
|
|
ai, err := dbg.dbgmem.Peek(a)
|
|
if err != nil {
|
|
dbg.printLine(terminal.StyleError, "%s", err)
|
|
} else {
|
|
dbg.printLine(terminal.StyleInstrument, ai.String())
|
|
}
|
|
|
|
// loop through all addresses
|
|
a, ok = tokens.Get()
|
|
}
|
|
|
|
case cmdPoke:
|
|
// get address token
|
|
a, _ := tokens.Get()
|
|
|
|
// convert address. note that the calls to dbgmem.poke() also call
|
|
// MapAddress(). the reason we map the address here is because we want
|
|
// a numeric address that we can iterate with in the for loop below.
|
|
// simply converting to a number is no good because we want the user to
|
|
// be able to specify an address by name, so we may as well just call
|
|
// MapAddress(), even if it does seem redundant
|
|
//
|
|
// see comment in DbgMem.Poke() for why we treat the address as a
|
|
// "read" address
|
|
ai := dbg.dbgmem.GetAddressInfo(a, true)
|
|
if ai == nil {
|
|
dbg.printLine(terminal.StyleError, fmt.Sprintf("%s: %v", dbgmem.PokeError, a))
|
|
return nil
|
|
}
|
|
addr := ai.MappedAddress
|
|
|
|
// get (first) value token
|
|
v, ok := tokens.Get()
|
|
|
|
for ok {
|
|
val, err := strconv.ParseUint(v, 0, 8)
|
|
if err != nil {
|
|
dbg.printLine(terminal.StyleError, "value must be an 8 bit number (%s)", v)
|
|
v, ok = tokens.Get()
|
|
continue // for loop (without advancing address)
|
|
}
|
|
|
|
ai, err := dbg.dbgmem.Poke(addr, uint8(val))
|
|
if err != nil {
|
|
dbg.printLine(terminal.StyleError, "%s", err)
|
|
} else {
|
|
dbg.printLine(terminal.StyleInstrument, ai.String())
|
|
}
|
|
|
|
// loop through all values
|
|
v, ok = tokens.Get()
|
|
addr++
|
|
}
|
|
|
|
case cmdSwap:
|
|
// get address token
|
|
a, _ := tokens.Get()
|
|
b, _ := tokens.Get()
|
|
|
|
ai, err := dbg.dbgmem.Peek(a)
|
|
if err != nil {
|
|
dbg.printLine(terminal.StyleError, "%s", err)
|
|
return nil
|
|
}
|
|
|
|
bi, err := dbg.dbgmem.Peek(b)
|
|
if err != nil {
|
|
dbg.printLine(terminal.StyleError, "%s", err)
|
|
return nil
|
|
}
|
|
|
|
if _, err := dbg.dbgmem.Poke(ai.MappedAddress, bi.Data); err != nil {
|
|
dbg.printLine(terminal.StyleError, "%s", err)
|
|
return nil
|
|
}
|
|
|
|
if _, err := dbg.dbgmem.Poke(bi.MappedAddress, ai.Data); err != nil {
|
|
dbg.printLine(terminal.StyleError, "%s", err)
|
|
return nil
|
|
}
|
|
|
|
aj, err := dbg.dbgmem.Peek(ai.Address)
|
|
if err != nil {
|
|
dbg.printLine(terminal.StyleError, "%s", err)
|
|
return nil
|
|
}
|
|
|
|
bj, err := dbg.dbgmem.Peek(bi.Address)
|
|
if err != nil {
|
|
dbg.printLine(terminal.StyleError, "%s", err)
|
|
return nil
|
|
}
|
|
|
|
dbg.printLine(terminal.StyleFeedback, fmt.Sprintf("%04x: %02x->%02x and %04x: %02x->%02x", ai.Address, ai.Data, aj.Data, bi.Address, bi.Data, bj.Data))
|
|
|
|
case cmdRAM:
|
|
dbg.printLine(terminal.StyleInstrument, dbg.vcs.Mem.RAM.String())
|
|
|
|
case cmdTIA:
|
|
arg, _ := tokens.Get()
|
|
switch arg {
|
|
case "HMOVE":
|
|
dbg.printLine(terminal.StyleInstrument, dbg.vcs.TIA.Hmove.String())
|
|
default:
|
|
dbg.printLine(terminal.StyleInstrument, dbg.vcs.TIA.String())
|
|
}
|
|
|
|
case cmdRIOT:
|
|
arg, _ := tokens.Get()
|
|
switch arg {
|
|
case "TIMER":
|
|
dbg.printLine(terminal.StyleInstrument, dbg.vcs.RIOT.Timer.String())
|
|
case "PORTS":
|
|
fallthrough
|
|
default:
|
|
dbg.printLine(terminal.StyleInstrument, dbg.vcs.RIOT.Ports.String())
|
|
}
|
|
|
|
case cmdAudio:
|
|
dbg.printLine(terminal.StyleInstrument, dbg.vcs.TIA.Audio.String())
|
|
|
|
case cmdTV:
|
|
option, ok := tokens.Get()
|
|
if ok {
|
|
option = strings.ToUpper(option)
|
|
switch option {
|
|
case "SPEC":
|
|
newspec, ok := tokens.Get()
|
|
if ok {
|
|
// unknown specifciations already handled by ValidateTokens()
|
|
err := dbg.vcs.TV.SetSpec(newspec)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
spec := dbg.vcs.TV.GetFrameInfo().Spec
|
|
s := strings.Builder{}
|
|
s.WriteString(spec.ID)
|
|
dbg.printLine(terminal.StyleInstrument, s.String())
|
|
default:
|
|
// already caught by command line ValidateTokens()
|
|
}
|
|
} else {
|
|
dbg.printLine(terminal.StyleInstrument, dbg.vcs.TV.String())
|
|
}
|
|
|
|
// information about the machine (sprites, playfield)
|
|
case cmdPlayer:
|
|
plyr := -1
|
|
|
|
arg, _ := tokens.Get()
|
|
switch arg {
|
|
case "0":
|
|
plyr = 0
|
|
case "1":
|
|
plyr = 1
|
|
}
|
|
|
|
switch plyr {
|
|
case 0:
|
|
dbg.printLine(terminal.StyleInstrument, dbg.vcs.TIA.Video.Player0.String())
|
|
|
|
case 1:
|
|
dbg.printLine(terminal.StyleInstrument, dbg.vcs.TIA.Video.Player1.String())
|
|
|
|
default:
|
|
dbg.printLine(terminal.StyleInstrument, dbg.vcs.TIA.Video.Player0.String())
|
|
dbg.printLine(terminal.StyleInstrument, dbg.vcs.TIA.Video.Player1.String())
|
|
}
|
|
|
|
case cmdMissile:
|
|
miss := -1
|
|
|
|
arg, _ := tokens.Get()
|
|
switch arg {
|
|
case "0":
|
|
miss = 0
|
|
case "1":
|
|
miss = 1
|
|
}
|
|
|
|
switch miss {
|
|
case 0:
|
|
dbg.printLine(terminal.StyleInstrument, dbg.vcs.TIA.Video.Missile0.String())
|
|
|
|
case 1:
|
|
dbg.printLine(terminal.StyleInstrument, dbg.vcs.TIA.Video.Missile1.String())
|
|
|
|
default:
|
|
dbg.printLine(terminal.StyleInstrument, dbg.vcs.TIA.Video.Missile0.String())
|
|
dbg.printLine(terminal.StyleInstrument, dbg.vcs.TIA.Video.Missile1.String())
|
|
}
|
|
|
|
case cmdBall:
|
|
dbg.printLine(terminal.StyleInstrument, dbg.vcs.TIA.Video.Ball.String())
|
|
|
|
case cmdPlayfield:
|
|
dbg.printLine(terminal.StyleInstrument, dbg.vcs.TIA.Video.Playfield.String())
|
|
|
|
case cmdPlusROM:
|
|
plusrom, ok := dbg.vcs.Mem.Cart.GetContainer().(*plusrom.PlusROM)
|
|
if !ok {
|
|
dbg.printLine(terminal.StyleError, "not a plusrom cartridge")
|
|
return nil
|
|
}
|
|
|
|
option, _ := tokens.Get()
|
|
|
|
switch option {
|
|
case "NICK":
|
|
nick, _ := tokens.Get()
|
|
err := dbg.vcs.Env.Prefs.PlusROM.Nick.Set(nick)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = dbg.vcs.Env.Prefs.PlusROM.Save()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
case "ID":
|
|
id, _ := tokens.Get()
|
|
err := dbg.vcs.Env.Prefs.PlusROM.ID.Set(id)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = dbg.vcs.Env.Prefs.PlusROM.Save()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
case "HOST":
|
|
ai := plusrom.GetAddrInfo()
|
|
host, _ := tokens.Get()
|
|
plusrom.SetAddrInfo(host, ai.Path)
|
|
case "PATH":
|
|
ai := plusrom.GetAddrInfo()
|
|
path, _ := tokens.Get()
|
|
plusrom.SetAddrInfo(ai.Host, path)
|
|
default:
|
|
dbg.printLine(terminal.StyleFeedback, fmt.Sprintf("Nick: %s", dbg.vcs.Env.Prefs.PlusROM.Nick.String()))
|
|
dbg.printLine(terminal.StyleFeedback, fmt.Sprintf("ID: %s", dbg.vcs.Env.Prefs.PlusROM.ID.String()))
|
|
ai := plusrom.GetAddrInfo()
|
|
dbg.printLine(terminal.StyleFeedback, fmt.Sprintf("Host: %s", ai.Host))
|
|
dbg.printLine(terminal.StyleFeedback, fmt.Sprintf("Path: %s", ai.Path))
|
|
}
|
|
|
|
case cmdCoProc:
|
|
bus := dbg.vcs.Mem.Cart.GetCoProcBus()
|
|
if bus == nil {
|
|
dbg.printLine(terminal.StyleError, "cartridge does not have a coprocessor")
|
|
return nil
|
|
}
|
|
|
|
option, _ := tokens.Get()
|
|
|
|
switch option {
|
|
case "TOP":
|
|
top := 10 // default of top 10
|
|
|
|
arg, ok := tokens.Get()
|
|
if ok {
|
|
n, err := strconv.ParseInt(arg, 0, 32)
|
|
if err != nil {
|
|
dbg.printLine(terminal.StyleError, fmt.Sprintf("%s is not a number", arg))
|
|
return nil
|
|
}
|
|
top = int(n)
|
|
}
|
|
|
|
dbg.CoProcDev.BorrowSource(func(src *dwarf.Source) {
|
|
if src == nil {
|
|
dbg.printLine(terminal.StyleError, "no source files found")
|
|
return
|
|
}
|
|
|
|
for i := 0; i < top; i++ {
|
|
f := src.SortedFunctions.Functions[i]
|
|
dbg.printLine(terminal.StyleFeedback, fmt.Sprintf("%02d: %s", i, f.Name))
|
|
}
|
|
})
|
|
|
|
case "LIST":
|
|
arg, _ := tokens.Get()
|
|
switch arg {
|
|
|
|
case "FAULTS":
|
|
dbg.CoProcDev.BorrowFaults(func(flt *faults.Faults) {
|
|
w := dbg.writerInStyle(terminal.StyleFeedback)
|
|
flt.WriteLog(w)
|
|
})
|
|
|
|
case "SOURCEFILES":
|
|
dbg.CoProcDev.BorrowSource(func(src *dwarf.Source) {
|
|
if src == nil {
|
|
dbg.printLine(terminal.StyleError, "no source files found")
|
|
return
|
|
}
|
|
for _, fn := range src.Files {
|
|
dbg.printLine(terminal.StyleFeedback, fn.Filename)
|
|
}
|
|
})
|
|
case "FUNCTIONS":
|
|
dbg.CoProcDev.BorrowSource(func(src *dwarf.Source) {
|
|
if src == nil {
|
|
dbg.printLine(terminal.StyleError, "no source files found")
|
|
return
|
|
}
|
|
for _, n := range src.FunctionNames {
|
|
fn := src.Functions[n]
|
|
dbg.printLine(terminal.StyleFeedback, fn.String())
|
|
}
|
|
})
|
|
default:
|
|
}
|
|
|
|
case "MEM":
|
|
bus := dbg.vcs.Mem.Cart.GetStaticBus()
|
|
if bus == nil {
|
|
dbg.printLine(terminal.StyleError, "cartridge does not have any coprocessor memory")
|
|
return nil
|
|
}
|
|
|
|
static := bus.GetStatic()
|
|
if static == nil {
|
|
dbg.printLine(terminal.StyleError, "cartridge does not have any coprocessor memory")
|
|
return nil
|
|
}
|
|
|
|
arg, _ := tokens.Get()
|
|
switch arg {
|
|
case "DUMP":
|
|
dump := func(name string) {
|
|
if data, ok := static.Reference(name); ok {
|
|
fn := unique.Filename(fmt.Sprintf("dump_%s", name), dbg.cartload.Name)
|
|
fn = fmt.Sprintf("%s.bin", fn)
|
|
err := os.WriteFile(fn, data, 0644)
|
|
if err != nil {
|
|
dbg.printLine(terminal.StyleError, fmt.Sprintf("error writing %s", fn))
|
|
} else {
|
|
dbg.printLine(terminal.StyleFeedback, fn)
|
|
}
|
|
}
|
|
}
|
|
|
|
if arg, ok := tokens.Get(); ok {
|
|
dump(arg)
|
|
} else {
|
|
for _, seg := range static.Segments() {
|
|
dump(seg.Name)
|
|
}
|
|
}
|
|
default:
|
|
for _, seg := range static.Segments() {
|
|
s := fmt.Sprintf("%s: %08x to %08x", seg.Name, seg.Origin, seg.Memtop)
|
|
dbg.printLine(terminal.StyleFeedback, s)
|
|
}
|
|
}
|
|
|
|
case "REGS":
|
|
coproc := bus.GetCoProc()
|
|
|
|
// list registers in order until we get a not-ok reply
|
|
regs := func(group coprocessor.ExtendedRegisterGroup) {
|
|
s := strings.Builder{}
|
|
for r := group.Start; r <= group.End; r++ {
|
|
v, f, ok := coproc.RegisterFormatted(r)
|
|
if !ok {
|
|
dbg.printLine(terminal.StyleError,
|
|
fmt.Sprintf("coprocessor doesn't have the %d register in the %s group", r, group.Name))
|
|
return
|
|
}
|
|
if group.Formatted {
|
|
s.WriteString(fmt.Sprintf("%s: %s [%08x]\t", group.Label(r), f, v))
|
|
} else {
|
|
s.WriteString(fmt.Sprintf("%s: %08x\t", group.Label(r), v))
|
|
}
|
|
|
|
if (r-group.Start+1)%3 == 0 {
|
|
dbg.printLine(terminal.StyleFeedback, s.String())
|
|
s.Reset()
|
|
}
|
|
}
|
|
if s.Len() > 0 {
|
|
dbg.printLine(terminal.StyleFeedback, s.String())
|
|
}
|
|
}
|
|
|
|
// use named group if supplied or default to core group
|
|
spec := coproc.RegisterSpec()
|
|
if arg, ok := tokens.Get(); ok {
|
|
if group, ok := spec.Group(arg); ok {
|
|
regs(group)
|
|
} else {
|
|
dbg.printLine(terminal.StyleError, fmt.Sprintf("coprocessor doesn't have a %s register group", arg))
|
|
}
|
|
} else {
|
|
if group, ok := spec.Group(coprocessor.ExtendedRegisterCoreGroup); ok {
|
|
regs(group)
|
|
} else {
|
|
dbg.printLine(terminal.StyleError, "coprocessor doesn't seem to have any registers")
|
|
}
|
|
}
|
|
|
|
case "SET":
|
|
var reg int
|
|
var value uint32
|
|
arg, ok := tokens.Get()
|
|
if ok {
|
|
n, err := strconv.ParseInt(arg, 0, 32)
|
|
if err != nil {
|
|
dbg.printLine(terminal.StyleError, fmt.Sprintf("%s is not a number", arg))
|
|
return nil
|
|
}
|
|
reg = int(n)
|
|
}
|
|
arg, ok = tokens.Get()
|
|
if ok {
|
|
n, err := strconv.ParseInt(arg, 0, 32)
|
|
if err != nil {
|
|
dbg.printLine(terminal.StyleError, fmt.Sprintf("%s is not a number", arg))
|
|
return nil
|
|
}
|
|
value = uint32(n)
|
|
}
|
|
if bus.GetCoProc().RegisterSet(reg, value) {
|
|
dbg.printLine(terminal.StyleFeedback, fmt.Sprintf("setting coproc register %d to %08x\n", reg, value))
|
|
} else {
|
|
dbg.printLine(terminal.StyleError, fmt.Sprintf("cannot set coproc register %d to %08x\n", reg, value))
|
|
}
|
|
|
|
case "STEP":
|
|
dbg.CoProcDev.BreakNextInstruction()
|
|
dbg.runUntilHalt = true
|
|
dbg.continueEmulation = true
|
|
|
|
case "ID":
|
|
fallthrough
|
|
default:
|
|
dbg.printLine(terminal.StyleFeedback, bus.GetCoProc().ProcessorID())
|
|
}
|
|
|
|
case cmdDWARF:
|
|
coproc := dbg.vcs.Mem.Cart.GetCoProcBus()
|
|
if coproc == nil {
|
|
dbg.printLine(terminal.StyleError, "cartridge does not have a coprocessor")
|
|
return nil
|
|
}
|
|
|
|
option, _ := tokens.Get()
|
|
|
|
switch option {
|
|
case "FUNCTIONS":
|
|
dbg.CoProcDev.BorrowSource(func(src *dwarf.Source) {
|
|
if src == nil {
|
|
dbg.printLine(terminal.StyleError, "no source available")
|
|
return
|
|
}
|
|
|
|
for _, n := range src.FunctionNames {
|
|
f := src.Functions[n]
|
|
dbg.printLine(terminal.StyleFeedback, f.String())
|
|
}
|
|
})
|
|
case "GLOBALS":
|
|
dbg.CoProcDev.BorrowSource(func(src *dwarf.Source) {
|
|
if src == nil {
|
|
dbg.printLine(terminal.StyleError, "no source available")
|
|
return
|
|
}
|
|
|
|
for _, g := range src.SortedGlobals.Variables {
|
|
g.Update()
|
|
dbg.printLine(terminal.StyleFeedback, g.String())
|
|
}
|
|
})
|
|
case "LOCALS":
|
|
var derivation bool
|
|
var ranges bool
|
|
var err bool
|
|
|
|
option, ok := tokens.Get()
|
|
for ok {
|
|
derivation = derivation || option == "DERIVATION"
|
|
ranges = ranges || option == "RANGES"
|
|
err = err || option == "ERROR"
|
|
option, ok = tokens.Get()
|
|
}
|
|
|
|
dbg.CoProcDev.BorrowSource(func(src *dwarf.Source) {
|
|
if src == nil {
|
|
dbg.printLine(terminal.StyleError, "no source available")
|
|
}
|
|
})
|
|
|
|
dbg.CoProcDev.BorrowYieldState(func(yld yield.State) {
|
|
var w io.Writer
|
|
if derivation {
|
|
w = dbg.writerInStyle(terminal.StyleFeedbackSecondary, "\t")
|
|
}
|
|
for _, l := range yld.LocalVariables {
|
|
dbg.printLine(terminal.StyleFeedback, l.String())
|
|
e := l.WriteDerivation(w)
|
|
if err && e != nil {
|
|
for _, s := range strings.Split(e.Error(), ":") {
|
|
dbg.printLine(terminal.StyleError, fmt.Sprintf("\t%s", s))
|
|
}
|
|
}
|
|
if ranges {
|
|
dbg.printLine(terminal.StyleFeedbackSecondary, fmt.Sprintf("\t%s", l.Range.String()))
|
|
}
|
|
}
|
|
})
|
|
case "FRAMEBASE":
|
|
dbg.CoProcDev.BorrowSource(func(src *dwarf.Source) {
|
|
if src == nil {
|
|
dbg.printLine(terminal.StyleError, "no source available")
|
|
}
|
|
|
|
var derivation bool
|
|
option, ok := tokens.Get()
|
|
for ok {
|
|
derivation = derivation || option == "DERIVATION"
|
|
option, ok = tokens.Get()
|
|
}
|
|
|
|
var w io.Writer
|
|
if derivation {
|
|
w = dbg.writerInStyle(terminal.StyleFeedback)
|
|
}
|
|
|
|
fb, err := src.FramebaseCurrent(w)
|
|
if err != nil {
|
|
dbg.printLine(terminal.StyleError, err.Error())
|
|
} else {
|
|
dbg.printLine(terminal.StyleFeedback, fmt.Sprintf("%08x", fb))
|
|
}
|
|
})
|
|
case "LINE":
|
|
arg, ok := tokens.Get()
|
|
if !ok {
|
|
dbg.printLine(terminal.StyleError, "command requires argument file:line")
|
|
return nil
|
|
}
|
|
|
|
// option is divided by a maximum of one colon, meaning the split
|
|
// array should be a length of two
|
|
s := strings.Split(arg, ":")
|
|
if len(s) != 2 {
|
|
dbg.printLine(terminal.StyleError, "command requires argument file:line")
|
|
return nil
|
|
}
|
|
|
|
// filename and line number
|
|
fn := s[0]
|
|
n, err := strconv.ParseInt(s[1], 0, 32)
|
|
if err != nil {
|
|
dbg.printLine(terminal.StyleError, fmt.Sprintf("%s is not a number", s[1]))
|
|
return nil
|
|
}
|
|
ln := int(n)
|
|
|
|
dbg.CoProcDev.BorrowSource(func(src *dwarf.Source) {
|
|
if src == nil {
|
|
dbg.printLine(terminal.StyleError, "no source available")
|
|
}
|
|
|
|
// check file by shortname and then by full name
|
|
f, ok := src.FilesByShortname[fn]
|
|
if !ok {
|
|
f, ok = src.Files[fn]
|
|
if !ok {
|
|
dbg.printLine(terminal.StyleError, fmt.Sprintf("no file named %s", fn))
|
|
return
|
|
}
|
|
}
|
|
|
|
// line numbers are counted from one
|
|
if ln < 1 {
|
|
ln = 1
|
|
}
|
|
if ln > len(f.Content.Lines) {
|
|
dbg.printLine(terminal.StyleError, fmt.Sprintf("%s only has %d lines", fn, len(f.Content.Lines)))
|
|
return
|
|
}
|
|
l := f.Content.Lines[ln-1]
|
|
|
|
// display what we know about line
|
|
dbg.printLine(terminal.StyleFeedback, l.String())
|
|
})
|
|
|
|
case "CALLSTACK":
|
|
dbg.CoProcDev.BorrowCallStack(func(callstack callstack.CallStack) {
|
|
w := dbg.writerInStyle(terminal.StyleFeedback)
|
|
callstack.WriteCallStack(w)
|
|
})
|
|
|
|
case "CALLERS":
|
|
arg, ok := tokens.Get()
|
|
if !ok {
|
|
dbg.printLine(terminal.StyleError, "function name is required")
|
|
return nil
|
|
}
|
|
dbg.CoProcDev.BorrowCallStack(func(callstack callstack.CallStack) {
|
|
w := dbg.writerInStyle(terminal.StyleFeedback)
|
|
if err := callstack.WriteCallers(arg, w); err != nil {
|
|
dbg.printLine(terminal.StyleError, err.Error())
|
|
return
|
|
}
|
|
})
|
|
}
|
|
|
|
case cmdPeripheral:
|
|
player, _ := tokens.Get()
|
|
|
|
var id plugging.PortID
|
|
|
|
switch strings.ToUpper(player) {
|
|
case "LEFT":
|
|
id = plugging.PortLeft
|
|
case "RIGHT":
|
|
id = plugging.PortRight
|
|
case "SWAP":
|
|
if dbg.controllers.Swap() {
|
|
dbg.printLine(terminal.StyleFeedback, "player peripherals are in the swapped state")
|
|
} else {
|
|
dbg.printLine(terminal.StyleFeedback, "player peripherals are in the normal state")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
var err error
|
|
|
|
controller, ok := tokens.Get()
|
|
if ok {
|
|
switch strings.ToUpper(controller) {
|
|
case "AUTO":
|
|
dbg.vcs.FingerprintPeripheral(id, *dbg.cartload)
|
|
case "STICK":
|
|
err = dbg.vcs.RIOT.Ports.Plug(id, controllers.NewStick)
|
|
case "PADDLE":
|
|
err = dbg.vcs.RIOT.Ports.Plug(id, controllers.NewPaddlePair)
|
|
case "KEYPAD":
|
|
err = dbg.vcs.RIOT.Ports.Plug(id, controllers.NewKeypad)
|
|
case "GAMEPAD":
|
|
err = dbg.vcs.RIOT.Ports.Plug(id, controllers.NewGamepad)
|
|
case "SAVEKEY":
|
|
err = dbg.vcs.RIOT.Ports.Plug(id, savekey.NewSaveKey)
|
|
case "ATARIVOX":
|
|
err = dbg.vcs.RIOT.Ports.Plug(id, atarivox.NewAtariVox)
|
|
}
|
|
}
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var p ports.Peripheral
|
|
switch strings.ToUpper(player) {
|
|
case "LEFT":
|
|
p = dbg.vcs.RIOT.Ports.LeftPlayer
|
|
case "RIGHT":
|
|
p = dbg.vcs.RIOT.Ports.RightPlayer
|
|
}
|
|
|
|
dbg.printLine(terminal.StyleInstrument, p.String())
|
|
|
|
case cmdPanel:
|
|
mode, ok := tokens.Get()
|
|
if !ok {
|
|
dbg.printLine(terminal.StyleInstrument, dbg.vcs.RIOT.Ports.Panel.String())
|
|
return nil
|
|
}
|
|
|
|
var err error
|
|
|
|
switch strings.ToUpper(mode) {
|
|
case "TOGGLE":
|
|
arg, _ := tokens.Get()
|
|
switch strings.ToUpper(arg) {
|
|
case "P0":
|
|
inp := ports.InputEvent{Port: plugging.PortPanel, Ev: ports.PanelTogglePlayer0Pro, D: nil}
|
|
_, err = dbg.vcs.Input.HandleInputEvent(inp)
|
|
case "P1":
|
|
inp := ports.InputEvent{Port: plugging.PortPanel, Ev: ports.PanelTogglePlayer1Pro, D: nil}
|
|
_, err = dbg.vcs.Input.HandleInputEvent(inp)
|
|
case "COL":
|
|
inp := ports.InputEvent{Port: plugging.PortPanel, Ev: ports.PanelToggleColor, D: nil}
|
|
_, err = dbg.vcs.Input.HandleInputEvent(inp)
|
|
}
|
|
case "SET":
|
|
arg, _ := tokens.Get()
|
|
switch strings.ToUpper(arg) {
|
|
case "P0PRO":
|
|
inp := ports.InputEvent{Port: plugging.PortPanel, Ev: ports.PanelSetPlayer0Pro, D: true}
|
|
_, err = dbg.vcs.Input.HandleInputEvent(inp)
|
|
case "P1PRO":
|
|
inp := ports.InputEvent{Port: plugging.PortPanel, Ev: ports.PanelSetPlayer1Pro, D: true}
|
|
_, err = dbg.vcs.Input.HandleInputEvent(inp)
|
|
case "P0AM":
|
|
inp := ports.InputEvent{Port: plugging.PortPanel, Ev: ports.PanelSetPlayer0Pro, D: false}
|
|
_, err = dbg.vcs.Input.HandleInputEvent(inp)
|
|
case "P1AM":
|
|
inp := ports.InputEvent{Port: plugging.PortPanel, Ev: ports.PanelSetPlayer1Pro, D: false}
|
|
_, err = dbg.vcs.Input.HandleInputEvent(inp)
|
|
case "COL":
|
|
inp := ports.InputEvent{Port: plugging.PortPanel, Ev: ports.PanelSetColor, D: true}
|
|
_, err = dbg.vcs.Input.HandleInputEvent(inp)
|
|
case "BW":
|
|
inp := ports.InputEvent{Port: plugging.PortPanel, Ev: ports.PanelSetColor, D: false}
|
|
_, err = dbg.vcs.Input.HandleInputEvent(inp)
|
|
}
|
|
case "HOLD":
|
|
arg, _ := tokens.Get()
|
|
switch strings.ToUpper(arg) {
|
|
case "SELECT":
|
|
inp := ports.InputEvent{Port: plugging.PortPanel, Ev: ports.PanelSelect, D: true}
|
|
_, err = dbg.vcs.Input.HandleInputEvent(inp)
|
|
case "RESET":
|
|
inp := ports.InputEvent{Port: plugging.PortPanel, Ev: ports.PanelReset, D: true}
|
|
_, err = dbg.vcs.Input.HandleInputEvent(inp)
|
|
}
|
|
case "RELEASE":
|
|
arg, _ := tokens.Get()
|
|
switch strings.ToUpper(arg) {
|
|
case "SELECT":
|
|
inp := ports.InputEvent{Port: plugging.PortPanel, Ev: ports.PanelSelect, D: false}
|
|
_, err = dbg.vcs.Input.HandleInputEvent(inp)
|
|
case "RESET":
|
|
inp := ports.InputEvent{Port: plugging.PortPanel, Ev: ports.PanelReset, D: false}
|
|
_, err = dbg.vcs.Input.HandleInputEvent(inp)
|
|
}
|
|
}
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
dbg.printLine(terminal.StyleInstrument, dbg.vcs.RIOT.Ports.Panel.String())
|
|
|
|
case cmdStick:
|
|
var err error
|
|
|
|
port, _ := tokens.Get()
|
|
action, _ := tokens.Get()
|
|
|
|
var event ports.Event
|
|
var value ports.EventData
|
|
|
|
switch strings.ToUpper(action) {
|
|
case "FIRE":
|
|
event = ports.Fire
|
|
value = ports.DataStickTrue
|
|
case "UP":
|
|
event = ports.Up
|
|
value = ports.DataStickTrue
|
|
case "DOWN":
|
|
event = ports.Down
|
|
value = ports.DataStickTrue
|
|
case "LEFT":
|
|
event = ports.Left
|
|
value = ports.DataStickTrue
|
|
case "RIGHT":
|
|
event = ports.Right
|
|
value = ports.DataStickTrue
|
|
|
|
case "NOFIRE":
|
|
event = ports.Fire
|
|
value = ports.DataStickFalse
|
|
case "NOUP":
|
|
event = ports.Up
|
|
value = ports.DataStickFalse
|
|
case "NODOWN":
|
|
event = ports.Down
|
|
value = ports.DataStickFalse
|
|
case "NOLEFT":
|
|
event = ports.Left
|
|
value = ports.DataStickFalse
|
|
case "NORIGHT":
|
|
event = ports.Right
|
|
value = ports.DataStickFalse
|
|
}
|
|
|
|
switch port {
|
|
case "LEFT":
|
|
inp := ports.InputEvent{Port: plugging.PortLeft, Ev: event, D: value}
|
|
_, err = dbg.vcs.Input.HandleInputEvent(inp)
|
|
case "RIGHT":
|
|
inp := ports.InputEvent{Port: plugging.PortRight, Ev: event, D: value}
|
|
_, err = dbg.vcs.Input.HandleInputEvent(inp)
|
|
}
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
case cmdKeypad:
|
|
var err error
|
|
|
|
port, _ := tokens.Get()
|
|
key, _ := tokens.Get()
|
|
|
|
switch port {
|
|
case "LEFT":
|
|
if strings.ToUpper(key) == "NONE" {
|
|
inp := ports.InputEvent{Port: plugging.PortLeft, Ev: ports.KeypadUp, D: nil}
|
|
_, err = dbg.vcs.Input.HandleInputEvent(inp)
|
|
} else {
|
|
inp := ports.InputEvent{Port: plugging.PortLeft, Ev: ports.KeypadDown, D: rune(key[0])}
|
|
_, err = dbg.vcs.Input.HandleInputEvent(inp)
|
|
}
|
|
case "RIGHT":
|
|
if strings.ToUpper(key) == "NONE" {
|
|
inp := ports.InputEvent{Port: plugging.PortRight, Ev: ports.KeypadUp, D: nil}
|
|
_, err = dbg.vcs.Input.HandleInputEvent(inp)
|
|
} else {
|
|
inp := ports.InputEvent{Port: plugging.PortLeft, Ev: ports.KeypadDown, D: rune(key[0])}
|
|
_, err = dbg.vcs.Input.HandleInputEvent(inp)
|
|
}
|
|
}
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
case cmdBreak:
|
|
err := dbg.halting.breakpoints.parseCommand(tokens)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
case cmdTrap:
|
|
err := dbg.halting.traps.parseCommand(tokens)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
case cmdWatch:
|
|
err := dbg.halting.watches.parseCommand(tokens)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
case cmdTrace:
|
|
err := dbg.traces.parseCommand(tokens)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
case cmdList:
|
|
list, _ := tokens.Get()
|
|
list = strings.ToUpper(list)
|
|
switch list {
|
|
case "BREAKS":
|
|
dbg.halting.breakpoints.list()
|
|
dbg.CoProcDev.BorrowBreakpoints(func(bp coproc_breakpoints.Breakpoints) {
|
|
w := dbg.writerInStyle(terminal.StyleFeedback)
|
|
bp.Write(w)
|
|
})
|
|
case "TRAPS":
|
|
dbg.halting.traps.list()
|
|
case "WATCHES":
|
|
dbg.halting.watches.list()
|
|
case "TRACES":
|
|
dbg.traces.list()
|
|
case "ALL":
|
|
dbg.halting.breakpoints.list()
|
|
dbg.halting.traps.list()
|
|
dbg.halting.watches.list()
|
|
dbg.traces.list()
|
|
default:
|
|
// already caught by command line ValidateTokens()
|
|
}
|
|
|
|
case cmdDrop:
|
|
drop, _ := tokens.Get()
|
|
|
|
s, _ := tokens.Get()
|
|
num, err := strconv.Atoi(s)
|
|
if err != nil {
|
|
return fmt.Errorf("drop attribute must be a number (%s)", s)
|
|
}
|
|
|
|
drop = strings.ToUpper(drop)
|
|
switch drop {
|
|
case "BREAK":
|
|
err := dbg.halting.breakpoints.drop(num)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
dbg.printLine(terminal.StyleFeedback, "breakpoint #%d dropped", num)
|
|
case "TRAP":
|
|
err := dbg.halting.traps.drop(num)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
dbg.printLine(terminal.StyleFeedback, "trap #%d dropped", num)
|
|
case "WATCH":
|
|
err := dbg.halting.watches.drop(num)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
dbg.printLine(terminal.StyleFeedback, "watch #%d dropped", num)
|
|
case "TRACE":
|
|
err := dbg.traces.drop(num)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
dbg.printLine(terminal.StyleFeedback, "trace #%d dropped", num)
|
|
default:
|
|
// already caught by command line ValidateTokens()
|
|
}
|
|
|
|
case cmdClear:
|
|
clear, _ := tokens.Get()
|
|
clear = strings.ToUpper(clear)
|
|
switch clear {
|
|
case "BREAKS":
|
|
dbg.halting.breakpoints.clear()
|
|
dbg.printLine(terminal.StyleFeedback, "breakpoints cleared")
|
|
case "TRAPS":
|
|
dbg.halting.traps.clear()
|
|
dbg.printLine(terminal.StyleFeedback, "traps cleared")
|
|
case "WATCHES":
|
|
dbg.halting.watches.clear()
|
|
dbg.printLine(terminal.StyleFeedback, "watches cleared")
|
|
case "TRACES":
|
|
dbg.traces.clear()
|
|
dbg.printLine(terminal.StyleFeedback, "traces cleared")
|
|
case "ALL":
|
|
dbg.halting.breakpoints.clear()
|
|
dbg.halting.traps.clear()
|
|
dbg.halting.watches.clear()
|
|
dbg.traces.clear()
|
|
dbg.printLine(terminal.StyleFeedback, "breakpoints, traps, watches and traces cleared")
|
|
default:
|
|
// already caught by command line ValidateTokens()
|
|
}
|
|
|
|
case cmdLog:
|
|
option, ok := tokens.Get()
|
|
if ok {
|
|
option = strings.ToUpper(option)
|
|
switch option {
|
|
case "LAST":
|
|
s := &strings.Builder{}
|
|
logger.Tail(s, 1)
|
|
dbg.printLine(terminal.StyleLog, s.String())
|
|
case "RECENT":
|
|
s := &strings.Builder{}
|
|
logger.WriteRecent(s)
|
|
dbg.printLine(terminal.StyleLog, s.String())
|
|
case "CLEAR":
|
|
logger.Clear()
|
|
}
|
|
} else {
|
|
s := &strings.Builder{}
|
|
logger.Write(s)
|
|
if s.Len() == 0 {
|
|
dbg.printLine(terminal.StyleFeedback, "log is empty")
|
|
} else {
|
|
dbg.printLine(terminal.StyleLog, s.String())
|
|
}
|
|
}
|
|
|
|
case cmdMemUsage:
|
|
option, ok := tokens.Get()
|
|
if ok {
|
|
option = strings.ToUpper(option)
|
|
switch option {
|
|
case "PROFILE":
|
|
fn, err := dbg.memoryProfile()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
dbg.printLine(terminal.StyleLog, fmt.Sprintf("memory profile written to %s", fn))
|
|
}
|
|
} else {
|
|
var m runtime.MemStats
|
|
runtime.ReadMemStats(&m)
|
|
|
|
s := strings.Builder{}
|
|
|
|
s.WriteString(fmt.Sprintf("Alloc = %v MB\n", m.Alloc/1048576))
|
|
s.WriteString(fmt.Sprintf(" TotalAlloc = %v MB\n", m.TotalAlloc/1048576))
|
|
s.WriteString(fmt.Sprintf(" Sys = %v MB\n", m.Sys/1048576))
|
|
s.WriteString(fmt.Sprintf(" NumGC = %v", m.NumGC))
|
|
|
|
dbg.printLine(terminal.StyleLog, s.String())
|
|
}
|
|
|
|
case cmdVersion:
|
|
dbg.printLine(terminal.StyleLog, version.Version)
|
|
|
|
option, ok := tokens.Get()
|
|
if ok {
|
|
option = strings.ToUpper(option)
|
|
switch option {
|
|
case "REVISION":
|
|
dbg.printLine(terminal.StyleLog, version.Revision)
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|