remotedebug.lua 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. local skynet = require "skynet"
  2. local debugchannel = require "skynet.debugchannel"
  3. local socketdriver = require "skynet.socketdriver"
  4. local injectrun = require "skynet.injectcode"
  5. local table = table
  6. local debug = debug
  7. local coroutine = coroutine
  8. local sethook = debugchannel.sethook
  9. local M = {}
  10. local HOOK_FUNC = "raw_dispatch_message"
  11. local raw_dispatcher
  12. local print = _G.print
  13. local skynet_suspend
  14. local prompt
  15. local newline
  16. local function change_prompt(s)
  17. newline = true
  18. prompt = s
  19. end
  20. local function replace_upvalue(func, uvname, value)
  21. local i = 1
  22. while true do
  23. local name, uv = debug.getupvalue(func, i)
  24. if name == nil then
  25. break
  26. end
  27. if name == uvname then
  28. if value then
  29. debug.setupvalue(func, i, value)
  30. end
  31. return uv
  32. end
  33. i = i + 1
  34. end
  35. end
  36. local function remove_hook(dispatcher)
  37. assert(raw_dispatcher, "Not in debug mode")
  38. replace_upvalue(dispatcher, HOOK_FUNC, raw_dispatcher)
  39. raw_dispatcher = nil
  40. print = _G.print
  41. skynet.error "Leave debug mode"
  42. end
  43. local function gen_print(fd)
  44. -- redirect print to socket fd
  45. return function(...)
  46. local tmp = table.pack(...)
  47. for i=1,tmp.n do
  48. tmp[i] = tostring(tmp[i])
  49. end
  50. table.insert(tmp, "\n")
  51. socketdriver.send(fd, table.concat(tmp, "\t"))
  52. end
  53. end
  54. local function run_exp(ok, ...)
  55. if ok then
  56. print(...)
  57. end
  58. return ok
  59. end
  60. local function run_cmd(cmd, env, co, level)
  61. if not run_exp(injectrun("return "..cmd, co, level, env)) then
  62. print(select(2, injectrun(cmd,co, level,env)))
  63. end
  64. end
  65. local ctx_skynet = debug.getinfo(skynet.start,"S").short_src -- skip when enter this source file
  66. local ctx_term = debug.getinfo(run_cmd, "S").short_src -- term when get here
  67. local ctx_active = {}
  68. local linehook
  69. local function skip_hook(mode)
  70. local co = coroutine.running()
  71. local ctx = ctx_active[co]
  72. if mode == "return" then
  73. ctx.level = ctx.level - 1
  74. if ctx.level == 0 then
  75. ctx.needupdate = true
  76. sethook(linehook, "crl")
  77. end
  78. else
  79. ctx.level = ctx.level + 1
  80. end
  81. end
  82. function linehook(mode, line)
  83. local co = coroutine.running()
  84. local ctx = ctx_active[co]
  85. if mode ~= "line" then
  86. ctx.needupdate = true
  87. if mode ~= "return" then
  88. if ctx.next_mode or debug.getinfo(2,"S").short_src == ctx_skynet then
  89. ctx.level = 1
  90. sethook(skip_hook, "cr")
  91. end
  92. end
  93. else
  94. if ctx.needupdate then
  95. ctx.needupdate = false
  96. ctx.filename = debug.getinfo(2, "S").short_src
  97. if ctx.filename == ctx_term then
  98. ctx_active[co] = nil
  99. sethook()
  100. change_prompt(string.format(":%08x>", skynet.self()))
  101. return
  102. end
  103. end
  104. change_prompt(string.format("%s(%d)>",ctx.filename, line))
  105. return true -- yield
  106. end
  107. end
  108. local function add_watch_hook()
  109. local co = coroutine.running()
  110. local ctx = {}
  111. ctx_active[co] = ctx
  112. local level = 1
  113. sethook(function(mode)
  114. if mode == "return" then
  115. level = level - 1
  116. else
  117. level = level + 1
  118. if level == 0 then
  119. ctx.needupdate = true
  120. sethook(linehook, "crl")
  121. end
  122. end
  123. end, "cr")
  124. end
  125. local function watch_proto(protoname, cond)
  126. local proto = assert(replace_upvalue(skynet.register_protocol, "proto"), "Can't find proto table")
  127. local p = proto[protoname]
  128. if p == nil then
  129. return "No " .. protoname
  130. end
  131. local dispatch = p.dispatch_origin or p.dispatch
  132. if dispatch == nil then
  133. return "No dispatch"
  134. end
  135. p.dispatch_origin = dispatch
  136. p.dispatch = function(...)
  137. if not cond or cond(...) then
  138. p.dispatch = dispatch -- restore origin dispatch function
  139. add_watch_hook()
  140. end
  141. dispatch(...)
  142. end
  143. end
  144. local function remove_watch()
  145. for co in pairs(ctx_active) do
  146. sethook(co)
  147. end
  148. ctx_active = {}
  149. end
  150. local dbgcmd = {}
  151. function dbgcmd.s(co)
  152. local ctx = ctx_active[co]
  153. ctx.next_mode = false
  154. skynet_suspend(co, coroutine.resume(co))
  155. end
  156. function dbgcmd.n(co)
  157. local ctx = ctx_active[co]
  158. ctx.next_mode = true
  159. skynet_suspend(co, coroutine.resume(co))
  160. end
  161. function dbgcmd.c(co)
  162. sethook(co)
  163. ctx_active[co] = nil
  164. change_prompt(string.format(":%08x>", skynet.self()))
  165. skynet_suspend(co, coroutine.resume(co))
  166. end
  167. local function hook_dispatch(dispatcher, resp, fd, channel)
  168. change_prompt(string.format(":%08x>", skynet.self()))
  169. print = gen_print(fd)
  170. local env = {
  171. print = print,
  172. watch = watch_proto
  173. }
  174. local watch_env = {
  175. print = print
  176. }
  177. local function watch_cmd(cmd)
  178. local co = next(ctx_active)
  179. watch_env._CO = co
  180. if dbgcmd[cmd] then
  181. dbgcmd[cmd](co)
  182. else
  183. run_cmd(cmd, watch_env, co, 0)
  184. end
  185. end
  186. local function debug_hook()
  187. while true do
  188. if newline then
  189. socketdriver.send(fd, prompt)
  190. newline = false
  191. end
  192. local cmd = channel:read()
  193. if cmd then
  194. if cmd == "cont" then
  195. -- leave debug mode
  196. break
  197. end
  198. if cmd ~= "" then
  199. if next(ctx_active) then
  200. watch_cmd(cmd)
  201. else
  202. run_cmd(cmd, env, coroutine.running(),2)
  203. end
  204. end
  205. newline = true
  206. else
  207. -- no input
  208. return
  209. end
  210. end
  211. -- exit debug mode
  212. remove_watch()
  213. remove_hook(dispatcher)
  214. resp(true)
  215. end
  216. local func
  217. local function hook(...)
  218. debug_hook()
  219. return func(...)
  220. end
  221. func = replace_upvalue(dispatcher, HOOK_FUNC, hook)
  222. if func then
  223. local function idle()
  224. if raw_dispatcher then
  225. skynet.timeout(10,idle) -- idle every 0.1s
  226. end
  227. end
  228. skynet.timeout(0, idle)
  229. end
  230. return func
  231. end
  232. function M.start(import, fd, handle)
  233. local dispatcher = import.dispatch
  234. skynet_suspend = import.suspend
  235. assert(raw_dispatcher == nil, "Already in debug mode")
  236. skynet.error "Enter debug mode"
  237. local channel = debugchannel.connect(handle)
  238. raw_dispatcher = hook_dispatch(dispatcher, skynet.response(), fd, channel)
  239. end
  240. return M