Support extended-stack, code coverage, PEEKW fn in expr

This commit is contained in:
Роман Бойков 2026-03-30 17:26:12 +03:00
parent dd39115296
commit 603ebac8a6
9 changed files with 247 additions and 98 deletions

View File

@ -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
}

View File

@ -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)
}

View File

@ -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",
}

View File

@ -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,10 +326,15 @@ 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 "", nil
}
func (p *ZRCP) handleLoadBinary() (string, error) {
params := strings.Split(p.params, " ")
loadError := errors.New(respErrorLoading)
@ -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)
es, err := p.computer.ExtendedStack()
if err == nil {
for i := sp; i > spEnd; i -= 2 {
resp += fmt.Sprintf("%04XH default\n", p.computer.MemRead(i))
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
}

View File

@ -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
)

View File

@ -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()
}

View File

@ -1,6 +1,18 @@
package c99
import "okemu/z80"
import (
"errors"
"okemu/z80"
)
const (
PushValueTypeDefault = iota
PushValueTypeCall
PushValueTypeRst
PushValueTypePush
PushValueTypeMaskableInt
PushValueTypeNonMaskableInt
)
type Z80 struct {
@ -35,6 +47,10 @@ type Z80 struct {
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
}

View File

@ -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 {

View File

@ -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: