mirror of
https://github.com/JetSetIlly/Gopher2600.git
synced 2025-04-02 11:02:17 -04:00
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:
parent
b2a0c9b9ea
commit
4e9f48630b
18 changed files with 124 additions and 61 deletions
|
@ -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",
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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])
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
15
disassembly/symbols/search.go
Normal file
15
disassembly/symbols/search.go
Normal 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}}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)",
|
||||
|
|
|
@ -178,7 +178,7 @@ func run(cartridgeFile string) error {
|
|||
for {
|
||||
_, _, err := vcs.Step(hardware.NullVideoCycleCallback)
|
||||
if err != nil {
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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":
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue