-- 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)