o debugger

- added SYMBOL command. searches for address label, returns
		address
	- fixed disassembly output for ROMs without a symbol file
	- improved disassembly a little bit. now attempts to disassemble
		entire of cartridge memory and doesn't bail out of loop
		at first sign of an unimplemented instruction. cpu
		package now returns a ProgramCounterCycled error when
		program counter reads past end of memory

o video
	- tweaked horizontal movement
	- fixed player vertical delay (tested with Pitfall ROM)
	- fixed ball enable detection (0x02 not 0x20. doh!)

o cpu
	- implemented LAX command
	- renamed 3 byte NOP to DOP
	- added program counter information to unimplemented instruction
		error

o gopher 2600
	- error message now returned and displayed in run mode

o
This commit is contained in:
steve 2018-07-01 19:57:08 +01:00
parent b2a0c9b9ea
commit 4e9f48630b
18 changed files with 124 additions and 61 deletions

View file

@ -7,6 +7,7 @@ import "gopher2600/debugger/parser"
const (
KeywordHelp = "HELP"
KeywordInsert = "INSERT"
KeywordSymbol = "SYMBOL"
KeywordBreak = "BREAK"
KeywordTrap = "TRAP"
KeywordList = "LIST"
@ -45,6 +46,7 @@ const (
// - the tab completion method for each argument for each command
var DebuggerCommands = parser.Commands{
KeywordInsert: parser.CommandArgs{parser.Arg{Typ: parser.ArgFile, Req: true}},
KeywordSymbol: parser.CommandArgs{parser.Arg{Typ: parser.ArgString, Req: true}},
KeywordBreak: parser.CommandArgs{parser.Arg{Typ: parser.ArgTarget, Req: true}, parser.Arg{Typ: parser.ArgValue, Req: false}},
KeywordTrap: parser.CommandArgs{parser.Arg{Typ: parser.ArgTarget, Req: true}},
KeywordOnHalt: parser.CommandArgs{parser.Arg{Typ: parser.ArgIndeterminate, Req: false}},
@ -84,6 +86,7 @@ func init() {
var Help = map[string]string{
KeywordHelp: "Lists commands and provides help for individual debugger commands",
KeywordInsert: "Insert cartridge into emulation (from file)",
KeywordSymbol: "Search for the address label symbol in disassembly. returns address",
KeywordBreak: "Cause emulator to halt when conditions are met",
KeywordList: "List current entries for BREAKS and TRAPS",
KeywordClear: "Clear all entries in BREAKS and TRAPS",

View file

@ -6,6 +6,7 @@ import (
"gopher2600/debugger/ui"
"gopher2600/disassembly"
"gopher2600/disassembly/symbols"
"gopher2600/errors"
"gopher2600/hardware"
"gopher2600/hardware/cpu"
"gopher2600/television"
@ -379,6 +380,20 @@ func (dbg *Debugger) parseCommand(input string) (bool, error) {
return false, err
}
case KeywordSymbol:
address, err := dbg.disasm.Symbols.SearchLocation(parts[1])
if err != nil {
switch err := err.(type) {
case errors.GopherError:
if err.Errno == errors.UnknownSymbol {
dbg.print(ui.Feedback, "%s -> not found", parts[1])
return false, nil
}
}
return false, err
}
dbg.print(ui.Feedback, "%s -> %#04x", parts[1], address)
case KeywordBreak:
err := dbg.breakpoints.parseBreakpoint(parts)
if err != nil {

View file

@ -13,6 +13,7 @@ const (
ArgFile
ArgTarget
ArgValue
ArgString
ArgAddress
ArgIndeterminate
)
@ -88,6 +89,8 @@ func (options Commands) CheckCommandInput(input []string) error {
return fmt.Errorf("emulation target required for %s", input[0])
case ArgValue:
return fmt.Errorf("numeric argument required for %s", input[0])
case ArgString:
return fmt.Errorf("string argument required for %s", input[0])
default:
return fmt.Errorf("too few arguments for %s", input[0])
}

View file

@ -38,7 +38,6 @@ func (dsm *Disassembly) ParseMemory(memory *memory.VCSMemory, symbols *symbols.T
mc.NoSideEffects = true
mc.LoadPC(hardware.AddressReset)
// loop over memory until we encounter a NullInstruction
for {
ir, err := mc.ExecuteInstruction(func(ir *cpu.InstructionResult) {})
@ -47,14 +46,15 @@ func (dsm *Disassembly) ParseMemory(memory *memory.VCSMemory, symbols *symbols.T
switch err := err.(type) {
case errors.GopherError:
switch err.Errno {
case errors.NullInstruction:
// we've encountered a null instruction, which means we've come
// to the end of the program code
case errors.ProgramCounterCycled:
// reached end of memory, exit loop with no errors
// TODO: handle multi-bank ROMS
return nil
case errors.NullInstruction:
// we've encountered a null instruction. ignore
continue
case errors.UnimplementedInstruction:
// ignore unimplemented instructions
// TODO: we need a more sophisticated method of ignoring
// data segments / unreachable code
continue
default:
return err

View file

@ -0,0 +1,15 @@
package symbols
import "gopher2600/errors"
// SearchLocation return the address of the supplied location label
func (sym *Table) SearchLocation(location string) (uint16, error) {
if sym != nil {
for k, v := range sym.Locations {
if v == location {
return k, nil
}
}
}
return 0, errors.GopherError{errors.UnknownSymbol, errors.Values{location}}
}

View file

@ -18,6 +18,8 @@ type Table struct {
MaxLocationWidth int
MaxSymbolWidth int
Valid bool
}
// NewTable is the preferred method of initialisation for Table type
@ -121,5 +123,8 @@ func NewTable(cartridgeFilename string) (*Table, error) {
}
}
// indicate that symbol table should be used
table.Valid = true
return table, nil
}

View file

@ -19,12 +19,14 @@ const (
// Debugger
NoSymbolsFile = CategoryDebugger + iota
SymbolsFileError
UnknownSymbol
// VCS
// CPU
UnimplementedInstruction = CategoryCPU + iota
NullInstruction
ProgramCounterCycled
// Memory
UnservicedChipWrite = CategoryMemory + iota
@ -45,12 +47,14 @@ var messages = map[int]string{
// Debugger
NoSymbolsFile: "no symbols file for %s",
SymbolsFileError: "error processing symbols file (%s)",
UnknownSymbol: "unrecognised symbol (%s)",
// VCS
// CPU
UnimplementedInstruction: "unimplemented instruction (%0#x)",
UnimplementedInstruction: "unimplemented instruction (%0#x) at (%#04x)",
NullInstruction: "unimplemented instruction (0xff)",
ProgramCounterCycled: "program counter cycled back to 0x0000",
// Memory
UnservicedChipWrite: "chip memory write signal has not been serviced since previous write (%s)",

View file

@ -178,7 +178,7 @@ func run(cartridgeFile string) error {
for {
_, _, err := vcs.Step(hardware.NullVideoCycleCallback)
if err != nil {
return nil
return err
}
}
}

View file

@ -188,7 +188,10 @@ func (mc *CPU) read8BitPC() (uint8, error) {
if err != nil {
return 0, err
}
mc.PC.Add(1, false)
carry, _ := mc.PC.Add(1, false)
if carry {
return 0, errors.GopherError{errors.ProgramCounterCycled, nil}
}
return op, nil
}
@ -200,7 +203,10 @@ func (mc *CPU) read16BitPC() (uint16, error) {
// strictly, PC should be incremented by one after reading the lo byte of
// the next instruction but I don't believe this has any side-effects
mc.PC.Add(2, false)
carry, _ := mc.PC.Add(2, false)
if carry {
return 0, errors.GopherError{errors.ProgramCounterCycled, nil}
}
return val, nil
}
@ -308,7 +314,7 @@ func (mc *CPU) ExecuteInstruction(cycleCallback func(*InstructionResult)) (*Inst
if operator == 0xff {
return nil, errors.GopherError{errors.NullInstruction, nil}
}
return nil, errors.GopherError{errors.UnimplementedInstruction, errors.Values{operator}}
return nil, errors.GopherError{errors.UnimplementedInstruction, errors.Values{operator, mc.PC.ToUint16() - 1}}
}
result.Defn = defn
@ -1052,6 +1058,17 @@ func (mc *CPU) ExecuteInstruction(cycleCallback func(*InstructionResult)) (*Inst
case "RTI":
// TODO: implement RTI
// undocumented instructions
case "dop":
// does nothing
case "lax":
mc.A.Load(value)
mc.Status.Zero = mc.A.IsZero()
mc.Status.Sign = mc.A.IsNegative()
mc.X.Load(value)
default:
// this should never, ever happen
log.Fatalf("WTF! unknown mnemonic! (%s)", defn.Mnemonic)

View file

@ -36,8 +36,8 @@
# TODO: maybe the number of cycles can be inferred in a similar way
# no operation
# (also see the undocuemted instruction, DOP)
0xea, NOP, 2, IMPLIED, False
0x04, NOP, 3, ZERO_PAGE, False
# status flags
0x58, CLI, 2, IMPLIED, False
@ -232,3 +232,6 @@
0x00, BRK, 7, IMPLIED, False
0x40, RTI, 6, IMPLIED, False
# undocumented instructions
0x04, dop, 3, ZERO_PAGE, False
0xa7, lax, 4, ABSOLUTE, False

Can't render this file because it contains an unexpected character in line 16 and column 5.

File diff suppressed because one or more lines are too long

View file

@ -51,6 +51,8 @@ func (result InstructionResult) GetString(symtable *symbols.Table, style symbols
var operator, operand string
var notes string
// include instruction address (and label) if this is the final result for
// this particular instruction
if result.Final {
programCounter = fmt.Sprintf("0x%04x", result.Address)
if symtable != nil && style.Has(symbols.StyleFlagLocation) {
@ -67,7 +69,7 @@ func (result InstructionResult) GetString(symtable *symbols.Table, style symbols
operator = result.Defn.Mnemonic
}
// parse instrution result data ...
// parse instruction result data ...
var idx uint16
switch result.InstructionData.(type) {
case uint8:
@ -85,7 +87,7 @@ func (result InstructionResult) GetString(symtable *symbols.Table, style symbols
}
// ... and use assembler symbol for the operand if available/appropriate
if symtable != nil && style.Has(symbols.StyleFlagSymbols) && result.InstructionData != nil && (operand == "" || operand[0] != '?') {
if symtable.Valid && style.Has(symbols.StyleFlagSymbols) && result.InstructionData != nil && (operand == "" || operand[0] != '?') {
if result.Defn.AddressingMode != definitions.Immediate {
switch result.Defn.Effect {
@ -183,12 +185,12 @@ func (result InstructionResult) GetString(symtable *symbols.Table, style symbols
if style.Has(symbols.StyleFlagColumns) {
programCounter = columnise(programCounter, 6)
operator = columnise(operator, 3)
if symtable != nil {
if symtable.Valid {
label = columnise(label, symtable.MaxLocationWidth)
operand = columnise(operand, symtable.MaxSymbolWidth)
} else {
label = columnise(label, 10)
operand = columnise(operand, 10)
label = columnise(label, 0)
operand = columnise(operand, 7)
}
}

View file

@ -61,13 +61,13 @@ func (hm *hmove) set() {
hm.phase = hm.colorClock.Phase
}
func (hm *hmove) tick() int {
func (hm *hmove) tick() (ct int, tick bool) {
// if hmove is active, when color clock phase cycles to where it was when
// hmove.set() was called reduce the hmove count
ct := 0
if hm.count > 0 && hm.phase == hm.colorClock.Phase {
ct = hm.count
hm.count--
tick = true
}
return ct
return ct, tick
}

View file

@ -167,7 +167,9 @@ func (tia *TIA) StepVideoCycle() bool {
}
// HMOVE clock stuffing
tia.Video.TickSpritesForHMOVE(tia.hmove.tick())
if ct, ok := tia.hmove.tick(); ok {
tia.Video.TickSpritesForHMOVE(ct)
}
// tick all video elements
tia.Video.Tick()

View file

@ -117,5 +117,5 @@ func (bs *ballSprite) scheduleReset(hblank *bool) {
}
func (bs *ballSprite) scheduleEnable(value uint8) {
bs.futureEnable.schedule(delayEnableBall, value&0x20 == 0x20)
bs.futureEnable.schedule(delayEnableBall, value&0x02 == 0x02)
}

View file

@ -13,6 +13,7 @@ type playerSprite struct {
color uint8
gfxData uint8
gfxDataPrev uint8
gfxDataDelay *uint8
size uint8
reflected bool
verticalDelay bool
@ -33,8 +34,10 @@ func newPlayerSprite(label string, colorClock *colorclock.ColorClock) *playerSpr
func (ps playerSprite) MachineInfoTerse() string {
gfxData := ps.gfxData
vdel := ""
if ps.verticalDelay {
gfxData = ps.gfxDataPrev
gfxData = *ps.gfxDataDelay
vdel = " v"
}
ref := " "
if ps.reflected {
@ -49,7 +52,7 @@ func (ps playerSprite) MachineInfoTerse() string {
visPix++
}
return fmt.Sprintf("%s (vis: %d) gfx: %s %08b", ps.sprite.MachineInfoTerse(), visPix, ref, gfxData)
return fmt.Sprintf("%s (vis: %d, hm: %d) gfx: %s %08b%s", ps.sprite.MachineInfoTerse(), visPix, ps.horizMovement-8, ref, gfxData, vdel)
}
// MachineInfo returns the missile sprite information in verbose format
@ -103,9 +106,10 @@ func (ps *playerSprite) pixel() (bool, uint8) {
// vertical delay
gfxData := ps.gfxData
if ps.verticalDelay {
gfxData = ps.gfxDataPrev
gfxData = *ps.gfxDataDelay
}
// reflection
if ps.reflected {
gfxData = bits.Reverse8(gfxData)
}
@ -132,8 +136,3 @@ func (ps *playerSprite) scheduleReset(hblank *bool) {
ps.futureReset.schedule(delayResetPlayer, true)
}
}
func (ps *playerSprite) setData(data uint8) {
ps.gfxDataPrev = ps.gfxData
ps.gfxData = data
}

View file

@ -37,6 +37,9 @@ type sprite struct {
drawSigCount int
drawSigMax int
drawSigOff int
// the amount of horizontal movement for the sprite
horizMovement uint8
}
func newSprite(label string, colorClock *colorclock.ColorClock) *sprite {

View file

@ -18,13 +18,6 @@ type Video struct {
Missile0 *missileSprite
Missile1 *missileSprite
Ball *ballSprite
// horizontal movement
hmp0 uint8
hmp1 uint8
hmm0 uint8
hmm1 uint8
hmbl uint8
}
// New is the preferred method of initialisation for the Video structure
@ -62,12 +55,9 @@ func New(colorClock *colorclock.ColorClock, hblank *bool) *Video {
return nil
}
// horizontal movment
vd.hmp0 = 0x08
vd.hmp1 = 0x08
vd.hmm0 = 0x08
vd.hmm1 = 0x08
vd.hmbl = 0x08
// connect player 0 and player 1 to each other (via the vertical delay bit)
vd.Player0.gfxDataDelay = &vd.Player1.gfxDataPrev
vd.Player1.gfxDataDelay = &vd.Player0.gfxDataPrev
return vd
}
@ -90,19 +80,19 @@ func (vd *Video) TickSpritesForHMOVE(count int) {
return
}
if vd.hmp0 >= uint8(count) {
if vd.Player0.horizMovement >= uint8(count) {
vd.Player0.tick()
}
if vd.hmp1 >= uint8(count) {
if vd.Player1.horizMovement >= uint8(count) {
vd.Player1.tick()
}
if vd.hmm0 >= uint8(count) {
if vd.Missile0.horizMovement >= uint8(count) {
vd.Missile0.tick()
}
if vd.hmm1 >= uint8(count) {
if vd.Missile1.horizMovement >= uint8(count) {
vd.Missile1.tick()
}
if vd.hmbl >= uint8(count) {
if vd.Ball.horizMovement >= uint8(count) {
vd.Ball.tick()
}
}
@ -236,9 +226,11 @@ func (vd *Video) ReadVideoMemory(register string, value uint8) bool {
case "RESBL":
vd.Ball.scheduleReset(vd.hblank)
case "GRP0":
vd.Player0.setData(value)
vd.Player0.gfxDataPrev = vd.Player1.gfxData
vd.Player0.gfxData = value
case "GRP1":
vd.Player1.setData(value)
vd.Player1.gfxDataPrev = vd.Player0.gfxData
vd.Player1.gfxData = value
case "ENAM0":
vd.Missile0.scheduleEnable(value)
case "ENAM1":
@ -246,15 +238,15 @@ func (vd *Video) ReadVideoMemory(register string, value uint8) bool {
case "ENABL":
vd.Ball.scheduleEnable(value)
case "HMP0":
vd.hmp0 = (value ^ 0x80) >> 4
vd.Player0.horizMovement = (value ^ 0x80) >> 4
case "HMP1":
vd.hmp1 = (value ^ 0x80) >> 4
vd.Player1.horizMovement = (value ^ 0x80) >> 4
case "HMM0":
vd.hmm0 = (value ^ 0x80) >> 4
vd.Missile0.horizMovement = (value ^ 0x80) >> 4
case "HMM1":
vd.hmm1 = (value ^ 0x80) >> 4
vd.Missile1.horizMovement = (value ^ 0x80) >> 4
case "HMBL":
vd.hmbl = (value ^ 0x80) >> 4
vd.Ball.horizMovement = (value ^ 0x80) >> 4
case "VDELP0":
vd.Player0.verticalDelay = value&0x01 == 0x01
case "VDELP1":
@ -264,11 +256,11 @@ func (vd *Video) ReadVideoMemory(register string, value uint8) bool {
case "RESMP0":
case "RESMP1":
case "HMCLR":
vd.hmp0 = 0x08
vd.hmp1 = 0x08
vd.hmm0 = 0x08
vd.hmm1 = 0x08
vd.hmbl = 0x08
vd.Player0.horizMovement = 0x08
vd.Player1.horizMovement = 0x08
vd.Missile0.horizMovement = 0x08
vd.Missile1.horizMovement = 0x08
vd.Ball.horizMovement = 0x08
case "CXCLR":
}