break/trap targets are now aware of CPU instruction boundaries

halting conditions are checked every video-cycle. this reverts a
temporary change made in a73dcae6dcc61e4ced03f23fe4df504cb966fb36

renamed files in debugger package for clarity
This commit is contained in:
JetSetIlly 2021-10-05 17:33:33 +01:00
parent a5d96cf8ba
commit d788fb46bb
9 changed files with 49 additions and 24 deletions

View file

@ -75,10 +75,14 @@ func (h *haltCoordination) reset() {
// check for a halt condition and set the halt flag if found.
func (h *haltCoordination) check() {
// whether CPU is at an instruction boundary. breakpoints and traps need to
// know this because some targets are sensitive to it
instructionBoundary := h.dbg.vcs.CPU.LastResult.Final
// we don't check for regular break/trap/wathes if there are volatileTraps in place
if h.volatileTraps.isEmpty() && h.volatileBreakpoints.isEmpty() {
breakMessage := h.breakpoints.check()
trapMessage := h.traps.check()
breakMessage := h.breakpoints.check(instructionBoundary)
trapMessage := h.traps.check(instructionBoundary)
watchMessage := h.watches.check()
h.dbg.printLine(terminal.StyleFeedback, breakMessage)
@ -92,7 +96,7 @@ func (h *haltCoordination) check() {
}
// check volatile conditions
breakMessage := h.volatileBreakpoints.check()
trapMessage := h.volatileTraps.check()
breakMessage := h.volatileBreakpoints.check(instructionBoundary)
trapMessage := h.volatileTraps.check(instructionBoundary)
h.halt = h.halt || breakMessage != "" || trapMessage != ""
}

View file

@ -203,13 +203,17 @@ func (bp *breakpoints) drop(num int) error {
// check compares the current state of the emulation with every breakpoint
// condition. returns a string listing every condition that matches (separated
// by \n).
func (bp *breakpoints) check() string {
func (bp *breakpoints) check(instructionBoundary bool) string {
if len(bp.breaks) == 0 {
return ""
}
checkString := strings.Builder{}
for i := range bp.breaks {
if bp.breaks[i].target.instructionBoundary && !instructionBoundary {
continue // for loop
}
// check current value of target with the requested value
if bp.breaks[i].check() == checkMatch {
checkString.WriteString(fmt.Sprintf("break on %s\n", bp.breaks[i]))

View file

@ -33,6 +33,9 @@ type target struct {
// must be a comparable type
currentValue targetValue
format string
// some targets should only be checked on an instruction boundary
instructionBoundary bool
}
func (trg target) Label() string {
@ -88,7 +91,8 @@ func parseTarget(dbg *Debugger, tokens *commandline.Tokens) (*target, error) {
ai := dbg.dbgmem.mapAddress(dbg.vcs.CPU.PC.Address(), true)
return int(ai.mappedAddress)
},
format: "%#04x",
format: "%#04x",
instructionBoundary: true,
}
case "A":
@ -97,7 +101,8 @@ func parseTarget(dbg *Debugger, tokens *commandline.Tokens) (*target, error) {
currentValue: func() targetValue {
return int(dbg.vcs.CPU.A.Value())
},
format: "%#02x",
format: "%#02x",
instructionBoundary: false,
}
case "X":
@ -106,7 +111,8 @@ func parseTarget(dbg *Debugger, tokens *commandline.Tokens) (*target, error) {
currentValue: func() targetValue {
return int(dbg.vcs.CPU.X.Value())
},
format: "%#02x",
format: "%#02x",
instructionBoundary: false,
}
case "Y":
@ -115,7 +121,8 @@ func parseTarget(dbg *Debugger, tokens *commandline.Tokens) (*target, error) {
currentValue: func() targetValue {
return int(dbg.vcs.CPU.Y.Value())
},
format: "%#02x",
format: "%#02x",
instructionBoundary: false,
}
case "SP":
@ -124,7 +131,8 @@ func parseTarget(dbg *Debugger, tokens *commandline.Tokens) (*target, error) {
currentValue: func() targetValue {
return int(dbg.vcs.CPU.SP.Value())
},
format: "%#02x",
format: "%#02x",
instructionBoundary: false,
}
// tv state
@ -134,6 +142,7 @@ func parseTarget(dbg *Debugger, tokens *commandline.Tokens) (*target, error) {
currentValue: func() targetValue {
return dbg.vcs.TV.GetState(signal.ReqFramenum)
},
instructionBoundary: false,
}
case "SCANLINE", "SL":
@ -142,6 +151,7 @@ func parseTarget(dbg *Debugger, tokens *commandline.Tokens) (*target, error) {
currentValue: func() targetValue {
return dbg.vcs.TV.GetState(signal.ReqScanline)
},
instructionBoundary: false,
}
case "CLOCK", "CL":
@ -150,6 +160,7 @@ func parseTarget(dbg *Debugger, tokens *commandline.Tokens) (*target, error) {
currentValue: func() targetValue {
return dbg.vcs.TV.GetState(signal.ReqClock)
},
instructionBoundary: false,
}
case "BANK":
@ -172,6 +183,7 @@ func parseTarget(dbg *Debugger, tokens *commandline.Tokens) (*target, error) {
}
return dbg.vcs.CPU.LastResult.Defn.Operator
},
instructionBoundary: true,
}
case "ADDRESSMODE", "AM":
@ -183,6 +195,7 @@ func parseTarget(dbg *Debugger, tokens *commandline.Tokens) (*target, error) {
}
return int(dbg.vcs.CPU.LastResult.Defn.AddressingMode)
},
instructionBoundary: true,
}
case "EFFECT", "EFF":
@ -194,6 +207,7 @@ func parseTarget(dbg *Debugger, tokens *commandline.Tokens) (*target, error) {
}
return int(dbg.vcs.CPU.LastResult.Defn.Effect)
},
instructionBoundary: true,
}
case "PAGEFAULT", "PAGE":
@ -202,6 +216,7 @@ func parseTarget(dbg *Debugger, tokens *commandline.Tokens) (*target, error) {
currentValue: func() targetValue {
return dbg.vcs.CPU.LastResult.PageFault
},
instructionBoundary: true,
}
case "BUG":
@ -214,6 +229,7 @@ func parseTarget(dbg *Debugger, tokens *commandline.Tokens) (*target, error) {
}
return s
},
instructionBoundary: true,
}
default:
@ -239,5 +255,6 @@ func bankTarget(dbg *Debugger) *target {
currentValue: func() targetValue {
return dbg.vcs.Mem.Cart.GetBank(dbg.vcs.CPU.PC.Address()).Number
},
instructionBoundary: false,
}
}

