mirror of
https://github.com/romychs/Ocean-240.2-Emulator.git
synced 2026-04-21 11:03:21 +03:00
557 lines
14 KiB
Go
557 lines
14 KiB
Go
package dzrp
|
|
|
|
import (
|
|
"bufio"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"okemu/config"
|
|
"okemu/debug"
|
|
"okemu/debug/breakpoint"
|
|
"okemu/okean240"
|
|
"okemu/z80/dis"
|
|
"strconv"
|
|
|
|
log "github.com/sirupsen/logrus"
|
|
)
|
|
|
|
type DZRP struct {
|
|
port string
|
|
config *config.OkEmuConfig
|
|
debugger *debug.Debugger
|
|
disassembler *dis.Disassembler
|
|
computer *okean240.ComputerType
|
|
conn net.Conn
|
|
reader *bufio.Reader
|
|
writer *bufio.Writer
|
|
cmd *Command
|
|
}
|
|
|
|
// SetupTcpHandler Setup TCP listener, handle connections
|
|
|
|
func NewDZRP(config *config.OkEmuConfig, debug *debug.Debugger, dissasm *dis.Disassembler, comp *okean240.ComputerType) *DZRP {
|
|
return &DZRP{
|
|
port: config.Debugger.Host + ":" + strconv.Itoa(config.Debugger.Port),
|
|
debugger: debug,
|
|
disassembler: dissasm,
|
|
computer: comp,
|
|
}
|
|
}
|
|
|
|
func (p *DZRP) SetupTcpHandler() {
|
|
|
|
var err error
|
|
var l net.Listener
|
|
|
|
l, err = net.Listen("tcp4", p.port)
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
return
|
|
}
|
|
|
|
defer func(l net.Listener) {
|
|
err := l.Close()
|
|
if err != nil {
|
|
log.Warnf("Error closing listener connection %v", err)
|
|
}
|
|
}(l)
|
|
|
|
log.Infof("Ready for debugger connections on %s", p.port)
|
|
for {
|
|
p.conn, err = l.Accept()
|
|
if err != nil {
|
|
log.Errorf("Accept connection: %v", err)
|
|
return
|
|
}
|
|
go p.handleConnection()
|
|
}
|
|
|
|
}
|
|
|
|
// Receive messages, split to strings and parse
|
|
func (p *DZRP) handleConnection() {
|
|
p.reader = bufio.NewReader(p.conn)
|
|
p.writer = bufio.NewWriter(p.conn)
|
|
var command Command
|
|
n := 0
|
|
for {
|
|
// receive command packet byte by byte
|
|
b, err := p.reader.ReadByte()
|
|
if err != nil {
|
|
if err == io.EOF {
|
|
break
|
|
} else {
|
|
log.Errorf("TCP error: %v", err)
|
|
p.debugger.SetStepMode(false)
|
|
return
|
|
}
|
|
}
|
|
switch n {
|
|
case 0:
|
|
command.Len = uint32(b)
|
|
case 1:
|
|
command.Len |= uint32(b) << 8
|
|
case 2:
|
|
command.Len |= uint32(b) << 16
|
|
case 3:
|
|
command.Len |= uint32(b) << 24
|
|
case 4:
|
|
command.Sn = b
|
|
case 5:
|
|
command.Id = b
|
|
default:
|
|
command.Payload = append(command.Payload, b)
|
|
}
|
|
if n >= 5 && int(command.Len) == len(command.Payload) {
|
|
p.cmd = &command
|
|
if p.handleCommand() {
|
|
break
|
|
}
|
|
command.Len = 0
|
|
command.Payload = []uint8{}
|
|
n = 0
|
|
} else {
|
|
n++
|
|
}
|
|
}
|
|
log.Debug("Closing connection")
|
|
|
|
p.debugger.SetStepMode(false)
|
|
_ = p.writer.Flush()
|
|
p.writer = nil
|
|
p.reader = nil
|
|
err := p.conn.Close()
|
|
if err != nil {
|
|
log.Warnf("Can not close TCP socket: %v", err)
|
|
}
|
|
}
|
|
|
|
// writeResponse Write response to command back to client
|
|
func (p *DZRP) writeResponse(response *Response) error {
|
|
// log.Infof("Ready for debugger connections on %s", port)
|
|
// Send Len
|
|
l := response.Len
|
|
for c := 0; c < 4; c++ {
|
|
e := p.writer.WriteByte(byte(l))
|
|
if e != nil {
|
|
log.Warnf("Error writing Len: %v", e)
|
|
}
|
|
l = l >> 8
|
|
}
|
|
// Send Sn
|
|
e := p.writer.WriteByte(response.Sn)
|
|
if e != nil {
|
|
log.Warnf("Error writing Sn: %v", e)
|
|
return e
|
|
}
|
|
// Send Payload
|
|
for _, b := range response.Payload {
|
|
e := p.writer.WriteByte(b)
|
|
if e != nil {
|
|
log.Warnf("Error writing payload: %v", e)
|
|
return e
|
|
}
|
|
}
|
|
e = p.writer.Flush()
|
|
if e != nil {
|
|
log.Warnf("Error flushing response: %v", e)
|
|
return e
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// BreakpointHit Send notification message to Dezog on BP Hit
|
|
func (p *DZRP) BreakpointHit(number uint16, typ byte) {
|
|
// turn off temporary breakpoints on any hit
|
|
p.debugger.SetBreakpointEnabled(breakpoint.BpTmp1, false)
|
|
p.debugger.SetBreakpointEnabled(breakpoint.BpTmp2, false)
|
|
if p.writer != nil {
|
|
err := p.writeResponse(p.buildBpHitResponse(number, typ))
|
|
if err != nil {
|
|
log.Warnf("NTF_PAUSE, write response err: %v", err)
|
|
}
|
|
} else {
|
|
log.Warn("NTF_PAUSE, writer is nil")
|
|
}
|
|
}
|
|
|
|
func (p *DZRP) buildBpHitResponse(number uint16, typ byte) *Response {
|
|
bpReason, bpAddr, mBank := p.getBpReasonAndAddr(number, typ)
|
|
rsn := getBpReason(bpReason, bpAddr)
|
|
// msg - ASCIIZ bytes
|
|
msg := []byte(rsn)
|
|
msg = append(msg, byte(0))
|
|
|
|
rep := []byte{
|
|
1, // NTF_PAUSE
|
|
bpReason,
|
|
byte(bpAddr), byte(bpAddr >> 8),
|
|
mBank, // bank
|
|
}
|
|
rep = append(rep, msg...)
|
|
|
|
log.Debugf("NTF_PAUSE %s resp: %v", rsn, PayloadToString(rep))
|
|
|
|
return &Response{
|
|
Len: uint32(len(rep) + 1),
|
|
Sn: 0,
|
|
Payload: rep,
|
|
}
|
|
}
|
|
|
|
// handleCommand Handle command received from client, return true if last command is CMD_CLOSE
|
|
func (p *DZRP) handleCommand() bool {
|
|
log.Debugf("Handling command: %s", p.cmd.toString())
|
|
var err error
|
|
var resp *Response
|
|
handler, ok := commandHandlers[int(p.cmd.Id)]
|
|
if ok {
|
|
resp, err = handler(p)
|
|
} else {
|
|
//resp = NewResponse(p.cmd, nil)
|
|
err = errors.New("unknown command Id: " + strconv.Itoa(int(p.cmd.Id)))
|
|
}
|
|
if err == nil {
|
|
err = p.writeResponse(resp)
|
|
}
|
|
if err != nil {
|
|
log.Errorf("Error handling command: %v", err)
|
|
}
|
|
return p.cmd.Id == CMD_CLOSE
|
|
}
|
|
|
|
type CommandHandler func(*DZRP) (*Response, error)
|
|
|
|
var commandHandlers = map[int]CommandHandler{
|
|
CMD_INIT: (*DZRP).handleCmdInit, //1
|
|
CMD_CLOSE: (*DZRP).handleCmdClose, //2
|
|
CMD_GET_REGISTERS: (*DZRP).handleCmdGetRegisters, //3
|
|
CMD_SET_REGISTER: (*DZRP).handleCmdSetRegister, //4
|
|
CMD_CONTINUE: (*DZRP).handleCmdContinue, //6
|
|
CMD_PAUSE: (*DZRP).handleCmdPause, // 7
|
|
CMD_READ_MEM: (*DZRP).handleCmdReadMem, //8
|
|
CMD_WRITE_MEM: (*DZRP).handleCmdWriteMem, //9
|
|
CMD_LOOPBACK: (*DZRP).handleCmdLoopback, // 15
|
|
CMD_READ_PORT: (*DZRP).handleCmdReadPort, // 20
|
|
CMD_WRITE_PORT: (*DZRP).handleCmdWritePort, // 21
|
|
CMD_INTERRUPT_ON_OFF: (*DZRP).handleCmdInterruptOnOff, // 23
|
|
CMD_ADD_BREAKPOINT: (*DZRP).handleCmdAddBreakpoint, //40
|
|
CMD_REMOVE_BREAKPOINT: (*DZRP).handleCmdRemoveBreakpoint, //41
|
|
|
|
}
|
|
|
|
func (p *DZRP) handleCmdInit() (*Response, error) {
|
|
if len(p.cmd.Payload) < 4 {
|
|
return nil, errors.New("too short payload")
|
|
}
|
|
p.debugger.SetStepMode(true)
|
|
p.debugger.SetBreakpointsEnabled(true)
|
|
p.debugger.ClearBreakpoints()
|
|
p.debugger.ClearMemBreakpoints()
|
|
|
|
app := string(p.cmd.Payload[3 : len(p.cmd.Payload)-1])
|
|
log.Debugf("CMD_INIT: client version %d.%d.%d App: %s", p.cmd.Payload[0], p.cmd.Payload[1], p.cmd.Payload[2], app)
|
|
|
|
payload := []byte{0, VersionMajor, VersionMinor, VersionPatch, MachineZX128K}
|
|
payload = append(payload, []byte(AppName)...)
|
|
payload = append(payload, 0)
|
|
return NewResponse(p.cmd, payload), nil
|
|
}
|
|
|
|
func (p *DZRP) handleCmdClose() (*Response, error) {
|
|
log.Debug("CMD_CLOSE")
|
|
return NewResponse(p.cmd, nil), nil
|
|
}
|
|
|
|
func (p *DZRP) handleCmdPause() (*Response, error) {
|
|
log.Debug("CMD_PAUSE")
|
|
p.debugger.SetStepMode(true)
|
|
//return NewResponse(p.cmd, []byte{p.cmd.Sn}), nil
|
|
return NewResponse(p.cmd, nil), nil
|
|
}
|
|
|
|
func (p *DZRP) handleCmdReadMem() (*Response, error) {
|
|
if len(p.cmd.Payload) < 5 {
|
|
return nil, errors.New("too short payload")
|
|
}
|
|
addr := uint16(p.cmd.Payload[2])<<8 + uint16(p.cmd.Payload[1])
|
|
size := uint16(p.cmd.Payload[4])<<8 + uint16(p.cmd.Payload[3])
|
|
|
|
log.Debugf("CMD_READ_MEM[0x%04X] len: 0x%04X", addr, size)
|
|
mem := make([]byte, size)
|
|
//mem[0] = cmd.Sn
|
|
for i := 0; i < int(size); i++ {
|
|
mem[i] = p.computer.MemRead(addr)
|
|
addr++
|
|
}
|
|
return NewResponse(p.cmd, mem), nil
|
|
}
|
|
|
|
func (p *DZRP) handleCmdWriteMem() (*Response, error) {
|
|
if len(p.cmd.Payload) < 4 {
|
|
return nil, errors.New("too short payload")
|
|
}
|
|
addr := uint16(p.cmd.Payload[1]) + uint16(p.cmd.Payload[2])<<8
|
|
log.Debugf("CMD_WRITE_MEM[0x%04X] len: 0x%04X", addr, len(p.cmd.Payload)-3)
|
|
for i := 3; i < len(p.cmd.Payload); i++ {
|
|
p.computer.MemWrite(addr, p.cmd.Payload[i])
|
|
addr++
|
|
}
|
|
return NewResponse(p.cmd, []byte{p.cmd.Sn}), nil
|
|
}
|
|
|
|
func (p *DZRP) handleCmdSetRegister() (*Response, error) {
|
|
lo := p.cmd.Payload[1]
|
|
hi := p.cmd.Payload[2]
|
|
word := uint16(hi)<<8 | uint16(lo)
|
|
s := p.computer.CPUState()
|
|
|
|
switch p.cmd.Payload[0] {
|
|
case RegPC:
|
|
s.PC = word
|
|
log.Debugf("Set PC=0x%04X", s.PC)
|
|
case RegSP:
|
|
s.SP = word
|
|
log.Debugf("Set SP=0x%04X", s.PC)
|
|
case RegAF:
|
|
s.A = hi
|
|
s.Flags.SetFlags(lo)
|
|
case RegBC:
|
|
s.B = hi
|
|
s.C = lo
|
|
case RegDE:
|
|
s.D = hi
|
|
s.E = lo
|
|
case RegHL:
|
|
s.H = hi
|
|
s.L = lo
|
|
case RegIX:
|
|
s.IX = word
|
|
case RegIY:
|
|
s.IY = word
|
|
case RegAF_:
|
|
s.AAlt = hi
|
|
s.FlagsAlt.SetFlags(lo)
|
|
case RegBC_:
|
|
s.BAlt = hi
|
|
s.CAlt = lo
|
|
case RegDE_:
|
|
s.DAlt = hi
|
|
s.EAlt = lo
|
|
case RegHL_:
|
|
s.HAlt = hi
|
|
s.LAlt = lo
|
|
case RegIM:
|
|
s.IMode = lo
|
|
case RegF:
|
|
s.Flags.SetFlags(lo)
|
|
case RegA:
|
|
s.A = lo
|
|
case RegC:
|
|
s.C = lo
|
|
case RegB:
|
|
s.B = lo
|
|
case RegE:
|
|
s.E = lo
|
|
case RegD:
|
|
s.D = lo
|
|
case RegL:
|
|
s.L = lo
|
|
case RegH:
|
|
s.H = lo
|
|
case RegIXL:
|
|
s.IX = s.IX&0xff00 | uint16(lo)
|
|
case RegIXH:
|
|
s.IX = (uint16(lo) << 8) | (s.IX & 0x00ff)
|
|
case RegIYL:
|
|
s.IY = s.IY&0xff00 | uint16(lo)
|
|
case RegIYH:
|
|
s.IY = (uint16(lo) << 8) | (s.IY & 0x00ff)
|
|
case RegF_:
|
|
s.FlagsAlt.SetFlags(lo)
|
|
case RegA_:
|
|
s.AAlt = lo
|
|
case RegC_:
|
|
s.CAlt = lo
|
|
case RegB_:
|
|
s.BAlt = lo
|
|
case RegE_:
|
|
s.EAlt = lo
|
|
case RegD_:
|
|
s.DAlt = lo
|
|
case RegL_:
|
|
s.LAlt = lo
|
|
case RegH_:
|
|
s.HAlt = lo
|
|
case RegR:
|
|
s.R = lo
|
|
case RegI:
|
|
s.I = lo
|
|
default:
|
|
return nil, errors.New("unknown register no: " + strconv.Itoa(int(p.cmd.Payload[0])))
|
|
}
|
|
p.computer.SetCPUState(s)
|
|
//return NewResponse(p.cmd, []byte{p.cmd.Sn}), nil
|
|
return NewResponse(p.cmd, nil), nil
|
|
}
|
|
|
|
func (p *DZRP) handleCmdGetRegisters() (*Response, error) {
|
|
s := p.computer.CPUState()
|
|
resp := []byte{
|
|
//p.cmd.Sn,
|
|
byte(s.PC), byte(s.PC >> 8),
|
|
byte(s.SP), byte(s.SP >> 8),
|
|
s.Flags.GetFlags(), s.A,
|
|
s.C, s.B,
|
|
s.E, s.D,
|
|
s.L, s.H,
|
|
byte(s.IX), byte(s.IX >> 8),
|
|
byte(s.IY), byte(s.IY >> 8),
|
|
s.FlagsAlt.GetFlags(), s.AAlt,
|
|
s.CAlt, s.BAlt,
|
|
s.EAlt, s.DAlt,
|
|
s.LAlt, s.HAlt,
|
|
s.R,
|
|
s.I,
|
|
s.IMode,
|
|
0, // reserved
|
|
0, // Nslots. The number of slots that will follow.
|
|
}
|
|
log.Debugf("CMD_GET_REGISTERS resp: %v", resp)
|
|
return NewResponse(p.cmd, resp), nil
|
|
}
|
|
|
|
func (p *DZRP) handleCmdContinue() (*Response, error) {
|
|
|
|
eb1 := p.cmd.Payload[0] != 0
|
|
ab1 := (uint16(p.cmd.Payload[2]) << 8) | uint16(p.cmd.Payload[1])
|
|
p.setTempBreakpoint(eb1, breakpoint.BpTmp1, ab1)
|
|
eb2 := p.cmd.Payload[3] != 0
|
|
ab2 := (uint16(p.cmd.Payload[5]) << 8) | uint16(p.cmd.Payload[4])
|
|
p.setTempBreakpoint(eb2, breakpoint.BpTmp2, ab2)
|
|
|
|
log.Debugf("CMD_CONTINUE BP1 en: %v, addr: 0x%04X; BP2 en: %v, addr: 0x%04X; AC: 0x%02X",
|
|
eb1, ab1, eb2, ab2, p.cmd.Payload[6])
|
|
|
|
p.debugger.SetRunMode(true)
|
|
|
|
//return NewResponse(p.cmd, []byte{p.cmd.Sn}), nil
|
|
return NewResponse(p.cmd, nil), nil
|
|
}
|
|
|
|
func (p *DZRP) setTempBreakpoint(ena bool, no uint16, addr uint16) {
|
|
if ena {
|
|
e := p.debugger.SetBreakpoint(no, fmt.Sprintf("PC=%04Xh", addr), 0)
|
|
if e != nil {
|
|
log.Debugf("setTmpBreakpoint err: %v", e)
|
|
}
|
|
p.debugger.SetBreakpointEnabled(no, true)
|
|
} else {
|
|
p.debugger.SetBreakpointEnabled(no, false)
|
|
}
|
|
}
|
|
|
|
func (p *DZRP) getBpReasonAndAddr(number uint16, typ byte) (reason byte, addr uint16, mBank uint8) {
|
|
reason = BprStepOver // default StepOver
|
|
addr = number
|
|
mBank = uint8(1)
|
|
if typ >= 1 && typ <= 2 { // 1-rd, 2-wr
|
|
reason = typ + 2
|
|
} else {
|
|
if number != breakpoint.BpTmp1 && number != breakpoint.BpTmp2 {
|
|
// bp hit
|
|
mBank = p.debugger.BreakpointMBank(addr)
|
|
reason = BprHit
|
|
}
|
|
addr = p.computer.CPUState().PC
|
|
}
|
|
return reason, addr, mBank
|
|
}
|
|
|
|
func (p *DZRP) handleCmdAddBreakpoint() (*Response, error) {
|
|
if len(p.cmd.Payload) < 4 {
|
|
return nil, errors.New("too short payload")
|
|
}
|
|
addr := (uint16(p.cmd.Payload[1]) << 8) | uint16(p.cmd.Payload[0])
|
|
mBank := p.cmd.Payload[2]
|
|
isCond := p.cmd.Payload[3] != 0
|
|
var expr string
|
|
if isCond {
|
|
expr = string(p.cmd.Payload[3 : len(p.cmd.Payload)-3])
|
|
} else {
|
|
expr = fmt.Sprintf("PC=%04Xh", addr)
|
|
}
|
|
log.Debugf("CMD_ADD_BREAKPOINT addr: 0x%04X, bank: %d, cond: '%s'", addr, mBank, expr)
|
|
bpNum, err := p.debugger.AddBreakpoint(expr, mBank)
|
|
if err != nil {
|
|
log.Debugf("SetBreakpoint err: %v", err)
|
|
return nil, err
|
|
}
|
|
if bpNum == breakpoint.MaxBreakpoints {
|
|
bpNum = 0
|
|
}
|
|
//bpNum := p.debugger.GetBreakpointNum()
|
|
//if bpNum < breakpoint.MaxBreakpoints {
|
|
// err := p.debugger.SetBreakpoint(bpNum, cond, mBank)
|
|
// p.debugger.SetBreakpointEnabled(bpNum, true)
|
|
// if err != nil {
|
|
// log.Debugf("SetBreakpoint err: %v", err)
|
|
// return nil, err
|
|
// }
|
|
//} else {
|
|
// bpNum = 0
|
|
//}
|
|
return NewResponse(p.cmd, []byte{byte(bpNum), byte(bpNum >> 8)}), nil
|
|
}
|
|
|
|
func getBpReason(reason byte, addr uint16) string {
|
|
rsn, ok := BprReasons[int(reason)]
|
|
if !ok {
|
|
rsn = strconv.Itoa(int(reason))
|
|
}
|
|
return fmt.Sprintf("BP: '%s', at: 0x%04X", rsn, addr)
|
|
}
|
|
|
|
func (p *DZRP) handleCmdRemoveBreakpoint() (*Response, error) {
|
|
if len(p.cmd.Payload) < 2 {
|
|
return nil, errors.New("too short payload")
|
|
}
|
|
bpNum := (uint16(p.cmd.Payload[1]) << 8) | uint16(p.cmd.Payload[0])
|
|
p.debugger.RemoveBreakpoint(bpNum)
|
|
log.Debugf("CMD_REMOVE_BREAKPOINT no: %d", bpNum)
|
|
return NewResponse(p.cmd, nil), nil
|
|
}
|
|
|
|
// handleCmdLoopback send back received data
|
|
func (p *DZRP) handleCmdLoopback() (*Response, error) {
|
|
return NewResponse(p.cmd, p.cmd.Payload), nil
|
|
}
|
|
|
|
func (p *DZRP) handleCmdReadPort() (*Response, error) {
|
|
if len(p.cmd.Payload) < 2 {
|
|
return nil, errors.New("too short payload")
|
|
}
|
|
addr := (uint16(p.cmd.Payload[1]) << 8) | uint16(p.cmd.Payload[0])
|
|
return NewResponse(p.cmd, []byte{p.computer.IORead(addr)}), nil
|
|
}
|
|
|
|
func (p *DZRP) handleCmdWritePort() (*Response, error) {
|
|
if len(p.cmd.Payload) < 3 {
|
|
return nil, errors.New("too short payload")
|
|
}
|
|
addr := (uint16(p.cmd.Payload[1]) << 8) | uint16(p.cmd.Payload[0])
|
|
p.computer.IOWrite(addr, p.cmd.Payload[2])
|
|
return NewResponse(p.cmd, nil), nil
|
|
}
|
|
|
|
func (p *DZRP) handleCmdInterruptOnOff() (*Response, error) {
|
|
if len(p.cmd.Payload) == 0 {
|
|
return nil, errors.New("too short payload")
|
|
}
|
|
on := p.cmd.Payload[0] != 0
|
|
p.debugger.SetBreakpointsEnabled(on)
|
|
log.Debugf("CMD_INTERRUPT_ONOFF on: %t", on)
|
|
return NewResponse(p.cmd, nil), nil
|
|
}
|