mirror of
https://github.com/JetSetIlly/Gopher2600.git
synced 2025-04-02 11:02:17 -04:00
- do not allow sdl to output the silence value because some audio devices turn off as soon as silence is received. this causes sound to be clipped and in some cases, never to be output at all.
314 lines
9.5 KiB
Go
314 lines
9.5 KiB
Go
package debugger
|
|
|
|
import (
|
|
"gopher2600/debugger/terminal"
|
|
"gopher2600/errors"
|
|
"gopher2600/gui"
|
|
"gopher2600/hardware/cpu/instructions"
|
|
"io"
|
|
"os"
|
|
"syscall"
|
|
)
|
|
|
|
// videoCycle() to be used with vcs.Step()
|
|
//
|
|
// compare the use of this function with videoCycleWithInput() function
|
|
// defined inside the inputLoop() function
|
|
func (dbg *Debugger) videoCycle() error {
|
|
// because we call this callback mid-instruction, the program counter
|
|
// maybe in its non-final state - we don't want to break or trap in those
|
|
// instances when the final effect of the instruction changes the program
|
|
// counter to some other value (ie. a flow, subroutine or interrupt
|
|
// instruction)
|
|
if !dbg.vcs.CPU.LastResult.Final &&
|
|
dbg.vcs.CPU.LastResult.Defn != nil {
|
|
if dbg.vcs.CPU.LastResult.Defn.Effect == instructions.Flow ||
|
|
dbg.vcs.CPU.LastResult.Defn.Effect == instructions.Subroutine ||
|
|
dbg.vcs.CPU.LastResult.Defn.Effect == instructions.Interrupt {
|
|
return nil
|
|
}
|
|
|
|
// display information about any CPU bugs that may have been triggered
|
|
if dbg.reportCPUBugs && dbg.vcs.CPU.LastResult.Bug != "" {
|
|
dbg.printLine(terminal.StyleInstrument, dbg.vcs.CPU.LastResult.Bug)
|
|
}
|
|
}
|
|
|
|
dbg.breakMessages = dbg.breakpoints.check(dbg.breakMessages)
|
|
dbg.trapMessages = dbg.traps.check(dbg.trapMessages)
|
|
dbg.watchMessages = dbg.watches.check(dbg.watchMessages)
|
|
|
|
return dbg.relfectMonitor.Check()
|
|
}
|
|
|
|
// inputLoop has two modes, defined by the videoCycle argument. when videoCycle
|
|
// is true then user will be prompted every video cycle; when false the user
|
|
// is prompted every cpu cycle.
|
|
func (dbg *Debugger) inputLoop(inputter terminal.Input, videoCycle bool) error {
|
|
var err error
|
|
|
|
// videoCycleWithInput() to be used with vcs.Step() instead of videoCycle()
|
|
// when in video-step mode
|
|
//
|
|
// (we're defining the function here because we want the inputter instance
|
|
// to be enclosed)
|
|
//
|
|
// compare the use of this function with Debugger.videoCycle() function
|
|
// defined elsewhere
|
|
videoCycleWithInput := func() error {
|
|
dbg.videoCycle()
|
|
if dbg.commandOnStep != "" {
|
|
_, err := dbg.parseInput(dbg.commandOnStep, false, true)
|
|
if err != nil {
|
|
dbg.printLine(terminal.StyleError, "%s", err)
|
|
}
|
|
}
|
|
return dbg.inputLoop(inputter, true)
|
|
}
|
|
|
|
for dbg.running {
|
|
// check for gui events and keyboard interrupts
|
|
err = dbg.checkInterruptsAndEvents()
|
|
if err != nil {
|
|
dbg.printLine(terminal.StyleError, "%s", err)
|
|
}
|
|
|
|
// if debugger is no longer running after checking interrupts and
|
|
// events then break for loop
|
|
if !dbg.running {
|
|
break // for loop
|
|
}
|
|
|
|
// this extra test is to prevent the video input loop from continuing
|
|
// when step granularity has been switched to every cpu instruction - the
|
|
// input loop will unravel and execution will continue in the main
|
|
// inputLoop
|
|
if videoCycle && !dbg.inputEveryVideoCycle && dbg.continueEmulation {
|
|
return nil
|
|
}
|
|
|
|
// check for breakpoints and traps
|
|
dbg.breakMessages = dbg.breakpoints.check(dbg.breakMessages)
|
|
dbg.trapMessages = dbg.traps.check(dbg.trapMessages)
|
|
dbg.watchMessages = dbg.watches.check(dbg.watchMessages)
|
|
stepTrapMessage := dbg.stepTraps.check("")
|
|
|
|
// check for halt conditions
|
|
dbg.haltEmulation = stepTrapMessage != "" ||
|
|
dbg.breakMessages != "" ||
|
|
dbg.trapMessages != "" ||
|
|
dbg.watchMessages != "" ||
|
|
dbg.lastStepError
|
|
|
|
// expand halt to include step-once/many flag
|
|
dbg.haltEmulation = dbg.haltEmulation || !dbg.runUntilHalt
|
|
|
|
// step traps are cleared once they have been encountered
|
|
if stepTrapMessage != "" {
|
|
dbg.stepTraps.clear()
|
|
}
|
|
|
|
// print and reset accumulated break/trap/watch messages
|
|
dbg.printLine(terminal.StyleFeedback, dbg.breakMessages)
|
|
dbg.printLine(terminal.StyleFeedback, dbg.trapMessages)
|
|
dbg.printLine(terminal.StyleFeedback, dbg.watchMessages)
|
|
|
|
// clear accumulated break/trap/watch messages
|
|
dbg.breakMessages = ""
|
|
dbg.trapMessages = ""
|
|
dbg.watchMessages = ""
|
|
|
|
// reset last step error
|
|
dbg.lastStepError = false
|
|
|
|
// something has happened to cause the emulation to halt
|
|
if dbg.haltEmulation {
|
|
// input has halted. print on halt command if it is defined
|
|
if dbg.commandOnHalt != "" {
|
|
_, err = dbg.parseInput(dbg.commandOnHalt, false, true)
|
|
if err != nil {
|
|
dbg.printLine(terminal.StyleError, "%s", err)
|
|
}
|
|
}
|
|
|
|
// pause tv when emulation has halted
|
|
err = dbg.scr.SetFeature(gui.ReqSetPause, true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// reset run until halt flag - it will be set again if the parsed command requires it
|
|
// (eg. the RUN command)
|
|
dbg.runUntilHalt = false
|
|
|
|
// get user input
|
|
inputLen, err := inputter.TermRead(dbg.input, dbg.buildPrompt(videoCycle), dbg.guiChan, dbg.guiEventHandler)
|
|
|
|
// errors returned by UserRead() functions are very rich. the
|
|
// following block interprets the error carefully and proceeds
|
|
// appropriately
|
|
if err != nil {
|
|
if !errors.IsAny(err) {
|
|
// if the error originated from outside of the emulation code
|
|
// then it is probably serious or unexpected
|
|
switch err {
|
|
case io.EOF:
|
|
// treat EOF events the same as UserInterrupt events
|
|
err = errors.New(errors.UserInterrupt)
|
|
default:
|
|
// the error is probably serious. exit input loop with
|
|
// err
|
|
return err
|
|
}
|
|
}
|
|
|
|
// we now know the we have an Atari Error so we can safely
|
|
// switch on the internal Errno
|
|
switch err.(errors.AtariError).Head {
|
|
|
|
// user interrupts are triggered by the user (in a terminal
|
|
// environment, usually by pressing ctrl-c)
|
|
case errors.UserInterrupt:
|
|
|
|
// if script input is being capture by a scriptScribe then
|
|
// we the user interrupt event as a SCRIPT END
|
|
// command.
|
|
if dbg.scriptScribe.IsActive() {
|
|
dbg.input = []byte("SCRIPT END")
|
|
inputLen = 11
|
|
|
|
} else if !inputter.IsInteractive() {
|
|
// if the input loop is processing a non-interactive
|
|
// session (a script) then we run the EXIT command
|
|
// immediately, without asking the user
|
|
dbg.input = []byte("EXIT")
|
|
inputLen = 5
|
|
|
|
} else {
|
|
// a scriptScribe is not active nor is this a script
|
|
// input loop. ask the user if they really want to quit
|
|
confirm := make([]byte, 1)
|
|
_, err := inputter.TermRead(confirm,
|
|
terminal.Prompt{
|
|
Content: "really quit (y/n) ",
|
|
Style: terminal.StylePromptConfirm},
|
|
nil, nil)
|
|
|
|
if err != nil {
|
|
// another UserInterrupt has occurred. we treat
|
|
// UserInterrupt as thought 'y' was pressed
|
|
if errors.Is(err, errors.UserInterrupt) {
|
|
confirm[0] = 'y'
|
|
} else {
|
|
dbg.printLine(terminal.StyleError, err.Error())
|
|
}
|
|
}
|
|
|
|
// check if confirmation has been confirmed and run
|
|
// EXIT command
|
|
if confirm[0] == 'y' || confirm[0] == 'Y' {
|
|
dbg.input = []byte("EXIT")
|
|
inputLen = 5
|
|
}
|
|
}
|
|
|
|
// user has asked to suspend the debuggin process (in a UNIX
|
|
// terminal environment this is usually done with ctrl-z)
|
|
case errors.UserSuspend:
|
|
p, err := os.FindProcess(os.Getppid())
|
|
if err != nil {
|
|
dbg.printLine(terminal.StyleError, "debugger doesn't seem to have a parent process")
|
|
} else {
|
|
// send TSTP signal to parent proces
|
|
p.Signal(syscall.SIGTSTP)
|
|
}
|
|
|
|
// a script that is being run will usually end with a ScriptEnd
|
|
// error. in these instances we can say simply say so (using
|
|
// the error message) with a feedback style (not an error
|
|
// style)
|
|
case errors.ScriptEnd:
|
|
if !videoCycle {
|
|
dbg.printLine(terminal.StyleFeedback, err.Error())
|
|
}
|
|
return nil
|
|
|
|
// a GUI event has triggered an error
|
|
case errors.GUIEventError:
|
|
dbg.printLine(terminal.StyleError, err.Error())
|
|
|
|
// all other errors are passed upwards to the calling function
|
|
default:
|
|
return err
|
|
}
|
|
}
|
|
|
|
// sometimes UserRead can return zero bytes read, we need to filter
|
|
// this out before we try any
|
|
if inputLen > 0 {
|
|
// parse user input, taking note of whether the emulation should
|
|
// continue
|
|
dbg.continueEmulation, err = dbg.parseInput(string(dbg.input[:inputLen-1]), inputter.IsInteractive(), false)
|
|
if err != nil {
|
|
dbg.printLine(terminal.StyleError, "%s", err)
|
|
}
|
|
}
|
|
|
|
// prepare for next loop
|
|
dbg.haltEmulation = false
|
|
|
|
// if continueEmulation is set at the end of the haltEmulation
|
|
// block, then unpause GUI
|
|
if dbg.continueEmulation {
|
|
err = dbg.scr.SetFeature(gui.ReqSetPause, false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
if dbg.continueEmulation {
|
|
// if this non-video-cycle input loop then
|
|
if !videoCycle {
|
|
if dbg.inputEveryVideoCycle {
|
|
err = dbg.vcs.Step(videoCycleWithInput)
|
|
} else {
|
|
err = dbg.vcs.Step(dbg.videoCycle)
|
|
}
|
|
|
|
if err != nil {
|
|
// exit input loop only if error is not an AtariError...
|
|
if !errors.IsAny(err) {
|
|
return err
|
|
}
|
|
|
|
// ...set lastStepError instead and allow emulation to halt
|
|
dbg.lastStepError = true
|
|
dbg.printLine(terminal.StyleError, "%s", err)
|
|
|
|
} else {
|
|
// check validity of instruction result
|
|
if dbg.vcs.CPU.LastResult.Final {
|
|
err := dbg.vcs.CPU.LastResult.IsValid()
|
|
if err != nil {
|
|
dbg.printLine(terminal.StyleError, "%s", dbg.vcs.CPU.LastResult.Defn)
|
|
dbg.printLine(terminal.StyleError, "%s", dbg.vcs.CPU.LastResult)
|
|
return errors.New(errors.DebuggerError, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
if dbg.commandOnStep != "" {
|
|
_, err := dbg.parseInput(dbg.commandOnStep, false, true)
|
|
if err != nil {
|
|
dbg.printLine(terminal.StyleError, "%s", err)
|
|
}
|
|
}
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|