debugger - error handling, ui rework

This commit is contained in:
kepler155c@gmail.com
2020-05-25 21:49:17 -06:00
parent 26b693d609
commit 70001196cb
3 changed files with 319 additions and 282 deletions

View File

@@ -1,35 +1,36 @@
--[[
some portions from https://github.com/slembcke/debugger.lua
]]
-- this code is loaded into the code being debugged
-- some portions from https://github.com/slembcke/debugger.lua
local fs = _G.fs
local dbg = { }
local function hookBreakpoint(info)
local function breakpointHook(info)
if dbg.breakpoints then
for _,v in pairs(dbg.breakpoints) do
if v.line == info.currentline and v.file == info.short_src and not v.disabled then
return true
if v.line == info.currentline and v.file == info.short_src then
print(v.line, not v.disabled)
return not v.disabled
end
end
end
end
local function hookFunction(fn)
local function functionHook(fn)
return function(info)
return info.func == fn
end
end
local function hookStep()
local function stepHook()
local co = coroutine.running()
return function(info)
return co == coroutine.running() or hookBreakpoint(info)
return co == coroutine.running()
or breakpointHook(info)
end
end
local function hookStepStacksize(n)
local function stackSizeHook(n)
local co = coroutine.running()
local i = 2
while true do
@@ -40,27 +41,23 @@ local function hookStepStacksize(n)
i = i + 1
end
return function(info)
if co == coroutine.running() then
if not debug.getinfo(i - n) then
return true
end
end
return hookBreakpoint(info)
return co == coroutine.running()
and not debug.getinfo(i - n)
or breakpointHook(info)
end
end
local function hookStepOut()
return hookStepStacksize(1)
local function stepOutHook()
return stackSizeHook(1)
end
local function hookStepOver()
return hookStepStacksize(0)
local function stepOverHook()
return stackSizeHook(0)
end
local hookEval = function() end
-- Create a table of all the locally accessible variables.
-- Globals are not included when running the locals command
local function local_bindings(offset, stack_inspect_offset)
offset = offset + 1 + stack_inspect_offset -- add this function to the offset
local func = debug.getinfo(offset).func
@@ -96,7 +93,7 @@ local function local_bindings(offset, stack_inspect_offset)
local t = { }
for k,v in pairs(bindings) do
if v.raw ~= nil then
if k ~= '(*temporary)' then
v.name = k
v.value = tostring(v.raw)
table.insert(t, v)
@@ -138,45 +135,44 @@ local inHook = false
local function hook()
local info = debug.getinfo(2)
if info.currentline < 0 then
return
end
if not inHook and hookEval(info) then
inHook = true
local offset = 2 -- the offset from this function to the code being debugged
local inspectOffset = 0
repeat
local done = true
local snapshot = {
info = debug.getinfo(offset + inspectOffset),
locals = local_bindings(offset, inspectOffset),
stack = get_trace(offset, inspectOffset),
}
local snapshot = {
info = debug.getinfo(2 + inspectOffset),
locals = local_bindings(2, inspectOffset),
stack = get_trace(2, inspectOffset),
}
inspectOffset = 0 -- reset
local cmd, param = dbg.read(snapshot)
os.queueEvent('debuggerX', dbg.debugger.uid, snapshot)
local e, cmd, param
repeat
e, cmd, param = os.pullEvent('debugger')
until e == 'debugger'
if cmd == 's' then
hookEval = hookStep()
hookEval = stepHook()
elseif cmd == 'n' then
hookEval = hookStepOver()
hookEval = stepOverHook()
elseif cmd == 'f' then
hookEval = hookStepOut()
hookEval = stepOutHook()
elseif cmd == 'c' then
hookEval = hookBreakpoint
elseif cmd == 'd' then -- detach
debug.sethook()
elseif cmd == 'q' then
os.exit(0)
elseif cmd == 'b' then
dbg.breakpoints = param
done = false
hookEval = breakpointHook
elseif cmd == 'i' then
-- inspect stack at this offset
-- get snapshot of stack at this offset
inspectOffset = param
done = false
else
os.sleep(1)
done = false
end
until done
@@ -184,41 +180,51 @@ local function hook()
end
end
local cocreate = coroutine.create
_ENV.coroutine = { }
for k,v in pairs(_G.coroutine) do
_ENV.coroutine[k] = v
function dbg.call(f, ...)
local args = { ... }
return xpcall(
function()
f(table.unpack(args))
end,
function(err)
hookEval = stepHook()
-- An error has occurred
return err
end)
end
_ENV.coroutine.create = function(f, ...)
local co = cocreate(f, ...)
debug.sethook(co, dbg.hook, "l")
_ENV.coroutine = setmetatable({
create = function(f)
local co = _G.coroutine.create(function(...)
local r = { dbg.call(f, ...) }
if not r[1] then
error(r[2], -1)
end
return table.unpack(r, 2)
end)
debug.sethook(co, hook, 'l')
return co
end
--[[
create = function(f)
local co = _G.coroutine.create(f)
debug.sethook(co, hook, 'l')
return co
end
]]
}, { __index = coroutine })
debug.sethook(hook, 'l')
-- Expose the debugger's functions
dbg.hook = hook
dbg.exit = function(err) os.exit(err) end
dbg.stopIn = function(fn)
hookEval = hookFunction(fn)
hookEval = functionHook(fn)
end
dbg.debugger = nil
dbg.read = function(info)
_G._pinfo = info
os.sleep(0) -- this is important ...
dbg.debugger:resume('debugger', 'info', info)
while true do
local _, cmd, args = os.pullEvent('debugger')
if cmd == 'b' then
dbg.breakpoints = args
else
return cmd, args
end
end
end
return dbg

