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: