From 603ebac8a622752d7fc8b8a3eabde4fcf20cb33d Mon Sep 17 00:00:00 2001 From: Roman Boykov Date: Mon, 30 Mar 2026 17:26:12 +0300 Subject: [PATCH] Support extended-stack, code coverage, PEEKW fn in expr --- debug/breakpoint/breakpoint.go | 32 +++++-- debug/breakpoint/breakpoint_test.go | 14 +++- debug/zrcp/constants.go | 9 ++ debug/zrcp/zrcp.go | 124 +++++++++++++++++++++++++--- dezog/protocol.go | 74 ----------------- okean240/computer.go | 21 +++++ z80/c99/cpu.go | 60 +++++++++++++- z80/c99/helper.go | 1 + z80/c99/opcodes.go | 10 ++- 9 files changed, 247 insertions(+), 98 deletions(-) delete mode 100644 dezog/protocol.go diff --git a/debug/breakpoint/breakpoint.go b/debug/breakpoint/breakpoint.go index 87f094d..7381a0b 100644 --- a/debug/breakpoint/breakpoint.go +++ b/debug/breakpoint/breakpoint.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "okemu/gval" + //"okemu/okean240" "regexp" "strconv" "strings" @@ -206,24 +207,25 @@ func getUint16(name string, ctx map[string]interface{}) uint16 { return 0 } -func (b *Breakpoint) Hit(ctx map[string]interface{}) bool { +func (b *Breakpoint) Hit(parameters map[string]interface{}) bool { if !b.enabled { return false } if b.bpType == BPTypeSimplePC { - pc := getUint16("PC", ctx) + pc := getUint16("PC", parameters) if pc == b.addr { log.Debugf("Breakpoint Hit PC=0x%04X", b.addr) } return pc == b.addr } else if b.bpType == BPTypeSimpleSP { - sp := getUint16("SP", ctx) + sp := getUint16("SP", parameters) if sp >= b.addr { log.Debugf("Breakpoint Hit SP>=0x%04X", b.addr) } return sp >= b.addr } - value, err := b.eval.EvalBool(context.Background(), ctx) + bc := context.WithValue(context.Background(), "MEM", parameters["MEM"]) + value, err := b.eval.EvalBool(bc, parameters) if err != nil { fmt.Println(err) } @@ -233,7 +235,9 @@ func (b *Breakpoint) Hit(ctx map[string]interface{}) bool { var language gval.Language func init() { - language = gval.NewLanguage(gval.Base(), gval.Arithmetic(), gval.Bitmask(), gval.PropositionalLogic()) + language = gval.NewLanguage(gval.Base(), gval.Arithmetic(), gval.Bitmask(), gval.PropositionalLogic(), + gval.Function("PEEKW", cfPeekW), + ) } func (b *Breakpoint) SetExpression(expression string) error { @@ -254,3 +258,21 @@ func (b *Breakpoint) Expression() string { func (b *Breakpoint) MBank() uint8 { return b.mBank } + +func cfPeekW(ctx context.Context, arguments ...interface{}) (interface{}, error) { + if len(arguments) != 1 { + return nil, fmt.Errorf("expected 1 argument") + } + addr, ok := arguments[0].(uint) + if !ok { + return nil, fmt.Errorf("argument must be a uint") + } + memInt := ctx.Value("MEM") + memRead, ok := memInt.(func(uint16) uint8) + if !ok { + return nil, fmt.Errorf("MEM must be func(uint16) uint8") + } + lo := memRead(uint16(addr)) + hi := memRead(uint16(addr + 1)) + return (uint16(hi) << 8) | uint16(lo), nil +} diff --git a/debug/breakpoint/breakpoint_test.go b/debug/breakpoint/breakpoint_test.go index 32227bc..8f802f6 100644 --- a/debug/breakpoint/breakpoint_test.go +++ b/debug/breakpoint/breakpoint_test.go @@ -18,6 +18,7 @@ var ctx = map[string]interface{}{ const exprRep = "PC=00115h and (B=5 or BC = 5)" const exprDst = "PC==0x00115 && (B==5 || BC == 5)" +const exprFn = "PC=PEEKW(SP-2) AND SP>=100" func Test_PatchExpression(t *testing.T) { ex := patchExpression(exprRep) @@ -29,7 +30,7 @@ func Test_PatchExpression(t *testing.T) { func Test_ComplexExpr(t *testing.T) { - b, e := NewBreakpoint(expr1) + b, e := NewBreakpoint(expr1, 1) //e := b.SetExpression(exp1) if e != nil { t.Error(e) @@ -45,7 +46,7 @@ func Test_ComplexExpr(t *testing.T) { const expSimplePC = "PC=00119h" func Test_BPSetPC(t *testing.T) { - b, e := NewBreakpoint(expSimplePC) + b, e := NewBreakpoint(expSimplePC, 1) if e != nil { t.Error(e) } else if b != nil { @@ -75,3 +76,12 @@ func Test_GetCtx(t *testing.T) { t.Errorf("PC value not found in context") } } + +func Test_PeekWFn(t *testing.T) { + b, e := NewBreakpoint(exprFn, 1) + if e != nil { + t.Error(e) + } + b.enabled = true + b.Hit(ctx) +} diff --git a/debug/zrcp/constants.go b/debug/zrcp/constants.go index d0a21a0..da7e285 100644 --- a/debug/zrcp/constants.go +++ b/debug/zrcp/constants.go @@ -12,3 +12,12 @@ const getMachineResponse = "64K RAM, no ZX\n" const respErrorLoading = "ERROR loading file" const quitResponse = "Sayonara baby\n" const runUntilBPMessage = "Running until a breakpoint, key press or data sent, menu opening or other event\n" + +var PushValueTypeName = []string{ + "default", + "call", + "rst", + "push", + "maskable_interrupt", + "non_maskable_interrupt", +} diff --git a/debug/zrcp/zrcp.go b/debug/zrcp/zrcp.go index 36db7b9..33e0dd2 100644 --- a/debug/zrcp/zrcp.go +++ b/debug/zrcp/zrcp.go @@ -189,7 +189,7 @@ var commandHandlers = map[string]CommandHandler{ "about": (*ZRCP).handleAbout, "clear-membreakpoints": (*ZRCP).handleClearMemBreakpoints, "close-all-menus": (*ZRCP).handleEmptyHandler, - "cpu-code-coverage": (*ZRCP).handleEmptyHandler, + "cpu-code-coverage": (*ZRCP).handleCPUCodeCoverage, "cpu-history": (*ZRCP).handleCPUHistory, "cpu-step": (*ZRCP).handleCpuStep, "disable-breakpoint": (*ZRCP).handleDisableBreakpoint, @@ -239,9 +239,8 @@ func (p *ZRCP) handleDisassemble() (string, error) { func convertToUint16(s string) (uint16, error) { v := strings.TrimSpace(strings.ToUpper(s)) base := 0 - if strings.HasSuffix(v, "h") || strings.HasSuffix(v, "H") { + if strings.HasSuffix(v, "H") { v = strings.TrimSuffix(v, "H") - v = strings.TrimSuffix(v, "h") base = 16 } a, e := strconv.ParseUint(v, base, 16) @@ -282,21 +281,26 @@ func (p *ZRCP) SetMemBreakpoint(param string) string { func (p *ZRCP) handleCPUHistory() (string, error) { params := strings.Split(p.params, " ") - if len(params) == 0 { - return "", errors.New("error, no parameter") + if len(params) < 1 { + return "", errors.New("error, no parameters") } + cmd := params[0] nspe := errors.New("error, no second parameter") + switch cmd { + case "enabled": - if len(params) != 2 { + if len(params) < 2 { return "", nspe } p.debugger.SetCpuHistoryEnabled(params[1] == "yes") + case "clear": p.debugger.CpuHistoryClear() + case "started": - if len(params) != 2 { + if len(params) < 2 { return "", nspe } p.debugger.SetCpuHistoryStarted(params[1] == "yes") @@ -322,8 +326,13 @@ func (p *ZRCP) handleCPUHistory() (string, error) { return p.stateResponse(history), nil } return "", errors.New("ERROR: index out of range") + case "ignrephalt": + // ignore + default: + return "", errors.New("error: unknown history command: " + cmd) } - return "", errors.New("error: unknown history command: " + cmd) + + return "", nil } func (p *ZRCP) handleLoadBinary() (string, error) { @@ -461,6 +470,19 @@ func (p *ZRCP) handleSetRegister() (string, error) { return "error", errors.New("invalid set register value") } switch params[0] { + case "AF": + state.A = uint8(val >> 8) + state.Flags.SetFlags(uint8(val)) + case "BC": + state.B = uint8(val >> 8) + state.C = uint8(val) + case "DE": + state.D = uint8(val >> 8) + state.E = uint8(val) + case "HL": + state.H = uint8(val >> 8) + state.L = uint8(val) + // ------------------------------ case "SP": state.SP = uint16(val) case "PC": @@ -469,8 +491,25 @@ func (p *ZRCP) handleSetRegister() (string, error) { state.IX = uint16(val) case "IY": state.IY = uint16(val) + // ------------------------------ + case "AF'": + state.AAlt = uint8(val >> 8) + state.FlagsAlt.SetFlags(uint8(val)) + case "BC'": + state.BAlt = uint8(val >> 8) + state.CAlt = uint8(val) + case "DE'": + state.DAlt = uint8(val >> 8) + state.EAlt = uint8(val) + case "HL'": + state.HAlt = uint8(val >> 8) + state.LAlt = uint8(val) + + // ------------------------------ case "A": state.A = uint8(val) + case "F": + state.Flags.SetFlags(uint8(val)) case "B": state.B = uint8(val) case "C": @@ -483,6 +522,24 @@ func (p *ZRCP) handleSetRegister() (string, error) { state.H = uint8(val) case "L": state.L = uint8(val) + // ------------------------------ + case "A'": + state.AAlt = uint8(val) + case "F'": + state.FlagsAlt.SetFlags(uint8(val)) + case "B'": + state.BAlt = uint8(val) + case "C'": + state.CAlt = uint8(val) + case "D'": + state.DAlt = uint8(val) + case "E'": + state.EAlt = uint8(val) + case "H'": + state.HAlt = uint8(val) + case "L'": + state.LAlt = uint8(val) + // ------------------------------ case "I": state.I = uint8(val) case "R": @@ -537,10 +594,14 @@ func (p *ZRCP) getExtendedStack() (string, error) { resp := "" spEnd := sp - uint16(size*2) - for i := sp; i > spEnd; i -= 2 { - resp += fmt.Sprintf("%04XH default\n", p.computer.MemRead(i)) + es, err := p.computer.ExtendedStack() + if err == nil { + for i := sp; i > spEnd; i -= 2 { + resp += fmt.Sprintf("%04XH %s\n", p.computer.MemRead(i), PushValueTypeName[es[i]]) + } } - return resp, nil + log.Tracef("extended-stack get: %s", resp) + return resp, err } func (p *ZRCP) handleSetBreakpoint() (string, error) { @@ -742,8 +803,47 @@ func (p *ZRCP) handleSetBreakpointPassCount() (string, error) { } func (p *ZRCP) handleExtendedStack() (string, error) { - if strings.HasPrefix(p.params, "get") { + params := strings.Split(p.params, " ") + if len(params) < 1 { + return "", errors.New("error, not enough params") + } + cmd := params[0] + if cmd == "get" { return p.getExtendedStack() + } else if cmd == "enabled" { + if len(params) < 2 { + return "", errors.New("error, expected yes|no") + } + p.computer.SetExtendedStack(params[1] == "yes") + } else { + return "", errors.New("error, unknown sub-command: " + cmd) } return "", nil } + +// handleCPUCodeCoverage Handle commands: +// cpu-code-coverage enabled yes +// cpu-code-coverage enabled no +// cpu-code-coverage clear +func (p *ZRCP) handleCPUCodeCoverage() (string, error) { + command := strings.Split(p.params, " ") + if len(command) < 1 { + return "", errors.New("error, not enough arguments") + } + cmd := command[0] + resp := "" + switch cmd { + case "enabled": + if len(command) < 2 { + return "", errors.New("error, not arguments for enabled [yas|no]") + } + p.computer.SetCodeCoverage(command[1] == "yes") + case "clear": + p.computer.ClearCodeCoverage() + case "get": + for addr, _ := range p.computer.CodeCoverage() { + resp += fmt.Sprintf("%04X ", addr) + } + } + return resp, nil +} diff --git a/dezog/protocol.go b/dezog/protocol.go deleted file mode 100644 index 0dd79af..0000000 --- a/dezog/protocol.go +++ /dev/null @@ -1,74 +0,0 @@ -package dezog - -// Command - DZRP protocol command -type Command struct { - length uint32 // Length of the payload data. (little endian) - sequence uint8 // Sequence number, 1-255. Increased with each command - command uint8 // Command ID - data []byte // Payload -} - -// Response - response on DZRP protocol command -type Response struct { - length uint32 // Length of the following data beginning with the sequence number. (little endian) - sequence uint8 // Sequence number, same as command. - data []byte // Payload -} - -// Notification - message from emulator to DEZOG -type Notification struct { - seqNo uint8 // Instead of Seq No. - command uint8 // NTF_PAUSE = 1 - breakReason uint8 // Break reason: 0 = no reason (e.g. a step-over), 1 = manual break, 2 = breakpoint hit, - // 3 = watchpoint hit read access, 4 = watchpoint hit write access, 255 = some other reason: - //the reason string might have useful information for the user - address uint16 // Breakpoint or watchpoint address. - bank uint8 // The bank+1 of the breakpoint or watchpoint address. - reason string // Null-terminated break reason string. Might in theory have almost 2^32 byte length. - // In practice, it will be normally less than 256. If reason string is empty it will contain at - // least a 0. -} - -const NTF_PAUSE = 1 - -// Notification, Break reasons -const ( - BR_REASON_MANUAL = 1 - BR_REASON_BP_HIT = 2 - BR_REASON_WP_HIT_R = 3 - BR_REASON_WP_HIT_W = 4 - BR_REASON_OTHER = 255 -) - -// DEZOG Commands to emulator -const ( - CMD_INIT = 1 - CMD_CLOSE = 2 - CMD_GET_REGISTERS = 3 - CMD_SET_REGISTER = 4 - CMD_WRITE_BANK = 5 - CMD_CONTINUE = 6 - CMD_PAUSE = 7 - CMD_READ_MEM = 8 - CMD_WRITE_MEM = 9 - CMD_SET_SLOT = 10 - CMD_GET_TBBLUE_REG = 11 - CMD_SET_BORDER = 12 - CMD_SET_BREAKPOINTS = 13 - CMD_RESTORE_MEM = 14 - CMD_LOOPBACK = 15 - CMD_GET_SPRITES_PALETTE = 16 - CMD_GET_SPRITES_CLIP_WINDOW_AND_CONTROL = 17 - CMD_GET_SPRITES = 18 - CMD_GET_SPRITE_PATTERNS = 19 - CMD_READ_PORT = 20 - CMD_WRITE_PORT = 21 - CMD_EXEC_ASM = 22 - CMD_INTERRUPT_ON_OFF = 23 - CMD_ADD_BREAKPOINT = 40 - CMD_REMOVE_BREAKPOINT = 41 - CMD_ADD_WATCHPOINT = 42 - CMD_REMOVE_WATCHPOINT = 43 - CMD_READ_STATE = 50 - CMD_WRITE_STATE = 51 -) diff --git a/okean240/computer.go b/okean240/computer.go index a222d1b..c234e2b 100644 --- a/okean240/computer.go +++ b/okean240/computer.go @@ -173,6 +173,7 @@ func (c *ComputerType) getContext() map[string]interface{} { context["DE"] = uint16(s.D)<<8 | uint16(s.E) context["HL"] = uint16(s.H)<<8 | uint16(s.L) context["AF"] = uint16(s.A)<<8 | uint16(s.Flags.GetFlags()) + context["MEM"] = c.memory.MemRead return context } @@ -458,3 +459,23 @@ func (c *ComputerType) AutoLoadFloppy() { } } } + +func (c *ComputerType) ClearCodeCoverage() { + c.cpu.ClearCodeCoverage() +} + +func (c *ComputerType) SetCodeCoverage(enabled bool) { + c.cpu.SetCodeCoverage(enabled) +} + +func (c *ComputerType) CodeCoverage() map[uint16]bool { + return c.cpu.CodeCoverage() +} + +func (c *ComputerType) SetExtendedStack(enabled bool) { + c.cpu.SetExtendedStack(enabled) +} + +func (c *ComputerType) ExtendedStack() ([]byte, error) { + return c.cpu.ExtendedStack() +} diff --git a/z80/c99/cpu.go b/z80/c99/cpu.go index af88099..57646f2 100644 --- a/z80/c99/cpu.go +++ b/z80/c99/cpu.go @@ -1,6 +1,18 @@ package c99 -import "okemu/z80" +import ( + "errors" + "okemu/z80" +) + +const ( + PushValueTypeDefault = iota + PushValueTypeCall + PushValueTypeRst + PushValueTypePush + PushValueTypeMaskableInt + PushValueTypeNonMaskableInt +) type Z80 struct { @@ -33,8 +45,12 @@ type Z80 struct { intPending bool nmiPending bool - core z80.MemIoRW - memAccess map[uint16]byte + core z80.MemIoRW + memAccess map[uint16]byte + codeCoverageEnabled bool + codeCoverage map[uint16]bool + extendedStackEnabled bool + extendedStack [65536]uint8 } const ( @@ -92,13 +108,17 @@ func New(core z80.MemIoRW) *Z80 { z.intPending = false z.nmiPending = false z.intData = 0 + z.codeCoverageEnabled = false + z.codeCoverage = make(map[uint16]bool) return &z } // RunInstruction executes the next instruction in memory + handles interrupts func (z *Z80) RunInstruction() (uint32, *map[uint16]byte) { z.memAccess = map[uint16]byte{} - + if z.codeCoverageEnabled { + z.codeCoverage[z.pc] = true + } pre := z.cycleCount if z.isHalted { z.execOpcode(0x00) @@ -223,3 +243,35 @@ func (z *Z80) getAltFlags() z80.FlagsType { func (z *Z80) PC() uint16 { return z.pc } + +func (z *Z80) ClearCodeCoverage() { + clear(z.codeCoverage) +} + +func (z *Z80) SetCodeCoverage(enabled bool) { + z.codeCoverageEnabled = enabled + if !enabled { + clear(z.codeCoverage) + } +} + +func (z *Z80) CodeCoverage() map[uint16]bool { + return z.codeCoverage +} + +func (z *Z80) SetExtendedStack(enabled bool) { + z.extendedStackEnabled = enabled + if enabled { + for addr := 0; addr < 65536; addr++ { + z.extendedStack[addr] = PushValueTypeDefault + } + } +} + +func (z *Z80) ExtendedStack() ([]byte, error) { + var err error + if !z.extendedStackEnabled { + err = errors.New("error, z80: ExtendedStack disabled") + } + return z.extendedStack[:], err +} diff --git a/z80/c99/helper.go b/z80/c99/helper.go index 6569fe9..a659a9a 100644 --- a/z80/c99/helper.go +++ b/z80/c99/helper.go @@ -28,6 +28,7 @@ func (z *Z80) ww(addr uint16, val uint16) { func (z *Z80) pushW(val uint16) { z.sp -= 2 z.ww(z.sp, val) + z.extendedStack[z.sp] = PushValueTypePush } func (z *Z80) popW() uint16 { diff --git a/z80/c99/opcodes.go b/z80/c99/opcodes.go index b8ab38c..dd56540 100644 --- a/z80/c99/opcodes.go +++ b/z80/c99/opcodes.go @@ -20,6 +20,7 @@ func (z *Z80) condJump(condition bool) { // calls to next word in memory func (z *Z80) call(addr uint16) { z.pushW(z.pc) + z.extendedStack[z.sp] = PushValueTypeCall z.pc = addr z.memPtr = addr } @@ -1155,21 +1156,28 @@ func (z *Z80) execOpcode(opcode byte) { case 0xC7: z.call(0x00) // rst 0 + z.extendedStack[z.sp] = PushValueTypeRst case 0xCF: z.call(0x08) // rst 1 + z.extendedStack[z.sp] = PushValueTypeRst case 0xD7: z.call(0x10) // rst 2 + z.extendedStack[z.sp] = PushValueTypeRst case 0xDF: z.call(0x18) // rst 3 + z.extendedStack[z.sp] = PushValueTypeRst case 0xE7: z.call(0x20) // rst 4 + z.extendedStack[z.sp] = PushValueTypeRst case 0xEF: z.call(0x28) // rst 5 + z.extendedStack[z.sp] = PushValueTypeRst case 0xF7: z.call(0x30) // rst 6 + z.extendedStack[z.sp] = PushValueTypeRst case 0xFF: z.call(0x38) // rst 7 - + z.extendedStack[z.sp] = PushValueTypeRst case 0xC5: z.pushW(z.bc()) // push bc case 0xD5: