Blame view

publish/skynet/lualib/skynet/remotedebug.lua 5.48 KB
4d6f285d   zhouhaihai   增加发布功能
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
  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