summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndreas Baumann <mail@andreasbaumann.cc>2014-10-17 11:11:27 +0200
committerAndreas Baumann <mail@andreasbaumann.cc>2014-10-17 11:11:27 +0200
commit20a6ded7e251b61c347aa08d3624afd192dacbff (patch)
tree916b209db4eb3cf65ce3f182e7ae987cd036382d
parent46428f37d62672a3d17663d1d704a6ebc3f21073 (diff)
downloadcrawler-20a6ded7e251b61c347aa08d3624afd192dacbff.tar.gz
crawler-20a6ded7e251b61c347aa08d3624afd192dacbff.tar.bz2
playing around with Lua debugger
-rwxr-xr-xREADME.3rdPARTY8
-rwxr-xr-xTODOS2
-rw-r--r--src/crawl/crawl.conf3
-rwxr-xr-xsrc/crawl/crawl.cpp6
-rw-r--r--src/crawl/debugger.lua407
5 files changed, 422 insertions, 4 deletions
diff --git a/README.3rdPARTY b/README.3rdPARTY
index 215b9da..acef764 100755
--- a/README.3rdPARTY
+++ b/README.3rdPARTY
@@ -429,3 +429,11 @@ You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
+
+debugger.lua
+------------
+
+https://github.com/slembcke/debugger.lua
+
+A simple, embedabble CLI debugger for Lua 5.1, Lua 5.2 and LuaJIT 2.0. Licensed under the very permissable MIT license.
+
diff --git a/TODOS b/TODOS
index 1065211..7a1b304 100755
--- a/TODOS
+++ b/TODOS
@@ -77,4 +77,4 @@
URLs to other processes.
- random distribute
- keep local links of host local
-- log.trace
+- debugging embedded Lua
diff --git a/src/crawl/crawl.conf b/src/crawl/crawl.conf
index 46599d6..0a9947c 100644
--- a/src/crawl/crawl.conf
+++ b/src/crawl/crawl.conf
@@ -1,3 +1,5 @@
+local dbg = require( "crawl/debugger" )
+
-- global settings
crawler = {
@@ -96,6 +98,7 @@ function destroy( )
end
function crawl( )
+ --dbg( )
log.notice( "Crawling.." )
local baseUrl = base:parseUrl( "http://www.base.com" )
log.debug( "base URL is: " .. baseUrl:str( ) )
diff --git a/src/crawl/crawl.cpp b/src/crawl/crawl.cpp
index 24d6427..bb3918e 100755
--- a/src/crawl/crawl.cpp
+++ b/src/crawl/crawl.cpp
@@ -156,7 +156,7 @@ int main( int /* argc */, char *argv[] )
initialize_libcrawler( (void *)&luaVm );
- //Logger::instance( ).openConsoleLog( logDEBUG );
+ Logger::instance( ).openConsoleLog( logDEBUG );
// load configuration (Lua)
luaVm.loadSource( argv[1] );
@@ -184,8 +184,8 @@ int main( int /* argc */, char *argv[] )
// of global variables)
luaVm.executeMain( );
- std::string logLevel = luaVm.getString( "logger.level" );
- Logger::instance( ).openConsoleLog( Logger::fromString( logLevel ) );
+ //std::string logLevel = luaVm.getString( "logger.level" );
+ //Logger::instance( ).openConsoleLog( Logger::fromString( logLevel ) );
int stopAfterNOperations = luaVm.getInt( "crawler.stop_after_N_operations" );
diff --git a/src/crawl/debugger.lua b/src/crawl/debugger.lua
new file mode 100644
index 0000000..5c92cab
--- /dev/null
+++ b/src/crawl/debugger.lua
@@ -0,0 +1,407 @@
+--[[
+ Copyright (c) 2013 Scott Lembcke and Howling Moon Software
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+
+ TODO:
+ * Print short function arguments as part of stack location.
+ * Bug: sometimes doesn't advance to next line (same line event reported multiple times).
+ * Do coroutines work as expected?
+]]
+
+local function pretty(obj, non_recursive)
+ if type(obj) == "string" then
+ return string.format("%q", obj)
+ elseif type(obj) == "table" and not non_recursive then
+ local str = "{"
+
+ for k, v in pairs(obj) do
+ local pair = pretty(k, true).." = "..pretty(v, true)
+ str = str..(str == "{" and pair or ", "..pair)
+ end
+
+ return str.."}"
+ else
+ return tostring(obj)
+ end
+end
+
+local help_message = [[
+[return] - re-run last command
+c(ontinue) - contiue execution
+s(tep) - step forward by one line (into functions)
+n(ext) - step forward by one line (skipping over functions)
+p(rint) [expression] - execute the expression and print the result
+f(inish) - step forward until exiting the current function
+u(p) - move up the stack by one frame
+d(own) - move down the stack by one frame
+t(race) - print the stack trace
+l(ocals) - print the function arguments, locals and upvalues.
+h(elp) - print this message
+]]
+
+-- The stack level that cmd_* functions use to access locals or info
+local LOCAL_STACK_LEVEL = 6
+
+-- Extra stack frames to chop off.
+-- Used for things like dbgcall() or the overridden assert/error functions
+local stack_top = 0
+
+-- The current stack frame index.
+-- Changed using the up/down commands
+local stack_offset = 0
+
+-- Override if you don't want to use stdin
+-- Override if you don't want to use stdout.
+local function dbg_write(str, ...)
+ io.write(string.format(str, ...))
+end
+
+local function dbg_writeln(str, ...)
+ dbg_write((str or "").."\n", ...)
+end
+
+local function dbg_read(prompt)
+ dbg_write(prompt)
+ return io.read()
+end
+
+local function formatStackLocation(info)
+ local fname = (info.name or string.format("<%s:%d>", info.short_src, info.linedefined))
+ return string.format("%s:%d in function '%s'", info.short_src, info.currentline, fname)
+end
+
+local repl
+
+local function hook_factory(repl_threshold)
+ return function(offset)
+ return function(event, line)
+ local info = debug.getinfo(2)
+
+ if event == "call" and info.linedefined >= 0 then
+ offset = offset + 1
+ elseif event == "return" and info.linedefined >= 0 then
+ if offset <= repl_threshold then
+ -- TODO this is what causes the duplicated lines
+ -- Don't remember why this is even here...
+ --repl()
+ else
+ offset = offset - 1
+ end
+ elseif event == "line" and offset <= repl_threshold then
+ repl()
+ end
+ end
+ end
+end
+
+local hook_step = hook_factory(1)
+local hook_next = hook_factory(0)
+local hook_finish = hook_factory(-1)
+
+local function table_merge(t1, t2)
+ local tbl = {}
+ for k, v in pairs(t1) do tbl[k] = v end
+ for k, v in pairs(t2) do tbl[k] = v end
+
+ return tbl
+end
+
+local VARARG_SENTINEL = "(*varargs)"
+
+local function local_bindings(offset, include_globals)
+ --[[ TODO
+ Need to figure out how to get varargs with LuaJIT
+ ]]
+
+ local level = stack_offset + offset + LOCAL_STACK_LEVEL
+ local func = debug.getinfo(level).func
+ local bindings = {}
+
+ -- Retrieve the upvalues
+ do local i = 1; repeat
+ local name, value = debug.getupvalue(func, i)
+ if name then bindings[name] = value end
+ i = i + 1
+ until name == nil end
+
+ -- Retrieve the locals (overwriting any upvalues)
+ do local i = 1; repeat
+ local name, value = debug.getlocal(level, i)
+ if name then bindings[name] = value end
+ i = i + 1
+ until name == nil end
+
+ -- Retrieve the varargs. (only works in Lua 5.2)
+ local varargs = {}
+ do local i = -1; repeat
+ local name, value = debug.getlocal(level, i)
+ table.insert(varargs, value)
+ i = i - 1
+ until name == nil end
+ bindings[VARARG_SENTINEL] = varargs
+
+ if include_globals then
+ -- Merge the local bindings over the top of the environment table.
+ -- In Lua 5.2, you have to get the environment table from the function's locals.
+ local env = (_VERSION <= "Lua 5.1" and getfenv(func) or bindings._ENV)
+
+ -- Finally, merge the tables and add a lookup for globals.
+ return setmetatable(table_merge(env, bindings), {__index = _G})
+ else
+ return bindings
+ end
+end
+
+local function compile_chunk(expr, env)
+ if _VERSION <= "Lua 5.1" then
+ local chunk = loadstring("return "..expr, "<debugger repl>")
+ if chunk then setfenv(chunk, env) end
+ return chunk
+ else
+ -- The Lua 5.2 way is a bit cleaner
+ return load("return "..expr, "<debugger repl>", "t", env)
+ end
+end
+
+local function super_pack(...)
+ return select("#", ...), {...}
+end
+
+local function cmd_print(expr)
+ local env = local_bindings(1, true)
+ local chunk = compile_chunk(expr, env)
+ if chunk == nil then
+ dbg_writeln("Error: Could not evaluate expression.")
+ return false
+ end
+
+ local count, results = super_pack(pcall(chunk, unpack(env[VARARG_SENTINEL])))
+ if not results[1] then
+ dbg_writeln("Error: %s", results[2])
+ elseif count == 1 then
+ dbg_writeln("Error: No expression to execute")
+ else
+ local result = ""
+ for i=2, count do
+ result = result..(i ~= 2 and ", " or "")..pretty(results[i])
+ end
+
+ dbg_writeln(expr.." => "..result)
+ end
+
+ return false
+end
+
+local function cmd_up()
+ local info = debug.getinfo(stack_offset + LOCAL_STACK_LEVEL + 1)
+
+ if info then
+ stack_offset = stack_offset + 1
+ dbg_writeln("Inspecting frame: "..formatStackLocation(info))
+ else
+ dbg_writeln("Error: Already at the top of the stack.")
+ end
+
+ return false
+end
+
+local function cmd_down()
+ if stack_offset > stack_top then
+ stack_offset = stack_offset - 1
+
+ local info = debug.getinfo(stack_offset + LOCAL_STACK_LEVEL)
+ dbg_writeln("Inspecting frame: "..formatStackLocation(info))
+ else
+ dbg_writeln("Error: Already at the bottom of the stack.")
+ end
+
+ return false
+end
+
+local function cmd_trace()
+ local location = formatStackLocation(debug.getinfo(stack_offset + LOCAL_STACK_LEVEL))
+ local offset = stack_offset - stack_top
+ local message = string.format("Inspecting frame: %d - (%s)", offset, location)
+ dbg_writeln(debug.traceback(message, stack_offset + LOCAL_STACK_LEVEL))
+
+ return false
+end
+
+local function cmd_locals()
+ for k, v in pairs(local_bindings(1, false)) do
+ -- Don't print the Lua 5.2 __ENV local. It's pretty huge and useless to see.
+ if k ~= "_ENV" then
+ dbg_writeln("\t%s => %s", k, pretty(v))
+ end
+ end
+
+ return false
+end
+
+local function cmd_help()
+ dbg_writeln(help_message)
+ return false
+end
+
+local last_cmd = false
+
+-- Run a command line
+-- Returns true if the REPL should exit and the hook function factory
+local function run_command(line)
+ -- Continue without caching the command if you hit control-d.
+ if line == nil then
+ dbg_writeln()
+ return true
+ end
+
+ -- Execute the previous command or cache it
+ if line == "" then
+ if last_cmd then return unpack({run_command(last_cmd)}) else return false end
+ else
+ last_cmd = line
+ end
+
+ local commands = {
+ ["c"] = function() return true end,
+ ["s"] = function() return true, hook_step end,
+ ["n"] = function() return true, hook_next end,
+ ["f"] = function() return true, hook_finish end,
+ ["p%s?(.*)"] = cmd_print,
+ ["u"] = cmd_up,
+ ["d"] = cmd_down,
+ ["t"] = cmd_trace,
+ ["l"] = cmd_locals,
+ ["h"] = cmd_help,
+ }
+
+ for cmd, cmd_func in pairs(commands) do
+ local matches = {string.match(line, "^("..cmd..")$")}
+ if matches[1] then
+ return unpack({cmd_func(select(2, unpack(matches)))})
+ end
+ end
+
+ dbg_writeln("Error: command '%s' not recognized", line)
+ return false
+end
+
+repl = function()
+ dbg_writeln(formatStackLocation(debug.getinfo(LOCAL_STACK_LEVEL - 3 + stack_top)))
+
+ repeat
+ local success, done, hook = pcall(run_command, dbg_read("debugger.lua> "))
+ if success then
+ debug.sethook(hook and hook(0), "crl")
+ else
+ local message = string.format("INTERNAL DEBUGGER.LUA ERROR. ABORTING\n: %s", done)
+ dbg_writeln(message)
+ error(message)
+ end
+ until done
+end
+
+local dbg = setmetatable({}, {
+ __call = function(self, condition, offset)
+ if condition then return end
+
+ offset = (offset or 0)
+ stack_offset = offset
+ stack_top = offset
+
+ debug.sethook(hook_next(1), "crl")
+ return
+ end,
+})
+
+dbg.write = dbg_write
+dbg.writeln = dbg_writeln
+dbg.pretty = pretty
+
+function dbg.error(err, level)
+ level = level or 1
+ dbg_writeln("Debugger stopped on error(%s)", pretty(err))
+ dbg(false, level)
+ error(err, level)
+end
+
+function dbg.assert(condition, message)
+ if not condition then
+ dbg_writeln("Debugger stopped on assert(..., %s)", message)
+ dbg(false, 1)
+ end
+ assert(condition, message)
+end
+
+function dbg.call(f, l)
+ return (xpcall(f, function(err)
+ dbg_writeln("Debugger stopped on error: "..pretty(err))
+ dbg(false, (l or 0) + 1)
+ return
+ end))
+end
+
+local function luajit_load_readline_support()
+ local ffi = require("ffi")
+
+ ffi.cdef[[
+ void free(void *ptr);
+
+ char *readline(const char *);
+ int add_history(const char *);
+ ]]
+
+ local readline = ffi.load("readline")
+
+ dbg_read = function(prompt)
+ local cstr = readline.readline(prompt)
+
+ if cstr ~= nil then
+ local str = ffi.string(cstr)
+
+ if string.match(str, "[^%s]+") then
+ readline.add_history(cstr)
+ end
+
+ ffi.C.free(cstr)
+ return str
+ else
+ return nil
+ end
+ end
+
+ dbg_writeln("Readline support loaded.")
+end
+
+if jit and
+ jit.version == "LuaJIT 2.0.0-beta10"
+then
+ dbg_writeln("debugger.lua loaded for "..jit.version)
+ pcall(luajit_load_readline_support)
+elseif
+ _VERSION == "Lua 5.2" or
+ _VERSION == "Lua 5.1"
+then
+ dbg_writeln("debugger.lua loaded for ".._VERSION)
+else
+ dbg_writeln("debugger.lua not tested against ".._VERSION)
+ dbg_writeln("Please send me feedback!")
+end
+
+return dbg