View file

@ -76,9 +76,13 @@ func (tr *traps) drop(num int) error {
// check compares the current state of the emulation with every trap condition.
// returns a string listing every condition that matches (separated by \n).
func (tr *traps) check() string {
func (tr *traps) check(instructionBoundary bool) string {
checkString := strings.Builder{}
for i := range tr.traps {
if tr.traps[i].target.instructionBoundary && !instructionBoundary {
continue // for loop
}
trapValue := tr.traps[i].target.TargetValue()
if trapValue != tr.traps[i].origValue {

View file

@ -394,10 +394,6 @@ func (dbg *Debugger) step(inputter terminal.Input, catchup bool) error {
return nil
}
// not updating disassembly. if we end up stopping mid-instruction the
// halt branch of the inputLoop() function will give us an opportinity
// to update, which is all we need.
// we do need to update the reflection however
err = dbg.ref.Step(dbg.lastBank)
if err != nil {
@ -416,6 +412,11 @@ func (dbg *Debugger) step(inputter terminal.Input, catchup bool) error {
}
}
// check halt condition. a second check is made after vcs.Step()
// returns below
dbg.halting.check()
dbg.continueEmulation = !dbg.halting.halt
if dbg.quantum == QuantumVideo || !dbg.continueEmulation {
// start another inputLoop() with the clockCycle boolean set to true
return dbg.inputLoop(inputter, true)
@ -433,22 +434,17 @@ func (dbg *Debugger) step(inputter terminal.Input, catchup bool) error {
// get to check the result of VCS.Step()
stepErr := dbg.vcs.Step(callback)
// check halt condition. checking here means we can only break on CPU
// instruction boundaries. checking every video cycle would be nice for
// some targets but for others they can be problematic. for instance PC
// breakpoints would break too earlier (an instruction would leave a PC on
// the target value but would not be ready to execute if the instruction
// affected flow)
// check halt condition again now that the instruction has finished (the
// Final flag is true). this does mean that some breakpoints/traps are
// matched twice but that's not currently a problem
dbg.halting.check()
dbg.continueEmulation = !dbg.halting.halt
var err error
// update disassembly after every CPU instruction. no exceptions.
dbg.lastResult = dbg.Disasm.ExecutedEntry(dbg.lastBank, dbg.vcs.CPU.LastResult, true, dbg.vcs.CPU.PC.Value())
// make sure reflection has been updated at the end of the instruction
if err = dbg.ref.Step(dbg.lastBank); err != nil {
if err := dbg.ref.Step(dbg.lastBank); err != nil {
return err
}