mame/plugins/gdbstub/init.lua
Vas Crabb b67b969bf0 -Improved some Lua APIs:
* Moved several machine lifecycle callbacks to the notifier/subscriber
  model.  The old callback registration model is still available for
  them for now, but prints a deprecation warning.
* Added pre-save/post-load notifications.
* Use a single allocated timer rather than one anonymous timer per
  waiter.  Waiters no longer prevent saved states from being loaded.
* Clean up outstanding waiters on stop or state load rather than just
  leaking them.
* Started documenting parts of the emulator interface object that should
  be relatively stable.

-imagedev/avivideo.cpp: Fixed an object leak on unload.  Also changed
 some other media image devices to use smart pointers.
2023-04-07 06:20:40 +10:00

291 lines
7.7 KiB
Lua

-- license:BSD-3-Clause
-- copyright-holders: Carl
local exports = {
name = "gdbstub",
version = "0.0.1",
description = "GDB stub plugin",
license = "BSD-3-Clause",
author = { name = "Carl" } }
local gdbstub = exports
-- percpu mapping of mame registers to gdb register order
local regmaps = {
i386 = {
togdb = {
EAX = 1, ECX = 2, EDX = 3, EBX = 4, ESP = 5, EBP = 6, ESI = 7, EDI = 8, EIP = 9, EFLAGS = 10, CS = 11, SS = 12,
DS = 13, ES = 14, FS = 15, GS = 16 },
fromgdb = {
"EAX", "ECX", "EDX", "EBX", "ESP", "EBP", "ESI", "EDI", "EIP", "EFLAGS", "CS", "SS", "DS", "ES", "FS", "GS" },
regsize = 4,
addrsize = 4,
pcreg = "EIP"
}
}
regmaps.i486 = regmaps.i386
regmaps.pentium = regmaps.i386
local reset_subscription, stop_subscription
function gdbstub.startplugin()
local debugger
local debug
local cpu
local breaks
local watches
local consolelog
local consolelast
local running
reset_subscription = emu.add_machine_reset_notifier(function ()
debugger = manager.machine.debugger
if not debugger then
print("gdbstub: debugger not enabled")
return
end
cpu = manager.machine.devices[":maincpu"]
if not cpu then
print("gdbstub: maincpu not found")
end
if not regmaps[cpu.shortname] then
print("gdbstub: no register map for cpu " .. cpu.shortname)
cpu = nil
end
consolelog = debugger.consolelog
consolelast = 0
breaks = {byaddr = {}, byidx = {}}
watches = {byaddr = {}, byidx = {}}
running = false
end)
stop_subscription = emu.add_machine_stop_notifier(function ()
consolelog = nil
cpu = nil
debug = nil
end)
local socket = emu.file("", 7)
local connected = false
socket:open("socket.127.0.0.1:2159")
emu.register_periodic(function ()
if not cpu then
return
end
if running and debugger.execution_state == "stop" then
socket:write("$S05#B8")
running = false
return
elseif debugger.execution_state == "run" then
running = true
end
local function chksum(str)
local sum = 0
str:gsub(".", function(s) sum = sum + s:byte() end)
return string.format("%.2x", sum & 0xff)
end
local function makebestr(val, len)
local str = ""
for count = 0, len - 1 do
str = str .. string.format("%.2x", (val >> (count * 8)) & 0xff)
end
return str
end
local last = consolelast
local msg = consolelog[#consolelog]
consolelast = #consolelog
if #consolelog > last and msg:find("Stopped at", 1, true) then
local point = tonumber(msg:match("Stopped at breakpoint ([0-9]+)"))
local map = regmaps[cpu.shortname]
running = false
if not point then
point = tonumber(msg:match("Stopped at watchpoint ([0-9]+"))
if not point then
return -- ??
end
local wp = watches.byidx[point]
if wp then
local reply = "T05" .. wp.type .. ":" .. makebestr(wp.addr, map.addrsize)
socket:write("$" .. reply .. "#" .. chksum(reply))
else
socket:write("$S05#B8")
end
return
else
local bp = breaks.byidx[point]
if bp then
local reply = "T05hwbreak:" .. makebestr(cpu.state[map.pcreg].value, map.regsize)
socket:write("$" .. reply .. "#" .. chksum(reply))
else
socket:write("$S05#B8")
end
return
end
end
if running and debugger.execution_state == "stop" then
socket:write("$S05#B8")
running = false
return
elseif debugger.execution_state == "run" then
running = true
end
local data = ""
repeat
local read = socket:read(100)
data = data .. read
until #read == 0
if #data == 0 then
return
end
if data == "\x03" then
debugger.execution_state = "stop"
socket:write("$S05#B8")
running = false
return
end
local packet, checksum = data:match("%$([^#]+)#(%x%x)")
if packet then
packet:gsub("}(.)", function(s) return string.char(string.byte(s) ~ 0x20) end)
local cmd = packet:sub(1, 1)
local map = regmaps[cpu.shortname]
if cmd == "g" then
local regs = {}
for reg, idx in pairs(map.togdb) do
regs[idx] = makebestr(cpu.state[reg].value, map.regsize)
end
local data = table.concat(regs)
socket:write("+$" .. data .. "#" .. chksum(data))
elseif cmd == "G" then
local count = 0
packet:sub(2):gsub(string.rep("%x", map.regsize * 2), function(s)
count = count + 1
cpu.state[map.fromgdb[count]].value = tonumber(s,16)
end)
socket:write("+$OK#9a")
elseif cmd == "m" then
local addr, len = packet:match("m(%x+),(%x+)")
if addr and len then
addr = tonumber(addr, 16)
len = tonumber(len, 16)
local data = ""
local space = cpu.spaces["program"]
for count = 1, len do
data = data .. string.format("%.2x", space:readv_u8(addr))
addr = addr + 1
end
socket:write("+$" .. data .. "#" .. chksum(data))
else
socket:write("+$E00#a5") -- fix error
end
elseif cmd == "M" then
local count = 0
local addr, len, data = packet:match("M(%x+),(%x+),(%x+)")
if addr and len and data then
addr = tonumber(addr, 16)
local space = cpu.spaces["program"]
data:gsub("%x%x", function(s) space:writev_u8(addr + count, tonumber(s, 16)) count = count + 1 end)
socket:write("+$OK#9a")
else
socket:write("+$E00#a5")
end
elseif cmd == "s" then
if #packet == 1 then
cpu.debug:step()
socket:write("+$OK#9a")
socket:write("$S05#B8")
running = false
else
socket:write("+$E00#a5")
end
elseif cmd == "c" then
if #packet == 1 then
cpu.debug:go()
socket:write("+$OK#9a")
else
socket:write("+$E00#a5")
end
elseif cmd == "Z" then
local btype, addr, kind = packet:match("Z([0-4]),(%x+),(.*)")
addr = tonumber(addr, 16)
if btype == "0" then
socket:write("") -- is machine dependant
elseif btype == "1" then
if breaks.byaddr[addr] then
socket:write("+$E00#a5")
return
end
local idx = cpu.debug:bpset(addr)
breaks.byaddr[addr] = idx
breaks.byidx[idx] = addr
socket:write("+$OK#9a")
elseif btype == "2" then
if watches.byaddr[addr] then
socket:write("+$E00#a5")
return
end
local idx = cpu.debug:wpset(cpu.spaces["program"], "w", addr, 1)
watches.byaddr[addr] = idx
watches.byidx[idx] = {addr = addr, type = "watch"}
socket:write("+$OK#9a")
elseif btype == "3" then
if watches.byaddr[addr] then
socket:write("+$E00#a5")
return
end
local idx = cpu.debug:wpset(cpu.spaces["program"], "r", addr, 1)
watches.byaddr[addr] = idx
watches.byidx[idx] = {addr = addr, type = "rwatch"}
socket:write("+$OK#9a")
elseif btype == "4" then
if watches.byaddr[addr] then
socket:write("+$E00#a5")
return
end
local idx = cpu.debug:wpset(cpu.spaces["program"], "rw", addr, 1)
watches.byaddr[addr] = idx
watches.byidx[idx] = {addr = addr, type = "awatch"}
socket:write("+$OK#9a")
end
elseif cmd == "z" then
local btype, addr, kind = packet:match("z([0-4]),(%x+),(.*)")
addr = tonumber(addr, 16)
if btype == "0" then
socket:write("") -- is machine dependent
elseif btype == "1" then
if not breaks.byaddr[addr] then
socket:write("+$E00#a5")
return
end
local idx = breaks.byaddr[addr]
cpu.debug:bpclr(idx)
breaks.byaddr[addr] = nil
breaks.byidx[idx] = nil
socket:write("+$OK#9a")
elseif btype == "2" or btype == "3" or btype == "4" then
if not watches.byaddr[addr] then
socket:write("+$E00#a5")
return
end
local idx = watches.byaddr[addr]
cpu.debug:wpclr(idx)
watches.byaddr[addr] = nil
watches.byidx[idx] = nil
socket:write("+$OK#9a")
end
elseif cmd == "?" then
socket:write("+$S05#B8")
else
socket:write("+$#00")
end
end
end)
end
return exports