View File

@@ -32,12 +32,13 @@ local client
local function startClient()
local env = kernel.makeEnv(_ENV)
currentFile = nil
local clientId = multishell.openTab(nil, {
env = env,
title = fs.getName(filename):match('([^%.]+)'),
args = args,
fn = function(...)
fn = function()
local dbg = require('debugger')
local fn, msg = loadfile(filename, env)
@@ -45,10 +46,14 @@ local function startClient()
error(msg, -1)
end
dbg.debugger = debugger
-- breakpoint table is shared across processes
dbg.breakpoints = breakpoints
dbg.debugger = debugger
dbg.stopIn(fn)
fn(...)
local s, m = dbg.call(fn, table.unpack(args))
if not s then
error(m, -1)
end
end,
})
client = kernel.find(clientId)
@@ -79,7 +84,7 @@ romFiles:load()
local function loadSource(file)
currentFile = romFiles:get(file) or file:match('@?(.*)')
local src = { }
local lines = Util.readLines(currentFile)
local lines = Util.readLines(currentFile) or type(file) == 'string' and Util.split(file)
if lines then
for i = 1, #lines do
@@ -95,21 +100,20 @@ local function message(...)
end
local page = UI.Page {
menuBar = UI.MenuBar {
buttons = {
{ text = 'Continue', event = 'cmd', cmd = 'c' },
{ text = 'Step', event = 'cmd', cmd = 's' },
{ text = 'Over', event = 'cmd', cmd = 'n' },
{ text = 'Out', event = 'cmd', cmd = 'f' },
{ text = 'Restart', event = 'restart', width = 9, ex = -1 },
},
},
backgroundColor = 'black',
container = UI.Window {
y = 2, ey = '50%',
locals = UI.ScrollingGrid {
y = 1, ey = '50%',
tabs = UI.Tabs {
ey = -2,
unselectedBackgroundColor = 'black',
locals = UI.Tab {
title = 'Locals',
index = 1,
grid = UI.ScrollingGrid {
disableHeader = true,
unfocusedBackgroundSelectedColor = 'black',
columns = {
{ heading = 'Key', key = 'name' },
{ heading = 'Value', key = 'value', textColor = 'yellow' },
@@ -124,71 +128,6 @@ local page = UI.Page {
or UI.Grid.getRowTextColor(self, row, selected)
end,
},
statusBar = UI.StatusBar {
ex = -7, y = -1,
backgroundColor = 'primary',
textColor = 'white',
},
UI.Button {
y = -1, x = -6,
event = 'open',
text = 'Open',
}
},
tabs = UI.Tabs {
y = '50%',
source = UI.Tab {
title = 'Source',
index = 1,
grid = UI.ScrollingGrid {
disableHeader = true,
columns = {
{ key = 'marker', width = 1 },
{ key = 'line', textColor = 'cyan', width = 4 },
{ heading = 'heading', key = 'source' },
},
getDisplayValues = function(_, row)
for _,v in pairs(breakpoints) do
if v.file == currentFile and v.line == row.line then
return {
marker = v.disabled and 'x' or '!',
line = row.line,
source = row.source,
}
end
end
return row
end,
accelerators = {
t = 'toggle_enabled'
},
getRowTextColor = function(self, row, selected)
return row.line == debugLine and currentFile == debugFile and 'yellow'
or UI.Grid.getRowTextColor(self, row, selected)
end,
eventHandler = function(self, event)
if event.type == 'grid_select' then
self:emit({
type = 'toggle_breakpoint',
file = currentFile,
line = event.selected.line,
})
elseif event.type == 'toggle_enabled' then
local line = self:getSelected() and self:getSelected().line
if line then
for _,v in pairs(breakpoints) do
if v.file == currentFile and v.line == line then
v.disabled = not v.disabled
self:emit({ type = 'update_breakpoints' })
break
end
end
end
end
return UI.Grid.eventHandler(self, event)
end,
},
},
stack = UI.Tab {
@@ -196,6 +135,8 @@ local page = UI.Page {
index = 3,
grid = UI.ScrollingGrid {
disableHeader = true,
sortColumn = 'index',
unfocusedBackgroundSelectedColor = 'black',
columns = {
{ key = 'index', width = 2 },
{ heading = 'heading', key = 'desc' },
@@ -204,7 +145,6 @@ local page = UI.Page {
return row.current and 'yellow'
or UI.Grid.getRowTextColor(self, row, selected)
end,
sortColumn = 'index',
eventHandler = function(self, event)
if event.type == 'grid_select' then
message('i', event.selected.index)
@@ -219,11 +159,13 @@ local page = UI.Page {
title = 'Env',
index = 4,
grid = UI.ScrollingGrid {
disableHeader = true,
autospace = true,
unfocusedBackgroundSelectedColor = 'black',
columns = {
{ heading = 'Key', key = 'name' },
{ heading = 'Value', key = 'value', textColor = 'yellow' },
},
autospace = true,
accelerators = {
grid_select = 'show_variable',
},
@@ -243,13 +185,13 @@ local page = UI.Page {
},
grid = UI.ScrollingGrid {
y = 2,
values = breakpoints,
autospace = true,
columns = {
{ heading = 'Line', key = 'line', width = 5 },
{ heading = 'Name', key = 'short' },
{ heading = 'Path', key = 'path', textColor = 'lightGray' },
},
values = breakpoints,
autospace = true,
getRowTextColor = function(self, row, selected)
return row.disabled and 'lightGray'
or UI.Grid.getRowTextColor(self, row, selected)
@@ -287,7 +229,81 @@ local page = UI.Page {
},
},
menuBar = UI.MenuBar {
y = -1,
buttons = {
{ text = 'Continue', event = 'cmd', cmd = 'c' },
{ text = 'Step', event = 'cmd', cmd = 's' },
{ text = 'Over', event = 'cmd', cmd = 'n' },
{ text = 'Out', event = 'cmd', cmd = 'f' },
{ text = 'Restart', event = 'restart', width = 9, ex = -1 },
},
},
},
source = UI.ScrollingGrid {
y = '50%', ey = -2,
disableHeader = true,
columns = {
{ key = 'marker', width = 1 },
{ key = 'line', textColor = 'cyan', width = 4 },
{ heading = 'heading', key = 'source' },
},
accelerators = {
t = 'toggle_enabled'
},
getDisplayValues = function(_, row)
for _,v in pairs(breakpoints) do
if v.file == currentFile and v.line == row.line then
return {
marker = v.disabled and 'x' or '!',
line = row.line,
source = row.source,
}
end
end
return row
end,
getRowTextColor = function(self, row, selected)
return row.line == debugLine and currentFile == debugFile and 'yellow'
or UI.Grid.getRowTextColor(self, row, selected)
end,
eventHandler = function(self, event)
if event.type == 'grid_select' then
self:emit({
type = 'toggle_breakpoint',
file = currentFile,
line = event.selected.line,
})
elseif event.type == 'toggle_enabled' then
local line = self:getSelected() and self:getSelected().line
if line then
for _,v in pairs(breakpoints) do
if v.file == currentFile and v.line == line then
v.disabled = not v.disabled
self:emit({ type = 'update_breakpoints' })
break
end
end
end
end
return UI.Grid.eventHandler(self, event)
end,
},
statusBar = UI.StatusBar {
ex = -7, y = -1,
backgroundColor = 'black',
textColor = 'orange',
},
UI.FlatButton {
y = -1, x = -5,
textColor = 'orange',
event = 'open',
text = 'Open',
},
quick_open = UI.QuickSelect {
y = '50%',
modal = true,
enable = function() end,
show = function(self)
@@ -310,26 +326,25 @@ local page = UI.Page {
openFile = function(self, file, line)
if file ~= currentFile then
local src = loadSource(file)
self.tabs.source.grid:setValues(src)
self.source:setValues(src)
end
if line then
self.tabs.source.grid:setIndex(#self.tabs.source.grid.values)
self.tabs.source.grid:setIndex(math.max(1, line - 4))
self.source:setIndex(#self.source.values)
self.source:setIndex(math.max(1, line - 4))
end
self.tabs.source.grid:setIndex(line or 1)
self.tabs:selectTab(self.tabs.source)
self.source:setIndex(line or 1)
if currentFile == debugFile then
self.container.statusBar:setStatus(
self.statusBar:setStatus(
string.format('%s : %d', fs.getName(file), debugLine))
else
self.container.statusBar:setStatus(fs.getName(file))
self.statusBar:setStatus(fs.getName(file))
end
self:draw()
end,
eventHandler = function(self, event)
if event.type == 'cmd' then
self.container.statusBar:setStatus('Running...')
self.statusBar:setStatus('Running...')
message(event.element.cmd)
elseif event.type == 'restart' then
@@ -345,10 +360,9 @@ local page = UI.Page {
self:openFile(event.file, event.line)
elseif event.type == 'update_breakpoints' then
self.tabs.breaks.grid:update()
self.tabs.breaks.grid:draw()
self.tabs.source.grid:draw()
message('b', breakpoints)
self.container.tabs.breaks.grid:update()
self.container.tabs.breaks.grid:draw()
self.source:draw()
Config.update('debugger', config)
elseif event.type == 'toggle_breakpoint' then
@@ -405,26 +419,22 @@ local page = UI.Page {
end,
}
Event.on('debugger', function(_, cmd, data)
if cmd == 'info' then
Event.on('debuggerX', function(_, uid, data)
if uid == debugger.uid then
kernel.raise(debugger.uid)
-- local tab
local t = data.locals
-- for k,v in pairs(data.locals or { }) do
-- table.insert(t, { name = k, value = tostring(v), raw = v })
-- end
table.sort(t, function(a, b) return a.name < b.name end)
page.container.locals:setValues(t)
page.container.locals.orig = Util.shallowCopy(t)
table.sort(data.locals, function(a, b) return a.name < b.name end)
page.container.tabs.locals.grid:setValues(data.locals)
page.container.tabs.locals.grid.orig = Util.shallowCopy(data.locals)
-- env tab
t = { }
local t = { }
for k,v in pairs(getfenv(data.info.func)) do
table.insert(t, { name = k, value = tostring(v), raw = v })
end
page.tabs.env.grid:setValues(t)
page.tabs.env.grid.orig = Util.shallowCopy(t)
page.container.tabs.env.grid:setValues(t)
page.container.tabs.env.grid.orig = Util.shallowCopy(t)
debugLine = data.info.currentline
debugFile = data.info.source:match('@?(.*)')
@@ -433,7 +443,7 @@ Event.on('debugger', function(_, cmd, data)
page:openFile(debugFile, debugLine)
-- stack
page.tabs.stack.grid:setValues(data.stack)
page.container.tabs.stack.grid:setValues(data.stack)
page:draw()
page:sync()

View File

@@ -4,16 +4,37 @@ end
local function method(times)
local a = 2
-- use step out to return out of method
for _ = 1, times do
a = a * a
end
return m2(a)
end
print('before')
term.current().clear()
print('after')
local chunk = load([[
local j = 5
for i = 1, 5 do
j = j * i
end
--table.insert(j, 5)
return j]], nil, nil, _ENV)
local j = chunk()
print(j)
require('opus.util').print(coroutine)
local co = coroutine.create(function(args)
print('in coroutine')
return 'hi'
end)
local _, t = coroutine.resume(co, 'test')
while coroutine.status(co) ~= 'dead' do
coroutine.resume(co, os.pullEvent())
--print('alive')
end
print(coroutine.status(co))
print(t)
local i = 2
print(i)
@@ -24,4 +45,4 @@ dofile("rom/modules/main/cc/expect.lua")
print(res)
print('result: ' .. res)
error('f')
table.insert(res, 5)