mirror of
https://github.com/cloudwu/skynet.git
synced 2026-07-22 02:53:09 +00:00
debug step mode
This commit is contained in:
@@ -85,18 +85,25 @@ LUA_API int lua_getstack (lua_State *L, int level, lua_Debug *ar) {
|
||||
CallInfo *ci;
|
||||
if (level < 0) return 0; /* invalid (negative) level */
|
||||
lua_lock(L);
|
||||
for (ci = L->ci; level > 0 && ci != &L->base_ci; ci = ci->previous)
|
||||
level--;
|
||||
if (level == 0 && ci != &L->base_ci) { /* level found? */
|
||||
if (level == 0 && L->status == LUA_YIELD && isLua(L->ci)) {
|
||||
ci = &(L->temp_ci);
|
||||
*ci = *(L->ci);
|
||||
ci->func = restorestack(L, ci->extra);
|
||||
status = 1;
|
||||
ar->i_ci = ci;
|
||||
} else {
|
||||
for (ci = L->ci; level > 0 && ci != &L->base_ci; ci = ci->previous)
|
||||
level--;
|
||||
if (level == 0 && ci != &L->base_ci) { /* level found? */
|
||||
status = 1;
|
||||
ar->i_ci = ci;
|
||||
}
|
||||
else status = 0; /* no such level */
|
||||
}
|
||||
else status = 0; /* no such level */
|
||||
lua_unlock(L);
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
static const char *upvalname (Proto *p, int uv) {
|
||||
TString *s = check_exp(uv < p->sizeupvalues, p->sp->upvalues[uv].name);
|
||||
if (s == NULL) return "?";
|
||||
@@ -130,7 +137,7 @@ static const char *findlocal (lua_State *L, CallInfo *ci, int n,
|
||||
else
|
||||
base = ci->func + 1;
|
||||
if (name == NULL) { /* no 'standard' name? */
|
||||
StkId limit = (ci == L->ci) ? L->top : ci->next->func;
|
||||
StkId limit = (ci == L->ci || ci == &(L->temp_ci)) ? L->top : ci->next->func;
|
||||
if (limit - base >= n && n > 0) /* is 'n' inside 'ci' stack? */
|
||||
name = "(*temporary)"; /* generic name for any valid slot */
|
||||
else
|
||||
|
||||
@@ -160,6 +160,7 @@ struct lua_State {
|
||||
struct lua_State *twups; /* list of threads with open upvalues */
|
||||
struct lua_longjmp *errorJmp; /* current error recover point */
|
||||
CallInfo base_ci; /* CallInfo for first level (C calling Lua) */
|
||||
CallInfo temp_ci; /* CallInfo for yield from hook (debug api use) */
|
||||
lua_Hook hook;
|
||||
ptrdiff_t errfunc; /* current error handling function (stack index) */
|
||||
int stacksize;
|
||||
|
||||
@@ -181,12 +181,97 @@ lconnect(lua_State *L) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
static const int HOOKKEY = 0;
|
||||
|
||||
/*
|
||||
** Auxiliary function used by several library functions: check for
|
||||
** an optional thread as function's first argument and set 'arg' with
|
||||
** 1 if this argument is present (so that functions can skip it to
|
||||
** access their other arguments)
|
||||
*/
|
||||
static lua_State *getthread (lua_State *L, int *arg) {
|
||||
if (lua_isthread(L, 1)) {
|
||||
*arg = 1;
|
||||
return lua_tothread(L, 1);
|
||||
}
|
||||
else {
|
||||
*arg = 0;
|
||||
return L; /* function will operate over current thread */
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** Call hook function registered at hook table for the current
|
||||
** thread (if there is one)
|
||||
*/
|
||||
static void hookf (lua_State *L, lua_Debug *ar) {
|
||||
static const char *const hooknames[] =
|
||||
{"call", "return", "line", "count", "tail call"};
|
||||
lua_rawgetp(L, LUA_REGISTRYINDEX, &HOOKKEY);
|
||||
lua_pushthread(L);
|
||||
if (lua_rawget(L, -2) == LUA_TFUNCTION) { /* is there a hook function? */
|
||||
lua_pushstring(L, hooknames[(int)ar->event]); /* push event name */
|
||||
if (ar->currentline >= 0)
|
||||
lua_pushinteger(L, ar->currentline); /* push current line */
|
||||
else lua_pushnil(L);
|
||||
lua_call(L, 2, 1); /* call hook function */
|
||||
int yield = lua_toboolean(L, -1);
|
||||
lua_pop(L,1);
|
||||
if (yield) {
|
||||
lua_yield(L, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** Convert a string mask (for 'sethook') into a bit mask
|
||||
*/
|
||||
static int makemask (const char *smask, int count) {
|
||||
int mask = 0;
|
||||
if (strchr(smask, 'c')) mask |= LUA_MASKCALL;
|
||||
if (strchr(smask, 'r')) mask |= LUA_MASKRET;
|
||||
if (strchr(smask, 'l')) mask |= LUA_MASKLINE;
|
||||
if (count > 0) mask |= LUA_MASKCOUNT;
|
||||
return mask;
|
||||
}
|
||||
|
||||
static int db_sethook (lua_State *L) {
|
||||
int arg, mask, count;
|
||||
lua_Hook func;
|
||||
lua_State *L1 = getthread(L, &arg);
|
||||
if (lua_isnoneornil(L, arg+1)) { /* no hook? */
|
||||
lua_settop(L, arg+1);
|
||||
func = NULL; mask = 0; count = 0; /* turn off hooks */
|
||||
}
|
||||
else {
|
||||
const char *smask = luaL_checkstring(L, arg+2);
|
||||
luaL_checktype(L, arg+1, LUA_TFUNCTION);
|
||||
count = (int)luaL_optinteger(L, arg + 3, 0);
|
||||
func = hookf; mask = makemask(smask, count);
|
||||
}
|
||||
if (lua_rawgetp(L, LUA_REGISTRYINDEX, &HOOKKEY) == LUA_TNIL) {
|
||||
lua_createtable(L, 0, 2); /* create a hook table */
|
||||
lua_pushvalue(L, -1);
|
||||
lua_rawsetp(L, LUA_REGISTRYINDEX, &HOOKKEY); /* set it in position */
|
||||
lua_pushstring(L, "k");
|
||||
lua_setfield(L, -2, "__mode"); /** hooktable.__mode = "k" */
|
||||
lua_pushvalue(L, -1);
|
||||
lua_setmetatable(L, -2); /* setmetatable(hooktable) = hooktable */
|
||||
}
|
||||
lua_pushthread(L1); lua_xmove(L1, L, 1); /* key (thread) */
|
||||
lua_pushvalue(L, arg + 1); /* value (hook function) */
|
||||
lua_rawset(L, -3); /* hooktable[L1] = new Lua hook */
|
||||
lua_sethook(L1, func, mask, count);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
luaopen_debugchannel(lua_State *L) {
|
||||
luaL_Reg l[] = {
|
||||
{ "create", lcreate }, // for write
|
||||
{ "connect", lconnect }, // for read
|
||||
{ "release", lrelease },
|
||||
{ "sethook", db_sethook },
|
||||
{ NULL, NULL },
|
||||
};
|
||||
luaL_checkversion(L);
|
||||
|
||||
@@ -233,6 +233,9 @@ function suspend(co, result, command, param, size)
|
||||
elseif command == "QUIT" then
|
||||
-- service exit
|
||||
return
|
||||
elseif command == nil then
|
||||
-- debug trace
|
||||
return
|
||||
else
|
||||
error("Unknown command : " .. command .. "\n" .. debug.traceback(co))
|
||||
end
|
||||
@@ -719,6 +722,7 @@ local debug = require "skynet.debug"
|
||||
debug(skynet, {
|
||||
dispatch = dispatch_message,
|
||||
clear = clear_pool,
|
||||
suspend = suspend,
|
||||
})
|
||||
|
||||
return skynet
|
||||
|
||||
@@ -60,7 +60,7 @@ end
|
||||
|
||||
function dbgcmd.REMOTEDEBUG(...)
|
||||
local remotedebug = require "skynet.remotedebug"
|
||||
remotedebug.start(export.dispatch, ...)
|
||||
remotedebug.start(export, ...)
|
||||
end
|
||||
|
||||
local function _debug_dispatch(session, address, cmd, ...)
|
||||
|
||||
133
lualib/skynet/injectcode.lua
Normal file
133
lualib/skynet/injectcode.lua
Normal file
@@ -0,0 +1,133 @@
|
||||
local debug = debug
|
||||
local table = table
|
||||
|
||||
local FUNC_TEMP=[[
|
||||
local $ARGS
|
||||
return function(...)
|
||||
$SOURCE
|
||||
end,
|
||||
function()
|
||||
return {$LOCALS}
|
||||
end
|
||||
]]
|
||||
|
||||
local temp = {}
|
||||
local function wrap_locals(co, source, level, ext_funcs)
|
||||
if co == coroutine.running() then
|
||||
level = level + 3
|
||||
end
|
||||
local f = debug.getinfo(co, level,"f").func
|
||||
if f == nil then
|
||||
return false, "Invalid level"
|
||||
end
|
||||
|
||||
local uv = {}
|
||||
local locals = {}
|
||||
local uv_id = {}
|
||||
local local_id = {}
|
||||
|
||||
if ext_funcs then
|
||||
for k,v in pairs(ext_funcs) do
|
||||
table.insert(uv, k)
|
||||
end
|
||||
end
|
||||
local i = 1
|
||||
while true do
|
||||
local name, value = debug.getlocal(co, level, i)
|
||||
if name == nil then
|
||||
break
|
||||
end
|
||||
if name:byte() ~= 40 then -- '('
|
||||
table.insert(uv, name)
|
||||
table.insert(locals, ("[%d]=%s,"):format(i,name))
|
||||
local_id[name] = value
|
||||
end
|
||||
i = i + 1
|
||||
end
|
||||
local i = 1
|
||||
while true do
|
||||
local name = debug.getupvalue(f, i)
|
||||
if name == nil then
|
||||
break
|
||||
end
|
||||
uv_id[name] = i
|
||||
table.insert(uv, name)
|
||||
i = i + 1
|
||||
end
|
||||
temp.ARGS = table.concat(uv, ",")
|
||||
temp.SOURCE = source
|
||||
temp.LOCALS = table.concat(locals)
|
||||
local full_source = FUNC_TEMP:gsub("%$(%w+)",temp)
|
||||
local loader, err = load(full_source, "=(debug)")
|
||||
if loader == nil then
|
||||
return false, err
|
||||
end
|
||||
local func, update = loader()
|
||||
-- join func's upvalues
|
||||
local i = 1
|
||||
while true do
|
||||
local name = debug.getupvalue(func, i)
|
||||
if name == nil then
|
||||
break
|
||||
end
|
||||
if ext_funcs then
|
||||
local v = ext_funcs[name]
|
||||
if v then
|
||||
debug.setupvalue(func, i, v)
|
||||
end
|
||||
end
|
||||
|
||||
local local_value = local_id[name]
|
||||
if local_value then
|
||||
debug.setupvalue(func, i, local_value)
|
||||
end
|
||||
local upvalue_id = uv_id[name]
|
||||
if upvalue_id then
|
||||
debug.upvaluejoin(func, i, f, upvalue_id)
|
||||
end
|
||||
i=i+1
|
||||
end
|
||||
local vararg, v = debug.getlocal(co, level, -1)
|
||||
if vararg then
|
||||
local vargs = { v }
|
||||
local i = 2
|
||||
while true do
|
||||
local vararg,v = debug.getlocal(co, level, -i)
|
||||
if vararg then
|
||||
vargs[i] = v
|
||||
else
|
||||
break
|
||||
end
|
||||
i=i+1
|
||||
end
|
||||
return func, update, table.unpack(vargs)
|
||||
else
|
||||
return func, update
|
||||
end
|
||||
end
|
||||
|
||||
local function exec(co, level, func, update, ...)
|
||||
if not func then
|
||||
return false, update
|
||||
end
|
||||
if co == coroutine.running() then
|
||||
level = level + 2
|
||||
end
|
||||
local rets = table.pack(pcall(func, ...))
|
||||
if rets[1] then
|
||||
local needupdate = update()
|
||||
for k,v in pairs(needupdate) do
|
||||
debug.setlocal(co, level,k,v)
|
||||
end
|
||||
return table.unpack(rets, 1, rets.n)
|
||||
else
|
||||
return false, rets[2]
|
||||
end
|
||||
end
|
||||
|
||||
return function (source, co, level, ext_funcs)
|
||||
co = co or coroutine.running()
|
||||
level = level or 0
|
||||
return exec(co, level, wrap_locals(co, source, level, ext_funcs))
|
||||
end
|
||||
|
||||
@@ -1,36 +1,56 @@
|
||||
local skynet = require "skynet"
|
||||
local debugchannel = require "debugchannel"
|
||||
local socket = require "socket"
|
||||
local injectrun = require "skynet.injectcode"
|
||||
local table = table
|
||||
local debug = debug
|
||||
local coroutine = coroutine
|
||||
local sethook = debugchannel.sethook
|
||||
|
||||
|
||||
local M = {}
|
||||
|
||||
local HOOK_FUNC = "raw_dispatch_message"
|
||||
local raw_dispatcher
|
||||
local print = _G.print
|
||||
local skynet_suspend
|
||||
local prompt
|
||||
local newline
|
||||
|
||||
local function change_prompt(s)
|
||||
newline = true
|
||||
prompt = s
|
||||
end
|
||||
|
||||
local function replace_upvalue(func, uvname, value)
|
||||
local i = 1
|
||||
while true do
|
||||
local name, uv = debug.getupvalue(func, i)
|
||||
if name == nil then
|
||||
break
|
||||
end
|
||||
if name == uvname then
|
||||
if value then
|
||||
debug.setupvalue(func, i, value)
|
||||
end
|
||||
return uv
|
||||
end
|
||||
i = i + 1
|
||||
end
|
||||
end
|
||||
|
||||
local function remove_hook(dispatcher)
|
||||
assert(raw_dispatcher, "Not in debug mode")
|
||||
for i=1,8 do
|
||||
local name, func = debug.getupvalue(dispatcher, i)
|
||||
if name == HOOK_FUNC then
|
||||
debug.setupvalue(dispatcher, i, raw_dispatcher)
|
||||
break
|
||||
end
|
||||
end
|
||||
replace_upvalue(dispatcher, HOOK_FUNC, raw_dispatcher)
|
||||
raw_dispatcher = nil
|
||||
print = _G.print
|
||||
|
||||
skynet.error "Leave debug mode"
|
||||
end
|
||||
|
||||
local function idle()
|
||||
skynet.timeout(10,idle) -- idle every 0.1s
|
||||
end
|
||||
|
||||
local function hook_dispatch(dispatcher, resp, fd, channel)
|
||||
local address = skynet.self()
|
||||
local prompt = string.format(":%08x>", address)
|
||||
local newline = true
|
||||
|
||||
local function print(...)
|
||||
local function gen_print(fd)
|
||||
-- redirect print to socket fd
|
||||
return function(...)
|
||||
local tmp = table.pack(...)
|
||||
for i=1,tmp.n do
|
||||
tmp[i] = tostring(tmp[i])
|
||||
@@ -38,91 +58,197 @@ local function hook_dispatch(dispatcher, resp, fd, channel)
|
||||
table.insert(tmp, "\n")
|
||||
socket.write(fd, table.concat(tmp, "\t"))
|
||||
end
|
||||
end
|
||||
|
||||
local message = {}
|
||||
local cond
|
||||
local function run_exp(ok, ...)
|
||||
if ok then
|
||||
print(...)
|
||||
end
|
||||
return ok
|
||||
end
|
||||
|
||||
local function breakpoint(f)
|
||||
cond = f
|
||||
local function run_cmd(cmd, env, co, level)
|
||||
if not run_exp(injectrun("return "..cmd, co, level, env)) then
|
||||
print(select(2, injectrun(cmd,co, level,env)))
|
||||
end
|
||||
end
|
||||
|
||||
local ctx_skynet = debug.getinfo(skynet.start,"S").short_src -- skip when enter this source file
|
||||
local ctx_term = debug.getinfo(run_cmd, "S").short_src -- term when get here
|
||||
local ctx_active = {}
|
||||
|
||||
local linehook
|
||||
local function skip_hook(mode)
|
||||
local co = coroutine.running()
|
||||
local ctx = ctx_active[co]
|
||||
if mode == "return" then
|
||||
ctx.level = ctx.level - 1
|
||||
if ctx.level == 0 then
|
||||
ctx.needupdate = true
|
||||
sethook(linehook, "crl")
|
||||
end
|
||||
else
|
||||
ctx.level = ctx.level + 1
|
||||
end
|
||||
end
|
||||
|
||||
function linehook(mode, line)
|
||||
local co = coroutine.running()
|
||||
local ctx = ctx_active[co]
|
||||
if mode ~= "line" then
|
||||
ctx.needupdate = true
|
||||
if mode ~= "return" then
|
||||
if ctx.next_mode or debug.getinfo(2,"S").short_src == ctx_skynet then
|
||||
ctx.level = 1
|
||||
sethook(skip_hook, "cr")
|
||||
end
|
||||
end
|
||||
else
|
||||
if ctx.needupdate then
|
||||
ctx.needupdate = false
|
||||
ctx.filename = debug.getinfo(2, "S").short_src
|
||||
if ctx.filename == ctx_term then
|
||||
ctx_active[co] = nil
|
||||
sethook()
|
||||
change_prompt(string.format(":%08x>", skynet.self()))
|
||||
return
|
||||
end
|
||||
end
|
||||
-- Lua 5.3 report currentline seems wrong
|
||||
change_prompt(string.format("%s(%d)>",ctx.filename, line-1))
|
||||
return true -- yield
|
||||
end
|
||||
end
|
||||
|
||||
local function add_watch_hook()
|
||||
local co = coroutine.running()
|
||||
local ctx = {}
|
||||
ctx_active[co] = ctx
|
||||
local level = 3
|
||||
sethook(function()
|
||||
level = level - 1
|
||||
if level == 0 then
|
||||
ctx.needupdate = true
|
||||
sethook(linehook, "crl")
|
||||
end
|
||||
end, "r")
|
||||
end
|
||||
|
||||
local function watch_proto(protoname)
|
||||
local proto = assert(replace_upvalue(skynet.register_protocol, "proto"), "Can't find proto table")
|
||||
local p = proto[protoname]
|
||||
local dispatch = p.dispatch_origin or p.dispatch
|
||||
if p == nil or dispatch == nil then
|
||||
return "No " .. protoname
|
||||
end
|
||||
p.dispatch_origin = dispatch
|
||||
p.dispatch = function(...)
|
||||
p.dispatch = dispatch -- restore origin dispatch function
|
||||
add_watch_hook()
|
||||
dispatch(...)
|
||||
end
|
||||
end
|
||||
|
||||
local function remove_watch()
|
||||
for co in pairs(ctx_active) do
|
||||
sethook(co)
|
||||
end
|
||||
ctx_active = {}
|
||||
end
|
||||
|
||||
local dbgcmd = {}
|
||||
|
||||
function dbgcmd.s(co)
|
||||
local ctx = ctx_active[co]
|
||||
ctx.next_mode = false
|
||||
skynet_suspend(co, coroutine.resume(co))
|
||||
end
|
||||
|
||||
function dbgcmd.n(co)
|
||||
local ctx = ctx_active[co]
|
||||
ctx.next_mode = true
|
||||
skynet_suspend(co, coroutine.resume(co))
|
||||
end
|
||||
|
||||
function dbgcmd.c(co)
|
||||
sethook(co)
|
||||
ctx_active[co] = nil
|
||||
change_prompt(string.format(":%08x>", skynet.self()))
|
||||
skynet_suspend(co, coroutine.resume(co))
|
||||
end
|
||||
|
||||
local function hook_dispatch(dispatcher, resp, fd, channel)
|
||||
change_prompt(string.format(":%08x>", skynet.self()))
|
||||
|
||||
print = gen_print(fd)
|
||||
local env = {
|
||||
print = print,
|
||||
watch = watch_proto
|
||||
}
|
||||
|
||||
local watch_env = {
|
||||
print = print
|
||||
}
|
||||
|
||||
local function watch_cmd(cmd)
|
||||
local co = next(ctx_active)
|
||||
if dbgcmd[cmd] then
|
||||
dbgcmd[cmd](co)
|
||||
else
|
||||
run_cmd(cmd, watch_env, co, 0)
|
||||
end
|
||||
end
|
||||
|
||||
local env = setmetatable({ skynet = skynet, print = print, hook = breakpoint, m = message }, { __index = _ENV })
|
||||
|
||||
local function debug_hook(proto, msg, sz, session, source)
|
||||
message.proto = proto
|
||||
message.session = session
|
||||
message.address = source
|
||||
message.message = msg
|
||||
message.size = sz
|
||||
local sleep = nil
|
||||
local function debug_hook()
|
||||
while true do
|
||||
if newline then
|
||||
socket.write(fd, "\n")
|
||||
socket.write(fd, prompt)
|
||||
newline = false
|
||||
end
|
||||
local cmd = channel:read(sleep)
|
||||
local cmd = channel:read()
|
||||
if cmd then
|
||||
if cmd == "cont" then
|
||||
-- leave debug mode
|
||||
break
|
||||
end
|
||||
if cmd ~= "" then
|
||||
if sleep then
|
||||
if cmd == "c" then
|
||||
print "continue..."
|
||||
prompt = string.format(":%08x>", address)
|
||||
newline = true
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
local f = load("return "..cmd, "=(debug)", "t", env)
|
||||
if not f then
|
||||
local err
|
||||
f,err = load(cmd, "=(debug)", "t", env)
|
||||
if not f then
|
||||
socket.write(fd, err .. "\n")
|
||||
end
|
||||
end
|
||||
if f then
|
||||
print(select(2,pcall(f)))
|
||||
if next(ctx_active) then
|
||||
watch_cmd(cmd)
|
||||
else
|
||||
run_cmd(cmd, env, coroutine.running(),2)
|
||||
end
|
||||
end
|
||||
newline = true
|
||||
else
|
||||
-- no input
|
||||
if sleep == nil then
|
||||
if cond and cond(message) then
|
||||
-- cond break point
|
||||
prompt = "break>"
|
||||
sleep = 0.1
|
||||
print()
|
||||
newline = true
|
||||
else
|
||||
return
|
||||
end
|
||||
end
|
||||
return
|
||||
end
|
||||
end
|
||||
-- exit debug mode
|
||||
remove_watch()
|
||||
remove_hook(dispatcher)
|
||||
resp(true)
|
||||
end
|
||||
|
||||
for i=1,8 do
|
||||
local name, func = debug.getupvalue(dispatcher, i)
|
||||
if name == HOOK_FUNC then
|
||||
local function hook(...)
|
||||
debug_hook(...)
|
||||
return func(...)
|
||||
end
|
||||
debug.setupvalue(dispatcher, i, hook)
|
||||
skynet.timeout(0, idle)
|
||||
return func
|
||||
end
|
||||
local func
|
||||
local function hook(...)
|
||||
debug_hook()
|
||||
return func(...)
|
||||
end
|
||||
func = replace_upvalue(dispatcher, HOOK_FUNC, hook)
|
||||
if func then
|
||||
local function idle()
|
||||
skynet.timeout(10,idle) -- idle every 0.1s
|
||||
end
|
||||
skynet.timeout(0, idle)
|
||||
end
|
||||
return func
|
||||
end
|
||||
|
||||
function M.start(dispatcher, fd, handle)
|
||||
function M.start(import, fd, handle)
|
||||
local dispatcher = import.dispatch
|
||||
skynet_suspend = import.suspend
|
||||
assert(raw_dispatcher == nil, "Already in debug mode")
|
||||
skynet.error "Enter debug mode"
|
||||
local channel = debugchannel.connect(handle)
|
||||
|
||||
Reference in New Issue
Block a user