1-- license:BSD-3-Clause 2-- copyright-holders: Carl 3local exports = {} 4exports.name = "gdbstub" 5exports.version = "0.0.1" 6exports.description = "GDB stub plugin" 7exports.license = "The BSD 3-Clause License" 8exports.author = { name = "Carl" } 9 10local gdbstub = exports 11 12-- percpu mapping of mame registers to gdb register order 13local regmaps = { 14 i386 = { 15 togdb = { 16 EAX = 1, ECX = 2, EDX = 3, EBX = 4, ESP = 5, EBP = 6, ESI = 7, EDI = 8, EIP = 9, EFLAGS = 10, CS = 11, SS = 12, 17 DS = 13, ES = 14, FS = 15, GS = 16 }, 18 fromgdb = { 19 "EAX", "ECX", "EDX", "EBX", "ESP", "EBP", "ESI", "EDI", "EIP", "EFLAGS", "CS", "SS", "DS", "ES", "FS", "GS" }, 20 regsize = 4, 21 addrsize = 4, 22 pcreg = "EIP" 23 } 24} 25regmaps.i486 = regmaps.i386 26regmaps.pentium = regmaps.i386 27 28function gdbstub.startplugin() 29 local debugger 30 local debug 31 local cpu 32 local breaks 33 local watches 34 local consolelog 35 local consolelast 36 local running 37 38 emu.register_start(function () 39 debugger = manager:machine():debugger() 40 if not debugger then 41 print("gdbstub: debugger not enabled") 42 return 43 end 44 cpu = manager:machine().devices[":maincpu"] 45 if not cpu then 46 print("gdbstub: maincpu not found") 47 end 48 if not regmaps[cpu:shortname()] then 49 print("gdbstub: no register map for cpu " .. cpu:shortname()) 50 cpu = nil 51 end 52 consolelog = debugger.consolelog 53 consolelast = 0 54 breaks = {byaddr = {}, byidx = {}} 55 watches = {byaddr = {}, byidx = {}} 56 running = false 57 end) 58 59 emu.register_stop(function() 60 consolelog = nil 61 cpu = nil 62 debug = nil 63 end) 64 65 local socket = emu.file("", 7) 66 local connected = false 67 socket:open("socket.127.0.0.1:2159") 68 69 emu.register_periodic(function () 70 if not cpu then 71 return 72 end 73 74 if running and debugger.execution_state == "stop" then 75 socket:write("$S05#B8") 76 running = false 77 return 78 elseif debugger.execution_state == "run" then 79 running = true 80 end 81 82 local function chksum(str) 83 local sum = 0 84 str:gsub(".", function(s) sum = sum + s:byte() end) 85 return string.format("%.2x", sum & 0xff) 86 end 87 88 local function makebestr(val, len) 89 local str = "" 90 for count = 0, len - 1 do 91 str = str .. string.format("%.2x", (val >> (count * 8)) & 0xff) 92 end 93 return str 94 end 95 96 local last = consolelast 97 local msg = consolelog[#consolelog] 98 consolelast = #consolelog 99 if #consolelog > last and msg:find("Stopped at", 1, true) then 100 local point = tonumber(msg:match("Stopped at breakpoint ([0-9]+)")) 101 local map = regmaps[cpu:shortname()] 102 running = false 103 if not point then 104 point = tonumber(msg:match("Stopped at watchpoint ([0-9]+")) 105 if not point then 106 return -- ?? 107 end 108 local wp = watches.byidx[point] 109 if wp then 110 local reply = "T05" .. wp.type .. ":" .. makebestr(wp.addr, map.addrsize) 111 socket:write("$" .. reply .. "#" .. chksum(reply)) 112 else 113 socket:write("$S05#B8") 114 end 115 return 116 else 117 local bp = breaks.byidx[point] 118 if bp then 119 local reply = "T05hwbreak:" .. makebestr(cpu.state[map.pcreg].value, map.regsize) 120 socket:write("$" .. reply .. "#" .. chksum(reply)) 121 else 122 socket:write("$S05#B8") 123 end 124 return 125 end 126 end 127 128 if running and debugger.execution_state == "stop" then 129 socket:write("$S05#B8") 130 running = false 131 return 132 elseif debugger.execution_state == "run" then 133 running = true 134 end 135 136 local data = "" 137 138 repeat 139 local read = socket:read(100) 140 data = data .. read 141 until #read == 0 142 if #data == 0 then 143 return 144 end 145 if data == "\x03" then 146 debugger.execution_state = "stop" 147 socket:write("$S05#B8") 148 running = false 149 return 150 end 151 local packet, checksum = data:match("%$([^#]+)#(%x%x)") 152 if packet then 153 packet:gsub("}(.)", function(s) return string.char(string.byte(s) ~ 0x20) end) 154 local cmd = packet:sub(1, 1) 155 local map = regmaps[cpu:shortname()] 156 if cmd == "g" then 157 local regs = {} 158 for reg, idx in pairs(map.togdb) do 159 regs[idx] = makebestr(cpu.state[reg].value, map.regsize) 160 end 161 local data = table.concat(regs) 162 socket:write("+$" .. data .. "#" .. chksum(data)) 163 elseif cmd == "G" then 164 local count = 0 165 packet:sub(2):gsub(string.rep("%x", map.regsize * 2), function(s) 166 count = count + 1 167 cpu.state[map.fromgdb[count]].value = tonumber(s,16) 168 end) 169 socket:write("+$OK#9a") 170 elseif cmd == "m" then 171 local addr, len = packet:match("m(%x+),(%x+)") 172 if addr and len then 173 addr = tonumber(addr, 16) 174 len = tonumber(len, 16) 175 local data = "" 176 local space = cpu.spaces["program"] 177 for count = 1, len do 178 data = data .. string.format("%.2x", space:read_log_u8(addr)) 179 addr = addr + 1 180 end 181 socket:write("+$" .. data .. "#" .. chksum(data)) 182 else 183 socket:write("+$E00#a5") -- fix error 184 end 185 elseif cmd == "M" then 186 local count = 0 187 local addr, len, data = packet:match("M(%x+),(%x+),(%x+)") 188 if addr and len and data then 189 addr = tonumber(addr, 16) 190 local space = cpu.spaces["program"] 191 data:gsub("%x%x", function(s) space:write_log_u8(addr + count, tonumber(s, 16)) count = count + 1 end) 192 socket:write("+$OK#9a") 193 else 194 socket:write("+$E00#a5") 195 end 196 elseif cmd == "s" then 197 if #packet == 1 then 198 cpu:debug():step() 199 socket:write("+$OK#9a") 200 socket:write("$S05#B8") 201 running = false 202 else 203 socket:write("+$E00#a5") 204 end 205 elseif cmd == "c" then 206 if #packet == 1 then 207 cpu:debug():go() 208 socket:write("+$OK#9a") 209 else 210 socket:write("+$E00#a5") 211 end 212 elseif cmd == "Z" then 213 local btype, addr, kind = packet:match("Z([0-4]),(%x+),(.*)") 214 addr = tonumber(addr, 16) 215 if btype == "0" then 216 socket:write("") -- is machine dependant 217 elseif btype == "1" then 218 if breaks.byaddr[addr] then 219 socket:write("+$E00#a5") 220 return 221 end 222 local idx = cpu:debug():bpset(addr) 223 breaks.byaddr[addr] = idx 224 breaks.byidx[idx] = addr 225 socket:write("+$OK#9a") 226 elseif btype == "2" then 227 if watches.byaddr[addr] then 228 socket:write("+$E00#a5") 229 return 230 end 231 local idx = cpu:debug():wpset(cpu.spaces["program"], "w", addr, 1) 232 watches.byaddr[addr] = idx 233 watches.byidx[idx] = {addr = addr, type = "watch"} 234 socket:write("+$OK#9a") 235 elseif btype == "3" then 236 if watches.byaddr[addr] then 237 socket:write("+$E00#a5") 238 return 239 end 240 local idx = cpu:debug():wpset(cpu.spaces["program"], "r", addr, 1) 241 watches.byaddr[addr] = idx 242 watches.byidx[idx] = {addr = addr, type = "rwatch"} 243 socket:write("+$OK#9a") 244 elseif btype == "4" then 245 if watches.byaddr[addr] then 246 socket:write("+$E00#a5") 247 return 248 end 249 local idx = cpu:debug():wpset(cpu.spaces["program"], "rw", addr, 1) 250 watches.byaddr[addr] = idx 251 watches.byidx[idx] = {addr = addr, type = "awatch"} 252 socket:write("+$OK#9a") 253 end 254 elseif cmd == "z" then 255 local btype, addr, kind = packet:match("z([0-4]),(%x+),(.*)") 256 addr = tonumber(addr, 16) 257 if btype == "0" then 258 socket:write("") -- is machine dependent 259 elseif btype == "1" then 260 if not breaks.byaddr[addr] then 261 socket:write("+$E00#a5") 262 return 263 end 264 local idx = breaks.byaddr[addr] 265 cpu:debug():bpclr(idx) 266 breaks.byaddr[addr] = nil 267 breaks.byidx[idx] = nil 268 socket:write("+$OK#9a") 269 elseif btype == "2" or btype == "3" or btype == "4" then 270 if not watches.byaddr[addr] then 271 socket:write("+$E00#a5") 272 return 273 end 274 local idx = watches.byaddr[addr] 275 cpu:debug():wpclr(idx) 276 watches.byaddr[addr] = nil 277 watches.byidx[idx] = nil 278 socket:write("+$OK#9a") 279 end 280 elseif cmd == "?" then 281 socket:write("+$S05#B8") 282 else 283 socket:write("+$#00") 284 end 285 end 286 end) 287end 288 289return exports 290