123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269 |
- local skynet = require "skynet"
- local debugchannel = require "skynet.debugchannel"
- local socketdriver = require "skynet.socketdriver"
- 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")
- replace_upvalue(dispatcher, HOOK_FUNC, raw_dispatcher)
- raw_dispatcher = nil
- print = _G.print
- skynet.error "Leave debug mode"
- end
- 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])
- end
- table.insert(tmp, "\n")
- socketdriver.send(fd, table.concat(tmp, "\t"))
- end
- end
- local function run_exp(ok, ...)
- if ok then
- print(...)
- end
- return ok
- end
- 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
- change_prompt(string.format("%s(%d)>",ctx.filename, line))
- return true -- yield
- end
- end
- local function add_watch_hook()
- local co = coroutine.running()
- local ctx = {}
- ctx_active[co] = ctx
- local level = 1
- sethook(function(mode)
- if mode == "return" then
- level = level - 1
- else
- level = level + 1
- if level == 0 then
- ctx.needupdate = true
- sethook(linehook, "crl")
- end
- end
- end, "cr")
- end
- local function watch_proto(protoname, cond)
- local proto = assert(replace_upvalue(skynet.register_protocol, "proto"), "Can't find proto table")
- local p = proto[protoname]
- if p == nil then
- return "No " .. protoname
- end
- local dispatch = p.dispatch_origin or p.dispatch
- if dispatch == nil then
- return "No dispatch"
- end
- p.dispatch_origin = dispatch
- p.dispatch = function(...)
- if not cond or cond(...) then
- p.dispatch = dispatch -- restore origin dispatch function
- add_watch_hook()
- end
- 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)
- watch_env._CO = co
- if dbgcmd[cmd] then
- dbgcmd[cmd](co)
- else
- run_cmd(cmd, watch_env, co, 0)
- end
- end
- local function debug_hook()
- while true do
- if newline then
- socketdriver.send(fd, prompt)
- newline = false
- end
- local cmd = channel:read()
- if cmd then
- if cmd == "cont" then
- -- leave debug mode
- break
- end
- if cmd ~= "" then
- if next(ctx_active) then
- watch_cmd(cmd)
- else
- run_cmd(cmd, env, coroutine.running(),2)
- end
- end
- newline = true
- else
- -- no input
- return
- end
- end
- -- exit debug mode
- remove_watch()
- remove_hook(dispatcher)
- resp(true)
- end
- local func
- local function hook(...)
- debug_hook()
- return func(...)
- end
- func = replace_upvalue(dispatcher, HOOK_FUNC, hook)
- if func then
- local function idle()
- if raw_dispatcher then
- skynet.timeout(10,idle) -- idle every 0.1s
- end
- end
- skynet.timeout(0, idle)
- end
- return func
- end
- 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)
- raw_dispatcher = hook_dispatch(dispatcher, skynet.response(), fd, channel)
- end
- return M
|