http://kneo.blogbus.com/logs/24242949.html
require "Json" -- {{{ import local string = string local table = table local coroutine = coroutine local debug = debug local io = io local os = os local pairs = pairs local ipairs = ipairs local print = print local require = require local error = error local type = type local tostring = tostring local pcall = pcall local assert = assert local loadfile = loadfile local unpack = unpack local select = select local Json = Json local G = _G -- }}} module("toydebugger") -- {{{ Variables local GlobalNames = {} local sock local BreakPoints = {} local Hooking local Threads local ThreadDepth local Mode = 'si' local Protocol = 'json' -- }}} -- {{{ Hook function IsInDebug() local X = debug.getinfo('3', 'S') if string.find(X.short_src, 'toydebugger.lua') then return true else return false end end function IsInBreak(Line) if table.maxn(BreakPoints) == 0 or not Line then return false end local File for I, BP in ipairs(BreakPoints) do if BP.Line == Line then if not File then local X = debug.getinfo('3', 'Sl') File = X.source end if BP.File == File then return true end end end return false end function GetDepth() local Depth = 1 for Info in function() return debug.getinfo(Depth) end do Depth = Depth + 1 end Depth = Depth - 2 return Depth end function GetThread() return coroutine.running() or 'thread: Main' end function GetThreadDepth() return { Thread = GetThread(), Depth = GetDepth() - 1 } end function GetLocals() local Locals = {} local Depth = 3 local I = 1 local Name local Value while true do Name, Value = debug.getlocal(Depth, I) if Name == nil then break elseif Name == '(*temporary)' then else table.insert(Locals, {Name, Value}) end I = I + 1 end return Locals end function GetGlobals() local Globals = {} for k, v in pairs(G) do if GlobalNames[k] == nil then table.insert(Globals, {k, v}) end end return Globals end function GetUpvalues() local Upvalues = {} local Depth = 3 local Func = debug.getinfo(Depth, 'f').func local I = 1 local Name local Value while true do Name, Value = debug.getupvalue(Func, I) if Name == nil then break elseif Name == '(*temporary)' then else table.insert(Upvalues, {Name, Value}) end I = I + 1 end return Upvalues end -- {{{ Hook function Hook(Event, Line) if not Hooking then EndHook() end if not IsInBreak(Line) then if Mode == 'sr' then if Event == 'return' and GetThread() == ThreadDepth.Thread and GetDepth() <= ThreadDepth.Depth then ThreadDepth = nil Mode = 'si' end -- always return return elseif Mode == 'so' then if Event == 'line' and GetThread() == ThreadDepth.Thread and GetDepth() <= ThreadDepth.Depth then ThreadDepth = nil Mode = 'si' -- continue else return end elseif Mode == 'si' then if Event == 'line' then -- continue else return end else return end end if IsInDebug() then return end Mode = 'si' -- Do Debug local Info = {} Info.Info = debug.getinfo('2') Info.Thread = coroutine.running() or 'thread: Main' while true do local Op = WaitOp(Info) if not Op then return end if not Op.Mode then Op.Mode = 'o' end if Op.Mode == 'o' then if Op.Op == 'si' then Mode = 'si' -- step into elseif Op.Op == 'so' then Mode = 'so' -- step over ThreadDepth = GetThreadDepth() elseif Op.Op == 'sr' then Mode = 'sr' -- step return ThreadDepth = GetThreadDepth() elseif Op.Op == 'rm' then Mode = 'rm' -- resume elseif Op.Op == 'end' then sock:close() os.exit(1) -- terminate elseif Op.Op == 'get' then -- elseif Op.Op == 'set' then -- else -- end if Op.BreakPoints then assert( type(Op.BreakPoints) == 'table' ) BreakPoints = Op.BreakPoints end break elseif Op.Mode == 'r' then Info = {} if Op.Op == 'g' then Info.Globals = GetGlobals() elseif Op.Op == 'u' then Info.Upvalues = GetUpvalues() elseif Op.Op == 'l' then Info.Locals = GetLocals() end elseif Op.Mode == 'w' then print "[debug] write" else end end end -- }}} local Coroutine = { create = coroutine.create, wrap = coroutine.wrap } function CreateCoroutine(F) local Thread = Coroutine.create(F) debug.sethook(Thread, Hook, 'rl') return Thread end function WrapCoroutine(F) local Thread = Coroutine.create(F) debug.sethook(Thread, Hook, 'rl') return function(...) return coroutine.resume(Thread, ...) end end function InitGlobalNames() for n, v in pairs(G) do GlobalNames[n] = v end end function BeginHook() coroutine.create = CreateCoroutine coroutine.wrap = WrapCoroutine io.stderr:write('Waiting for connection.../n') WaitRemote() io.stderr:write('Connected./n') io.stderr:write('Protocol: ', Protocol, '/n/n') InitGlobalNames() Hooking = true debug.sethook(Hook, "rl") end function EndHook() debug.sethook() Hooking = false end -- }}} -- {{{ Remote function WaitRemote() local socket = require("socket") local server = socket.bind("*", 8173) local client = server:accept() sock = client local Str, ErrMsg = sock:receive('*l') local Code, Object = pcall(Json.Decode, Str) if Code and type(Object) == 'string' then Str = Object end if string.lower(Str) == 'json' then Protocol = 'json' elseif string.lower(Str) == 'simple' then Protocol = 'simple' else Protocol = 'simple' -- by default end end -- {{{ Parse Object function DeepCopy(Src, Seen) if type(Src) == 'function' then return tostring(Src) elseif type(Src) == 'thread' then return tostring(Src) elseif type(Src) == 'userdata' then return tostring(Src) elseif type(Src) ~= 'table' then return Src end if not Seen then Seen = {} end if Seen[Src] then -- TODO: support recursive table --error("Don't allow recursive table...") return tostring(Src) end local Dest = {} Seen[Src] = Dest for Key, Val in pairs(Src) do Key = DeepCopy(Key, Seen) Val = DeepCopy(Val, Seen) Dest[Key] = Val end return Dest end local Parsers = { json = { Encode = function(Object) Object = DeepCopy(Object) return Json.Encode(Object) .. '/n' end , Decode = function(Str) local Code, Object = pcall(Json.Decode, Str) if Code then return Object else print("[debug][error][parse] "..Object) -- use simple protocol return Str end end }, simple = { Encode = function(Object) return Object.Info.short_src .. ': ' .. Object.Info.currentline .. '/n' end , Decode = function(Str) local File, Line = string.match(Str, '^(.*):%s*(%d+)$') return { Mode = 'o', Op = Str} end } } function SendObject(Sock, Object) local Str = Parsers[Protocol].Encode(Object) Sock:send(Str) end function ReceiveObject(Sock) local Str, ErrMsg = Sock:receive('*l') if ErrMsg then --error(ErrMsg) print("[debug][error][receive] "..ErrMsg) EndHook() return end if Str then return Parsers[Protocol].Decode(Str) end end -- }}} function WaitOp(Info) --local Info = 'Connected...' local Op local ErrMsg while Hooking do SendObject(sock, Info) Op = ReceiveObject(sock) Info = coroutine.yield(Op) end end WaitOp = coroutine.wrap(WaitOp) -- }}} function RunScript(ScriptName, ...) BeginHook() local F, ErrMsg = loadfile(ScriptName) if not F then error(ErrMsg) end F(...) EndHook() end if(...) then RunScript(unpack({...}, 1, 1), select(2, ...)) else end -- vim: foldmethod=marker: