Gopher2600/debugger/breakpoints.go
steve d569fb4bca o targets
- started to generalise targets a little. preparation for future
		changes

o register
	- split register operations into a separate file - for clarity
2020-01-05 18:58:28 +00:00

145 lines
4.1 KiB
Go

// breakpoints are used to halt execution when a target is *changed to* a
// specific value. compare to traps which are used to halt execution when the
// target *changes* from its current value to any other value.
package debugger
import (
"gopher2600/debugger/ui"
"strconv"
"strings"
)
// breakpoints keeps track of all the currently defined breaker
type breakpoints struct {
dbg *Debugger
breaks []breaker
// ignore certain target values
ignoredBreakerStates map[target]interface{}
}
// breaker defines a specific break condition
type breaker struct {
target target
value interface{}
}
// newBreakpoints is the preferred method of initialisation for breakpoins
func newBreakpoints(dbg *Debugger) *breakpoints {
bp := new(breakpoints)
bp.dbg = dbg
bp.clear()
return bp
}
func (bp *breakpoints) clear() {
bp.breaks = make([]breaker, 0, 10)
}
// prepareBreakpoints prepares for next breakpoint by storing the current state
// of all Targets. we can then use these stored values to know what to
// ignore. used primarily so that we're not breaking immediately on a previous
// breakstate.
//
// one possible flaw in the current implementation of this idea is that the
// emulation will not honour new breaks until the value has cycled back to the
// break value:
//
// A == v
// break A v
// A == v -> no break
// A == w -> no break
// A == v -> breaks
//
func (bp *breakpoints) prepareBreakpoints() {
bp.ignoredBreakerStates = make(map[target]interface{}, len(bp.breaks))
for _, b := range bp.breaks {
bp.ignoredBreakerStates[b.target] = b.target.Value()
}
}
// check compares the current state of the emulation with every break
// condition. it lists every condition that applies, not just the first
// condition it encounters.
func (bp *breakpoints) check() bool {
broken := false
for i := range bp.breaks {
// check current value of target with the requested value
if bp.breaks[i].target.Value() == bp.breaks[i].value {
// make sure that we're not breaking on an ignore state
bv, prs := bp.ignoredBreakerStates[bp.breaks[i].target]
if !prs || prs && bp.breaks[i].target.Value() != bv {
bp.dbg.print(ui.Feedback, "break on %s=%d", bp.breaks[i].target.ShortLabel(), bp.breaks[i].value)
broken = true
}
}
}
// remove ignoreBreakerState if the break target has changed from its
// ignored value
if !broken {
for i := range bp.breaks {
bv, prs := bp.ignoredBreakerStates[bp.breaks[i].target]
if prs && bp.breaks[i].target.Value() != bv {
delete(bp.ignoredBreakerStates, bp.breaks[i].target)
}
}
}
return broken
}
func (bp breakpoints) list() {
if len(bp.breaks) == 0 {
bp.dbg.print(ui.Feedback, "no breakpoints")
} else {
for i := range bp.breaks {
bp.dbg.print(ui.Feedback, "%s->%d", bp.breaks[i].target.ShortLabel(), bp.breaks[i].value)
}
}
}
func (bp *breakpoints) parseBreakpoint(parts []string) error {
var tgt target
// default target of CPU PC. meaning that "BREAK n" will cause a breakpoint
// being set on the PC. breaking on PC is probably the most common type of
// breakpoint. the target will change value when the input string sees
// something appropriate
tgt = bp.dbg.vcs.MC.PC
// loop over parts. if part is a number then add the breakpoint for the
// current target. if it is not a number, look for a keyword that changes
// the target (or run a BREAK meta-command)
//
// note that this method of looping allows the user to chain break commands
for i := 1; i < len(parts); i++ {
parts[i] = strings.ToUpper(parts[i])
val, err := strconv.ParseUint(parts[i], 0, 16)
if err == nil {
// check to see if breakpoint already exists
addNewBreak := true
for _, mv := range bp.breaks {
if mv.target == tgt && mv.value == int(val) {
addNewBreak = false
bp.dbg.print(ui.Feedback, "breakpoint already exists")
break // for loop
}
}
if addNewBreak {
bp.breaks = append(bp.breaks, breaker{target: tgt, value: int(val)})
}
} else {
// defer parsing of other keywords to parseTargets()
tgt, err = parseTarget(bp.dbg.vcs, parts[i])
if err != nil {
return err
}
}
}
return nil
}