mirror of
https://github.com/JetSetIlly/Gopher2600.git
synced 2025-04-02 11:02:17 -04:00
two reasons: 1) to keep performance acceptable playmode only checks halting conditions on a CPU instruction boundary. however a CLK changes many times during an instruction meaning it will never match. 2) a CLK breakpoint will always match within 228 emulation ticks so there is no point entering playmode at all because it will definitely drop back to the debugger (within microseconds) added a range change check to SCANLINE and CLK targets in breakpoints.parseCommand(). we know what the possible values are for these targets so we can be helpful and inform the user the some values will never match
261 lines
6.7 KiB
Go
261 lines
6.7 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 (
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/jetsetilly/gopher2600/curated"
|
|
"github.com/jetsetilly/gopher2600/debugger/dbgmem"
|
|
"github.com/jetsetilly/gopher2600/debugger/terminal"
|
|
"github.com/jetsetilly/gopher2600/debugger/terminal/commandline"
|
|
)
|
|
|
|
type watcher struct {
|
|
ai dbgmem.AddressInfo
|
|
|
|
// whether to watch for a specific value. a matchValue of false means the
|
|
// watcher will match regardless of the value
|
|
matchValue bool
|
|
value uint8
|
|
|
|
// whether the address should be interpreted strictly or whether mirrors
|
|
// should be considered too
|
|
strict bool
|
|
}
|
|
|
|
func (w watcher) String() string {
|
|
val := ""
|
|
if w.matchValue {
|
|
val = fmt.Sprintf(" (value=%#02x)", w.value)
|
|
}
|
|
event := "write"
|
|
if w.ai.Read {
|
|
event = "read"
|
|
}
|
|
strict := ""
|
|
if w.strict {
|
|
strict = " (strict)"
|
|
}
|
|
return fmt.Sprintf("%s %s%s%s", w.ai, event, val, strict)
|
|
}
|
|
|
|
// the list of currently defined watches in the system.
|
|
type watches struct {
|
|
dbg *Debugger
|
|
watches []watcher
|
|
lastAddressAccessed uint16
|
|
lastAddressWrite bool
|
|
}
|
|
|
|
// newWatches is the preferred method of initialisation for the watches type.
|
|
func newWatches(dbg *Debugger) *watches {
|
|
wtc := &watches{
|
|
dbg: dbg,
|
|
}
|
|
wtc.clear()
|
|
return wtc
|
|
}
|
|
|
|
// clear all watches.
|
|
func (wtc *watches) clear() {
|
|
wtc.watches = make([]watcher, 0, 10)
|
|
}
|
|
|
|
// drop a specific watcher by a position in the list.
|
|
func (wtc *watches) drop(num int) error {
|
|
if len(wtc.watches)-1 < num {
|
|
return curated.Errorf("watch #%d is not defined", num)
|
|
}
|
|
|
|
h := wtc.watches[:num]
|
|
t := wtc.watches[num+1:]
|
|
wtc.watches = make([]watcher, len(h)+len(t), cap(wtc.watches))
|
|
copy(wtc.watches, h)
|
|
copy(wtc.watches[len(h):], t)
|
|
|
|
return nil
|
|
}
|
|
|
|
// check compares the current state of the emulation with every watch
|
|
// condition. returns a string listing every condition that matches (separated
|
|
// by \n).
|
|
func (wtc *watches) check() string {
|
|
if len(wtc.watches) == 0 {
|
|
return ""
|
|
}
|
|
|
|
// no check for phantom access
|
|
if wtc.dbg.vcs.CPU.PhantomMemAccess {
|
|
return ""
|
|
}
|
|
|
|
// no check if access address & write flag haven't changed
|
|
//
|
|
// note that the write flag comparison is required otherwise RMW
|
|
// instructions will not be caught on the write signal (which would mean
|
|
// that a WRITE watch will never match a RMW instruction)
|
|
if wtc.lastAddressAccessed == wtc.dbg.vcs.Mem.LastAccessAddress && wtc.lastAddressWrite == wtc.dbg.vcs.Mem.LastAccessWrite {
|
|
return ""
|
|
}
|
|
|
|
checkString := strings.Builder{}
|
|
|
|
for _, w := range wtc.watches {
|
|
var accessAddress uint16
|
|
var watchAddress uint16
|
|
|
|
// pick which addresses to comare depending on whether watch is strict
|
|
if w.strict {
|
|
accessAddress = wtc.dbg.vcs.Mem.LastAccessAddress
|
|
watchAddress = w.ai.Address
|
|
} else {
|
|
accessAddress = wtc.dbg.vcs.Mem.LastAccessAddressMapped
|
|
watchAddress = w.ai.MappedAddress
|
|
}
|
|
|
|
// continue loop if we're not matching last address accessed
|
|
if watchAddress != accessAddress {
|
|
continue
|
|
}
|
|
|
|
if w.matchValue && w.value != wtc.dbg.vcs.Mem.LastAccessValue {
|
|
continue
|
|
}
|
|
|
|
if w.ai.Read {
|
|
if !wtc.dbg.vcs.Mem.LastAccessWrite {
|
|
checkString.WriteString(fmt.Sprintf("watch at %s (read value %#02x)\n", w, wtc.dbg.vcs.Mem.LastAccessValue))
|
|
}
|
|
} else {
|
|
if wtc.dbg.vcs.Mem.LastAccessWrite {
|
|
checkString.WriteString(fmt.Sprintf("watch at %s (written value %#02x)\n", w, wtc.dbg.vcs.Mem.LastAccessValue))
|
|
}
|
|
}
|
|
}
|
|
|
|
// note what the last address accessed was
|
|
wtc.lastAddressAccessed = wtc.dbg.vcs.Mem.LastAccessAddress
|
|
wtc.lastAddressWrite = wtc.dbg.vcs.Mem.LastAccessWrite
|
|
|
|
return checkString.String()
|
|
}
|
|
|
|
// list currently defined watches.
|
|
func (wtc *watches) list() {
|
|
if len(wtc.watches) == 0 {
|
|
wtc.dbg.printLine(terminal.StyleFeedback, "no watches")
|
|
} else {
|
|
wtc.dbg.printLine(terminal.StyleFeedback, "watches:")
|
|
for i := range wtc.watches {
|
|
wtc.dbg.printLine(terminal.StyleFeedback, "% 2d: %s", i, wtc.watches[i])
|
|
}
|
|
}
|
|
}
|
|
|
|
// parse tokens and add new watch. unlike breakpoints and traps, only one watch
|
|
// at a time can be specified on the command line.
|
|
func (wtc *watches) parseCommand(tokens *commandline.Tokens) error {
|
|
var read bool
|
|
var strict bool
|
|
|
|
// event type
|
|
arg, _ := tokens.Get()
|
|
arg = strings.ToUpper(arg)
|
|
switch arg {
|
|
case "READ":
|
|
read = true
|
|
case "WRITE":
|
|
read = false
|
|
default:
|
|
// default watch event is READ
|
|
read = true
|
|
tokens.Unget()
|
|
}
|
|
|
|
// strict addressing or not
|
|
arg, _ = tokens.Get()
|
|
arg = strings.ToUpper(arg)
|
|
switch arg {
|
|
case "STRICT":
|
|
strict = true
|
|
default:
|
|
strict = false
|
|
tokens.Unget()
|
|
}
|
|
|
|
// get address. required.
|
|
a, _ := tokens.Get()
|
|
|
|
// convert address
|
|
var ai *dbgmem.AddressInfo
|
|
|
|
if read {
|
|
ai = wtc.dbg.dbgmem.MapAddress(a, true)
|
|
} else {
|
|
ai = wtc.dbg.dbgmem.MapAddress(a, false)
|
|
}
|
|
|
|
// mapping of the address was unsuccessful
|
|
if ai == nil {
|
|
if read {
|
|
return curated.Errorf("invalid watch address (%s) expecting 16-bit address or a read symbol", a)
|
|
}
|
|
return curated.Errorf("invalid watch address (%s) expecting 16-bit address or a write symbol", a)
|
|
}
|
|
|
|
// get value if possible
|
|
var val uint64
|
|
var err error
|
|
v, useVal := tokens.Get()
|
|
if useVal {
|
|
val, err = strconv.ParseUint(v, 0, 8)
|
|
if err != nil {
|
|
return curated.Errorf("invalid watch value (%s) expecting 8-bit value", a)
|
|
}
|
|
}
|
|
|
|
nw := watcher{
|
|
ai: *ai,
|
|
matchValue: useVal,
|
|
value: uint8(val),
|
|
strict: strict,
|
|
}
|
|
|
|
// check to see if watch already exists
|
|
for _, w := range wtc.watches {
|
|
// the conditions for a watch matching are very specific: both must
|
|
// have the same address, be the same /type/ of address (read or
|
|
// write), and the same watch value (if applicable)
|
|
//
|
|
// note that this method means we can add a watch that is a subset of
|
|
// an existing watch (or vice-versa) but that's okay, the check()
|
|
// function will list all matches. plus, if we combine two watches such
|
|
// that only the larger set remains, it may confuse the user
|
|
if w.ai.Address == nw.ai.Address &&
|
|
w.ai.Read == nw.ai.Read &&
|
|
w.matchValue == nw.matchValue && w.value == nw.value {
|
|
return curated.Errorf("already being watched (%s)", w)
|
|
}
|
|
}
|
|
|
|
// add watch
|
|
wtc.watches = append(wtc.watches, nw)
|
|
|
|
return nil
|
|
}
|