luaforwindows/SciTE/scite-debug/scite_lua/debug/luagdb.lua

245 lines
8.1 KiB
Lua
Executable File

-- GDB-style interface to clidebug
scite_require 'gdb.lua'
local find = string.find
local match = string.match
local push = table.insert
local pop = table.remove
local esc = string.char(26)
local gprefix = esc..esc
--local ferr = io.stderr
local GTK = scite_GetProp('PLAT_GTK')
-- the convention with cli debug is that all Windows filenames are lowercase!
local function canonical(file)
if not GTK then file = file:lower() end
return file
end
local function fpath(file)
return canonical(fullpath(file))
end
function slashify(s)
return s:gsub('\\','\\\\')
end
local function file_is_lua (file)
return extension_of(file) == 'lua'
end
local info_line_success = '^Line %d+ of'
local info_line_error = '^No line number information available'
function info_line_handler(line,dbg)
local success = line:find(info_line_success)
if success then -- we can set a break command
--dbg:queue_command('tbreak *'..dbg.addr)
spawner_command('tbreak *'..dbg.addr)
dbg.addresses[dbg.addr] = true
end
-- either way, we want to get out of GDB mode at the earliest opportunity!
--dbg:queue_command 'continue'
spawner_command('continue')
end
LGdb = class(Gdb)
function LGdb.discriminator(target)
local res = find(target,'^:gdb') == 1
return res
end
function LGdb:init (root)
Gdb.init(self,root)
self.root = root
self.target_dir = canonical(props['FileDir'])
self.no_target_ext = false
-- this is added to the package.path of the Lua program
print(extman_Path())
self.clidebug_path = scite_GetProp('clidebug.path',join(extman_Path(),'lua_clidebugger'))
self.clidebug_debugger = canonical(join(self.clidebug_path,'debugger.lua'))
-- self.no_quit_confirm = true
self.skip_system_extension = ".lua"
self.deferred_stack = {}
self.postprocess_command['info line'] = {pattern=info_line_success,
action=info_line_handler, alt_pat=info_line_error}
self.addresses = {}
self.mode = 'gdb'
-- GDB likes forward slashes, on both platforms...
local dbgl_file = join(self.clidebug_path,"dbgl.c"):gsub('\\','/')
-- this is a persistent event handler which monitors every program break,
-- and keeps track of whether we are in GDB or clidebug. Will raise
-- the events 'gdb' and 'lua' accordingly.
self:set_event('break',function(file,line)
local new_mode
local lf = file_is_lua(file)
-- don't respond to any breaks in dbgl.c
if not lf and file == dbgl_file then
return true,true
end
if self.mode ~= 'lua' and lf then
new_mode = 'lua'
end
if self.mode == 'lua' and not lf then
new_mode = 'gdb'
end
if new_mode then
self.mode = new_mode
self:raise_event(new_mode)
end
return true
end)
end
function LGdb:check_breakpoint (b)
return not file_is_lua(b.file)
end
function LGdb:parameter_string ()
local parms = Gdb.parameter_string(self)
-- we have to modify the package path and cpath for this process so that
-- the clidebug and dbgl packages are visible.
local so = choose(GTK,'?.so;','?.dll;')
local ppath = "'"..slashify(join(self.clidebug_path,'?.lua;')).."'"
local cpath = "'"..slashify(join(self.clidebug_path,so)).."'"
local p = 'package'
local cmdline
-- if the target isn't Lua, then we assume it's a program that hosts Lua and that
-- there's an explicit clidebug initialization somewhere in a user Lua script.
if self.not_lua then
cmdline = ''
else
cmdline = ('-e "%s.path=%s..%s.path; %s.cpath=%s..%s.cpath; GDB=true; WIN=%s" -lclidebug %s'):format(
p,ppath,p,p,cpath,p,choose(GTK,'false','true'),self.lua_target)
end
print('*',cmdline)
return cmdline..' '..parms
end
function LGdb:command_line(target)
local gtarget,ltarget = target:match('^:gdb;([^;]+);(.*)')
self.lua_target = ltarget
print('+',gtarget,ltarget)
local idx = gtarget:find('%[h%]$')
if idx then
gtarget = gtarget:sub(1,idx-1)
self.not_lua = true
end
--- we are going to embed a clidebug session inside a GDB session, so it's
--- necessary to explicitly create the clidebug.cmd file. This folows the
--- sequence in create_existing_breakpoints() in debugger.lua
local lua_cmd = join(self.root,'clidebug.cmd')
local out = io.open(lua_cmd,'w')
for b in Breakpoints() do
if file_is_lua(b.file) then
out:write('break '..canonical(b.file)..':'..b.line..'\n')
end
end
out:write('rootpath '..self.target_dir..'\n')
if not self.not_lua then
out:write('run\n')
end
out:close()
return Gdb.command_line(self,gtarget)
end
-- need to put at least one breakpoint into the system, so that we can drop into
-- gdb mode when necessary. Under Windows, you definitely do not want a separate
-- console window, since we want to capture the result of running clidebug inside GDB.
function LGdb:special_debugger_setup(out)
Gdb.special_debugger_setup(self,out)
if not GTK then
out:write('set new-console off\n')
end
-- a useful command when in C Lua code.
out:write[[
define lstack
p debug_lua_stack($arg0)
end
]]
out:write('directory ',self.clidebug_path,'\n')
-- clidebug will use this break to get us into gdb
out:write('break dbgl.c:9\n')
end
function LGdb:set_breakpoint(file,lno)
local lf = file_is_lua(file)
-- clidebug works best with absolute paths
if lf then file = fullpath(file) end
-- if we are in the wrong mode, then the actual setting of a breakpoint
-- needs to happen when we next switch to the correct mode.
if (self.mode == 'lua') ~= lf then
local function set_break ()
Gdb.set_breakpoint(self,file,lno)
end
if self.mode == 'lua' then
spawner_command('debugbreak')
self:set_event('break',set_break)
self:queue_command 'continue'
else
self:set_event('lua',set_break)
end
else
Gdb.set_breakpoint(self,file,lno)
end
end
function LGdb:goto_file_line(file,line)
ProcessOutput(gprefix..self.target_dir..'/'..file..":"..line..'\n')
end
-- there is some clidebugger hackery going on here. It will put us into its own version of debug.stacktrace,
-- and we need to put the program into frame #3, which is where the wobby originally happened. The usual Lua
-- error message is put out by the 'Message: ' line, which we use to capture the file:line needed to jump to.
-- The jumping is achieved by pushing the correct break pattern back into the input above (there must be
-- a more elegant way of doing this!)
local fmsg,lmsg
function LGdb:find_execution_break(line)
local _,_,file,lineno = find(line,self.break_line)
if _ then
-- has our program thrown a wobbly in Lua?
if file == self.clidebug_debugger and fmsg then
self:frame(3)
return fmsg,lmsg,true
else
return file,lineno
end
else
fmsg,lmsg = match(line,'Message: (%S+):(%d+)')
if fmsg then return end
-- clidebug emits this pattern when Lua is entering a C function
-- we have to check whether this function has any debug symbols
-- before trying to step into it.
local addr = match(line,'//@//%s(.+)')
if addr then
self.addr = strip_eol(addr)
-- at this point, we have entered GDB at debug_break (forced by clidebug)
local cached = self.addresses[self.addr]
if cached == nil then
-- haven't met this function before; check for line info.
self:set_event('break',function()
dbg_command('info line','*'..self.addr)
-- info_line_handler() above will process the output...
end)
elseif cached == true then
--we know this function has line numbers defined
self:set_event('break',function()
self:queue_command('tbreak *'..self.addr)
self:queue_command('continue')
end)
end
return
end
end
end
register_debugger('luagdb','lua',LGdb)