Ocean-240.2-Emulator/debug/dzrp/dzrp.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
}