This Trac instance is not used for development anymore!

We migrated our development workflow to git and Gitea.
To test the future redirection, replace trac by ariadne in the page URL.

source: ps/trunk/build/premake/premake5/mobdebug.lua

Last change on this file was 22021, checked in by Itms, 6 years ago

Upgrade premake5 from alpha12 to alpha13, refs #3729.

Fixes an issue on macOS Mojave that was patched in rP21941.

The list of changes can be found under build/premake/premake5/CHANGES.txt.

File size: 67.2 KB
RevLine 
[22021]1--
2-- MobDebug -- Lua remote debugger
3-- Copyright 2011-15 Paul Kulchenko
4-- Based on RemDebug 1.0 Copyright Kepler Project 2005
5--
6
7-- use loaded modules or load explicitly on those systems that require that
8local require = require
9local io = io or require "io"
10local table = table or require "table"
11local string = string or require "string"
12local coroutine = coroutine or require "coroutine"
13local debug = require "debug"
14-- protect require "os" as it may fail on embedded systems without os module
15local os = os or (function(module)
16 local ok, res = pcall(require, module)
17 return ok and res or nil
18end)("os")
19
20local mobdebug = {
21 _NAME = "mobdebug",
22 _VERSION = "0.702",
23 _COPYRIGHT = "Paul Kulchenko",
24 _DESCRIPTION = "Mobile Remote Debugger for the Lua programming language",
25 port = os and os.getenv and tonumber((os.getenv("MOBDEBUG_PORT"))) or 8172,
26 checkcount = 200,
27 yieldtimeout = 0.02, -- yield timeout (s)
28 connecttimeout = 2, -- connect timeout (s)
29}
30
31local HOOKMASK = "lcr"
32local error = error
33local getfenv = getfenv
34local setfenv = setfenv
35local loadstring = loadstring or load -- "load" replaced "loadstring" in Lua 5.2
36local pairs = pairs
37local setmetatable = setmetatable
38local tonumber = tonumber
39local unpack = table.unpack or unpack
40local rawget = rawget
41local gsub, sub, find = string.gsub, string.sub, string.find
42
43-- if strict.lua is used, then need to avoid referencing some global
44-- variables, as they can be undefined;
45-- use rawget to avoid complaints from strict.lua at run-time.
46-- it's safe to do the initialization here as all these variables
47-- should get defined values (if any) before the debugging starts.
48-- there is also global 'wx' variable, which is checked as part of
49-- the debug loop as 'wx' can be loaded at any time during debugging.
50local genv = _G or _ENV
51local jit = rawget(genv, "jit")
52local MOAICoroutine = rawget(genv, "MOAICoroutine")
53
54-- ngx_lua debugging requires a special handling as its coroutine.*
55-- methods use a different mechanism that doesn't allow resume calls
56-- from debug hook handlers.
57-- Instead, the "original" coroutine.* methods are used.
58-- `rawget` needs to be used to protect against `strict` checks, but
59-- ngx_lua hides those in a metatable, so need to use that.
60local metagindex = getmetatable(genv) and getmetatable(genv).__index
61local ngx = type(metagindex) == "table" and metagindex.rawget and metagindex:rawget("ngx") or nil
62local corocreate = ngx and coroutine._create or coroutine.create
63local cororesume = ngx and coroutine._resume or coroutine.resume
64local coroyield = ngx and coroutine._yield or coroutine.yield
65local corostatus = ngx and coroutine._status or coroutine.status
66local corowrap = coroutine.wrap
67
68if not setfenv then -- Lua 5.2+
69 -- based on http://lua-users.org/lists/lua-l/2010-06/msg00314.html
70 -- this assumes f is a function
71 local function findenv(f)
72 local level = 1
73 repeat
74 local name, value = debug.getupvalue(f, level)
75 if name == '_ENV' then return level, value end
76 level = level + 1
77 until name == nil
78 return nil end
79 getfenv = function (f) return(select(2, findenv(f)) or _G) end
80 setfenv = function (f, t)
81 local level = findenv(f)
82 if level then debug.setupvalue(f, level, t) end
83 return f end
84end
85
86-- check for OS and convert file names to lower case on windows
87-- (its file system is case insensitive, but case preserving), as setting a
88-- breakpoint on x:\Foo.lua will not work if the file was loaded as X:\foo.lua.
89-- OSX and Windows behave the same way (case insensitive, but case preserving).
90-- OSX can be configured to be case-sensitive, so check for that. This doesn't
91-- handle the case of different partitions having different case-sensitivity.
92local win = os and os.getenv and (os.getenv('WINDIR') or (os.getenv('OS') or ''):match('[Ww]indows')) and true or false
93local mac = not win and (os and os.getenv and os.getenv('DYLD_LIBRARY_PATH') or not io.open("/proc")) and true or false
94local iscasepreserving = win or (mac and io.open('/library') ~= nil)
95
96-- turn jit off based on Mike Pall's comment in this discussion:
97-- http://www.freelists.org/post/luajit/Debug-hooks-and-JIT,2
98-- "You need to turn it off at the start if you plan to receive
99-- reliable hook calls at any later point in time."
100if jit and jit.off then jit.off() end
101
102local socket = require "socket"
103local coro_debugger
104local coro_debugee
105local coroutines = {}; setmetatable(coroutines, {__mode = "k"}) -- "weak" keys
106local events = { BREAK = 1, WATCH = 2, RESTART = 3, STACK = 4 }
107local breakpoints = {}
108local watches = {}
109local lastsource
110local lastfile
111local watchescnt = 0
112local abort -- default value is nil; this is used in start/loop distinction
113local seen_hook = false
114local checkcount = 0
115local step_into = false
116local step_over = false
117local step_level = 0
118local stack_level = 0
119local server
120local buf
121local outputs = {}
122local iobase = {print = print}
123local basedir = ""
124local deferror = "execution aborted at default debugee"
125local debugee = function ()
126 local a = 1
127 for _ = 1, 10 do a = a + 1 end
128 error(deferror)
129end
130local function q(s) return string.gsub(s, '([%(%)%.%%%+%-%*%?%[%^%$%]])','%%%1') end
131
132local serpent = (function() ---- include Serpent module for serialization
133local n, v = "serpent", "0.30" -- (C) 2012-17 Paul Kulchenko; MIT License
134local c, d = "Paul Kulchenko", "Lua serializer and pretty printer"
135local snum = {[tostring(1/0)]='1/0 --[[math.huge]]',[tostring(-1/0)]='-1/0 --[[-math.huge]]',[tostring(0/0)]='0/0'}
136local badtype = {thread = true, userdata = true, cdata = true}
137local getmetatable = debug and debug.getmetatable or getmetatable
138local pairs = function(t) return next, t end -- avoid using __pairs in Lua 5.2+
139local keyword, globals, G = {}, {}, (_G or _ENV)
140for _,k in ipairs({'and', 'break', 'do', 'else', 'elseif', 'end', 'false',
141 'for', 'function', 'goto', 'if', 'in', 'local', 'nil', 'not', 'or', 'repeat',
142 'return', 'then', 'true', 'until', 'while'}) do keyword[k] = true end
143for k,v in pairs(G) do globals[v] = k end -- build func to name mapping
144for _,g in ipairs({'coroutine', 'debug', 'io', 'math', 'string', 'table', 'os'}) do
145 for k,v in pairs(type(G[g]) == 'table' and G[g] or {}) do globals[v] = g..'.'..k end end
146
147local function s(t, opts)
148 local name, indent, fatal, maxnum = opts.name, opts.indent, opts.fatal, opts.maxnum
149 local sparse, custom, huge = opts.sparse, opts.custom, not opts.nohuge
150 local space, maxl = (opts.compact and '' or ' '), (opts.maxlevel or math.huge)
151 local maxlen, metatostring = tonumber(opts.maxlength), opts.metatostring
152 local iname, comm = '_'..(name or ''), opts.comment and (tonumber(opts.comment) or math.huge)
153 local numformat = opts.numformat or "%.17g"
154 local seen, sref, syms, symn = {}, {'local '..iname..'={}'}, {}, 0
155 local function gensym(val) return '_'..(tostring(tostring(val)):gsub("[^%w]",""):gsub("(%d%w+)",
156 -- tostring(val) is needed because __tostring may return a non-string value
157 function(s) if not syms[s] then symn = symn+1; syms[s] = symn end return tostring(syms[s]) end)) end
158 local function safestr(s) return type(s) == "number" and tostring(huge and snum[tostring(s)] or numformat:format(s))
159 or type(s) ~= "string" and tostring(s) -- escape NEWLINE/010 and EOF/026
160 or ("%q"):format(s):gsub("\010","n"):gsub("\026","\\026") end
161 local function comment(s,l) return comm and (l or 0) < comm and ' --[['..select(2, pcall(tostring, s))..']]' or '' end
162 local function globerr(s,l) return globals[s] and globals[s]..comment(s,l) or not fatal
163 and safestr(select(2, pcall(tostring, s))) or error("Can't serialize "..tostring(s)) end
164 local function safename(path, name) -- generates foo.bar, foo[3], or foo['b a r']
165 local n = name == nil and '' or name
166 local plain = type(n) == "string" and n:match("^[%l%u_][%w_]*$") and not keyword[n]
167 local safe = plain and n or '['..safestr(n)..']'
168 return (path or '')..(plain and path and '.' or '')..safe, safe end
169 local alphanumsort = type(opts.sortkeys) == 'function' and opts.sortkeys or function(k, o, n) -- k=keys, o=originaltable, n=padding
170 local maxn, to = tonumber(n) or 12, {number = 'a', string = 'b'}
171 local function padnum(d) return ("%0"..tostring(maxn).."d"):format(tonumber(d)) end
172 table.sort(k, function(a,b)
173 -- sort numeric keys first: k[key] is not nil for numerical keys
174 return (k[a] ~= nil and 0 or to[type(a)] or 'z')..(tostring(a):gsub("%d+",padnum))
175 < (k[b] ~= nil and 0 or to[type(b)] or 'z')..(tostring(b):gsub("%d+",padnum)) end) end
176 local function val2str(t, name, indent, insref, path, plainindex, level)
177 local ttype, level, mt = type(t), (level or 0), getmetatable(t)
178 local spath, sname = safename(path, name)
179 local tag = plainindex and
180 ((type(name) == "number") and '' or name..space..'='..space) or
181 (name ~= nil and sname..space..'='..space or '')
182 if seen[t] then -- already seen this element
183 sref[#sref+1] = spath..space..'='..space..seen[t]
184 return tag..'nil'..comment('ref', level) end
185 -- protect from those cases where __tostring may fail
186 if type(mt) == 'table' then
187 local to, tr = pcall(function() return mt.__tostring(t) end)
188 local so, sr = pcall(function() return mt.__serialize(t) end)
189 if (opts.metatostring ~= false and to or so) then -- knows how to serialize itself
190 seen[t] = insref or spath
191 t = so and sr or tr
192 ttype = type(t)
193 end -- new value falls through to be serialized
194 end
195 if ttype == "table" then
196 if level >= maxl then return tag..'{}'..comment('maxlvl', level) end
197 seen[t] = insref or spath
198 if next(t) == nil then return tag..'{}'..comment(t, level) end -- table empty
199 if maxlen and maxlen < 0 then return tag..'{}'..comment('maxlen', level) end
200 local maxn, o, out = math.min(#t, maxnum or #t), {}, {}
201 for key = 1, maxn do o[key] = key end
202 if not maxnum or #o < maxnum then
203 local n = #o -- n = n + 1; o[n] is much faster than o[#o+1] on large tables
204 for key in pairs(t) do if o[key] ~= key then n = n + 1; o[n] = key end end end
205 if maxnum and #o > maxnum then o[maxnum+1] = nil end
206 if opts.sortkeys and #o > maxn then alphanumsort(o, t, opts.sortkeys) end
207 local sparse = sparse and #o > maxn -- disable sparsness if only numeric keys (shorter output)
208 for n, key in ipairs(o) do
209 local value, ktype, plainindex = t[key], type(key), n <= maxn and not sparse
210 if opts.valignore and opts.valignore[value] -- skip ignored values; do nothing
211 or opts.keyallow and not opts.keyallow[key]
212 or opts.keyignore and opts.keyignore[key]
213 or opts.valtypeignore and opts.valtypeignore[type(value)] -- skipping ignored value types
214 or sparse and value == nil then -- skipping nils; do nothing
215 elseif ktype == 'table' or ktype == 'function' or badtype[ktype] then
216 if not seen[key] and not globals[key] then
217 sref[#sref+1] = 'placeholder'
218 local sname = safename(iname, gensym(key)) -- iname is table for local variables
219 sref[#sref] = val2str(key,sname,indent,sname,iname,true) end
220 sref[#sref+1] = 'placeholder'
221 local path = seen[t]..'['..tostring(seen[key] or globals[key] or gensym(key))..']'
222 sref[#sref] = path..space..'='..space..tostring(seen[value] or val2str(value,nil,indent,path))
223 else
224 out[#out+1] = val2str(value,key,indent,insref,seen[t],plainindex,level+1)
225 if maxlen then
226 maxlen = maxlen - #out[#out]
227 if maxlen < 0 then break end
228 end
229 end
230 end
231 local prefix = string.rep(indent or '', level)
232 local head = indent and '{\n'..prefix..indent or '{'
233 local body = table.concat(out, ','..(indent and '\n'..prefix..indent or space))
234 local tail = indent and "\n"..prefix..'}' or '}'
235 return (custom and custom(tag,head,body,tail,level) or tag..head..body..tail)..comment(t, level)
236 elseif badtype[ttype] then
237 seen[t] = insref or spath
238 return tag..globerr(t, level)
239 elseif ttype == 'function' then
240 seen[t] = insref or spath
241 if opts.nocode then return tag.."function() --[[..skipped..]] end"..comment(t, level) end
242 local ok, res = pcall(string.dump, t)
243 local func = ok and "((loadstring or load)("..safestr(res)..",'@serialized'))"..comment(t, level)
244 return tag..(func or globerr(t, level))
245 else return tag..safestr(t) end -- handle all other types
246 end
247 local sepr = indent and "\n" or ";"..space
248 local body = val2str(t, name, indent) -- this call also populates sref
249 local tail = #sref>1 and table.concat(sref, sepr)..sepr or ''
250 local warn = opts.comment and #sref>1 and space.."--[[incomplete output with shared/self-references skipped]]" or ''
251 return not name and body..warn or "do local "..body..sepr..tail.."return "..name..sepr.."end"
252end
253
254local function deserialize(data, opts)
255 local env = (opts and opts.safe == false) and G
256 or setmetatable({}, {
257 __index = function(t,k) return t end,
258 __call = function(t,...) error("cannot call functions") end
259 })
260 local f, res = (loadstring or load)('return '..data, nil, nil, env)
261 if not f then f, res = (loadstring or load)(data, nil, nil, env) end
262 if not f then return f, res end
263 if setfenv then setfenv(f, env) end
264 return pcall(f)
265end
266
267local function merge(a, b) if b then for k,v in pairs(b) do a[k] = v end end; return a; end
268return { _NAME = n, _COPYRIGHT = c, _DESCRIPTION = d, _VERSION = v, serialize = s,
269 load = deserialize,
270 dump = function(a, opts) return s(a, merge({name = '_', compact = true, sparse = true}, opts)) end,
271 line = function(a, opts) return s(a, merge({sortkeys = true, comment = true}, opts)) end,
272 block = function(a, opts) return s(a, merge({indent = ' ', sortkeys = true, comment = true}, opts)) end }
273end)() ---- end of Serpent module
274
275mobdebug.line = serpent.line
276mobdebug.dump = serpent.dump
277mobdebug.linemap = nil
278mobdebug.loadstring = loadstring
279
280local function removebasedir(path, basedir)
281 if iscasepreserving then
282 -- check if the lowercased path matches the basedir
283 -- if so, return substring of the original path (to not lowercase it)
284 return path:lower():find('^'..q(basedir:lower()))
285 and path:sub(#basedir+1) or path
286 else
287 return string.gsub(path, '^'..q(basedir), '')
288 end
289end
290
291local function stack(start)
292 local function vars(f)
293 local func = debug.getinfo(f, "f").func
294 local i = 1
295 local locals = {}
296 -- get locals
297 while true do
298 local name, value = debug.getlocal(f, i)
299 if not name then break end
300 if string.sub(name, 1, 1) ~= '(' then
301 locals[name] = {value, select(2,pcall(tostring,value))}
302 end
303 i = i + 1
304 end
305 -- get varargs (these use negative indices)
306 i = 1
307 while true do
308 local name, value = debug.getlocal(f, -i)
309 -- `not name` should be enough, but LuaJIT 2.0.0 incorrectly reports `(*temporary)` names here
310 if not name or name ~= "(*vararg)" then break end
311 locals[name:gsub("%)$"," "..i..")")] = {value, select(2,pcall(tostring,value))}
312 i = i + 1
313 end
314 -- get upvalues
315 i = 1
316 local ups = {}
317 while func do -- check for func as it may be nil for tail calls
318 local name, value = debug.getupvalue(func, i)
319 if not name then break end
320 ups[name] = {value, select(2,pcall(tostring,value))}
321 i = i + 1
322 end
323 return locals, ups
324 end
325
326 local stack = {}
327 local linemap = mobdebug.linemap
328 for i = (start or 0), 100 do
329 local source = debug.getinfo(i, "Snl")
330 if not source then break end
331
332 local src = source.source
333 if src:find("@") == 1 then
334 src = src:sub(2):gsub("\\", "/")
335 if src:find("%./") == 1 then src = src:sub(3) end
336 end
337
338 table.insert(stack, { -- remove basedir from source
339 {source.name, removebasedir(src, basedir),
340 linemap and linemap(source.linedefined, source.source) or source.linedefined,
341 linemap and linemap(source.currentline, source.source) or source.currentline,
342 source.what, source.namewhat, source.short_src},
343 vars(i+1)})
344 if source.what == 'main' then break end
345 end
346 return stack
347end
348
349local function set_breakpoint(file, line)
350 if file == '-' and lastfile then file = lastfile
351 elseif iscasepreserving then file = string.lower(file) end
352 if not breakpoints[line] then breakpoints[line] = {} end
353 breakpoints[line][file] = true
354end
355
356local function remove_breakpoint(file, line)
357 if file == '-' and lastfile then file = lastfile
358 elseif file == '*' and line == 0 then breakpoints = {}
359 elseif iscasepreserving then file = string.lower(file) end
360 if breakpoints[line] then breakpoints[line][file] = nil end
361end
362
363local function has_breakpoint(file, line)
364 return breakpoints[line]
365 and breakpoints[line][iscasepreserving and string.lower(file) or file]
366end
367
368local function restore_vars(vars)
369 if type(vars) ~= 'table' then return end
370
371 -- locals need to be processed in the reverse order, starting from
372 -- the inner block out, to make sure that the localized variables
373 -- are correctly updated with only the closest variable with
374 -- the same name being changed
375 -- first loop find how many local variables there is, while
376 -- the second loop processes them from i to 1
377 local i = 1
378 while true do
379 local name = debug.getlocal(3, i)
380 if not name then break end
381 i = i + 1
382 end
383 i = i - 1
384 local written_vars = {}
385 while i > 0 do
386 local name = debug.getlocal(3, i)
387 if not written_vars[name] then
388 if string.sub(name, 1, 1) ~= '(' then
389 debug.setlocal(3, i, rawget(vars, name))
390 end
391 written_vars[name] = true
392 end
393 i = i - 1
394 end
395
396 i = 1
397 local func = debug.getinfo(3, "f").func
398 while true do
399 local name = debug.getupvalue(func, i)
400 if not name then break end
401 if not written_vars[name] then
402 if string.sub(name, 1, 1) ~= '(' then
403 debug.setupvalue(func, i, rawget(vars, name))
404 end
405 written_vars[name] = true
406 end
407 i = i + 1
408 end
409end
410
411local function capture_vars(level, thread)
412 level = (level or 0)+2 -- add two levels for this and debug calls
413 local func = (thread and debug.getinfo(thread, level, "f") or debug.getinfo(level, "f") or {}).func
414 if not func then return {} end
415
416 local vars = {['...'] = {}}
417 local i = 1
418 while true do
419 local name, value = debug.getupvalue(func, i)
420 if not name then break end
421 if string.sub(name, 1, 1) ~= '(' then vars[name] = value end
422 i = i + 1
423 end
424 i = 1
425 while true do
426 local name, value
427 if thread then
428 name, value = debug.getlocal(thread, level, i)
429 else
430 name, value = debug.getlocal(level, i)
431 end
432 if not name then break end
433 if string.sub(name, 1, 1) ~= '(' then vars[name] = value end
434 i = i + 1
435 end
436 -- get varargs (these use negative indices)
437 i = 1
438 while true do
439 local name, value
440 if thread then
441 name, value = debug.getlocal(thread, level, -i)
442 else
443 name, value = debug.getlocal(level, -i)
444 end
445 -- `not name` should be enough, but LuaJIT 2.0.0 incorrectly reports `(*temporary)` names here
446 if not name or name ~= "(*vararg)" then break end
447 vars['...'][i] = value
448 i = i + 1
449 end
450 -- returned 'vars' table plays a dual role: (1) it captures local values
451 -- and upvalues to be restored later (in case they are modified in "eval"),
452 -- and (2) it provides an environment for evaluated chunks.
453 -- getfenv(func) is needed to provide proper environment for functions,
454 -- including access to globals, but this causes vars[name] to fail in
455 -- restore_vars on local variables or upvalues with `nil` values when
456 -- 'strict' is in effect. To avoid this `rawget` is used in restore_vars.
457 setmetatable(vars, { __index = getfenv(func), __newindex = getfenv(func) })
458 return vars
459end
460
461local function stack_depth(start_depth)
462 for i = start_depth, 0, -1 do
463 if debug.getinfo(i, "l") then return i+1 end
464 end
465 return start_depth
466end
467
468local function is_safe(stack_level)
469 -- the stack grows up: 0 is getinfo, 1 is is_safe, 2 is debug_hook, 3 is user function
470 if stack_level == 3 then return true end
471 for i = 3, stack_level do
472 -- return if it is not safe to abort
473 local info = debug.getinfo(i, "S")
474 if not info then return true end
475 if info.what == "C" then return false end
476 end
477 return true
478end
479
480local function in_debugger()
481 local this = debug.getinfo(1, "S").source
482 -- only need to check few frames as mobdebug frames should be close
483 for i = 3, 7 do
484 local info = debug.getinfo(i, "S")
485 if not info then return false end
486 if info.source == this then return true end
487 end
488 return false
489end
490
491local function is_pending(peer)
492 -- if there is something already in the buffer, skip check
493 if not buf and checkcount >= mobdebug.checkcount then
494 peer:settimeout(0) -- non-blocking
495 buf = peer:receive(1)
496 peer:settimeout() -- back to blocking
497 checkcount = 0
498 end
499 return buf
500end
501
502local function readnext(peer, num)
503 peer:settimeout(0) -- non-blocking
504 local res, err, partial = peer:receive(num)
505 peer:settimeout() -- back to blocking
506 return res or partial or '', err
507end
508
509local function handle_breakpoint(peer)
510 -- check if the buffer has the beginning of SETB/DELB command;
511 -- this is to avoid reading the entire line for commands that
512 -- don't need to be handled here.
513 if not buf or not (buf:sub(1,1) == 'S' or buf:sub(1,1) == 'D') then return end
514
515 -- check second character to avoid reading STEP or other S* and D* commands
516 if #buf == 1 then buf = buf .. readnext(peer, 1) end
517 if buf:sub(2,2) ~= 'E' then return end
518
519 -- need to read few more characters
520 buf = buf .. readnext(peer, 5-#buf)
521 if buf ~= 'SETB ' and buf ~= 'DELB ' then return end
522
523 local res, _, partial = peer:receive() -- get the rest of the line; blocking
524 if not res then
525 if partial then buf = buf .. partial end
526 return
527 end
528
529 local _, _, cmd, file, line = (buf..res):find("^([A-Z]+)%s+(.-)%s+(%d+)%s*$")
530 if cmd == 'SETB' then set_breakpoint(file, tonumber(line))
531 elseif cmd == 'DELB' then remove_breakpoint(file, tonumber(line))
532 else
533 -- this looks like a breakpoint command, but something went wrong;
534 -- return here to let the "normal" processing to handle,
535 -- although this is likely to not go well.
536 return
537 end
538
539 buf = nil
540end
541
542local function normalize_path(file)
543 local n
544 repeat
545 file, n = file:gsub("/+%.?/+","/") -- remove all `//` and `/./` references
546 until n == 0
547 -- collapse all up-dir references: this will clobber UNC prefix (\\?\)
548 -- and disk on Windows when there are too many up-dir references: `D:\foo\..\..\bar`;
549 -- handle the case of multiple up-dir references: `foo/bar/baz/../../../more`;
550 -- only remove one at a time as otherwise `../../` could be removed;
551 repeat
552 file, n = file:gsub("[^/]+/%.%./", "", 1)
553 until n == 0
554 -- there may still be a leading up-dir reference left (as `/../` or `../`); remove it
555 return (file:gsub("^(/?)%.%./", "%1"))
556end
557
558local function debug_hook(event, line)
559 -- (1) LuaJIT needs special treatment. Because debug_hook is set for
560 -- *all* coroutines, and not just the one being debugged as in regular Lua
561 -- (http://lua-users.org/lists/lua-l/2011-06/msg00513.html),
562 -- need to avoid debugging mobdebug's own code as LuaJIT doesn't
563 -- always correctly generate call/return hook events (there are more
564 -- calls than returns, which breaks stack depth calculation and
565 -- 'step' and 'step over' commands stop working; possibly because
566 -- 'tail return' events are not generated by LuaJIT).
567 -- the next line checks if the debugger is run under LuaJIT and if
568 -- one of debugger methods is present in the stack, it simply returns.
569 if jit then
570 -- when luajit is compiled with LUAJIT_ENABLE_LUA52COMPAT,
571 -- coroutine.running() returns non-nil for the main thread.
572 local coro, main = coroutine.running()
573 if not coro or main then coro = 'main' end
574 local disabled = coroutines[coro] == false
575 or coroutines[coro] == nil and coro ~= (coro_debugee or 'main')
576 if coro_debugee and disabled or not coro_debugee and (disabled or in_debugger())
577 then return end
578 end
579
580 -- (2) check if abort has been requested and it's safe to abort
581 if abort and is_safe(stack_level) then error(abort) end
582
583 -- (3) also check if this debug hook has not been visited for any reason.
584 -- this check is needed to avoid stepping in too early
585 -- (for example, when coroutine.resume() is executed inside start()).
586 if not seen_hook and in_debugger() then return end
587
588 if event == "call" then
589 stack_level = stack_level + 1
590 elseif event == "return" or event == "tail return" then
591 stack_level = stack_level - 1
592 elseif event == "line" then
593 if mobdebug.linemap then
594 local ok, mappedline = pcall(mobdebug.linemap, line, debug.getinfo(2, "S").source)
595 if ok then line = mappedline end
596 if not line then return end
597 end
598
599 -- may need to fall through because of the following:
600 -- (1) step_into
601 -- (2) step_over and stack_level <= step_level (need stack_level)
602 -- (3) breakpoint; check for line first as it's known; then for file
603 -- (4) socket call (only do every Xth check)
604 -- (5) at least one watch is registered
605 if not (
606 step_into or step_over or breakpoints[line] or watchescnt > 0
607 or is_pending(server)
608 ) then checkcount = checkcount + 1; return end
609
610 checkcount = mobdebug.checkcount -- force check on the next command
611
612 -- this is needed to check if the stack got shorter or longer.
613 -- unfortunately counting call/return calls is not reliable.
614 -- the discrepancy may happen when "pcall(load, '')" call is made
615 -- or when "error()" is called in a function.
616 -- in either case there are more "call" than "return" events reported.
617 -- this validation is done for every "line" event, but should be "cheap"
618 -- as it checks for the stack to get shorter (or longer by one call).
619 -- start from one level higher just in case we need to grow the stack.
620 -- this may happen after coroutine.resume call to a function that doesn't
621 -- have any other instructions to execute. it triggers three returns:
622 -- "return, tail return, return", which needs to be accounted for.
623 stack_level = stack_depth(stack_level+1)
624
625 local caller = debug.getinfo(2, "S")
626
627 -- grab the filename and fix it if needed
628 local file = lastfile
629 if (lastsource ~= caller.source) then
630 file, lastsource = caller.source, caller.source
631 -- technically, users can supply names that may not use '@',
632 -- for example when they call loadstring('...', 'filename.lua').
633 -- Unfortunately, there is no reliable/quick way to figure out
634 -- what is the filename and what is the source code.
635 -- If the name doesn't start with `@`, assume it's a file name if it's all on one line.
636 if find(file, "^@") or not find(file, "[\r\n]") then
637 file = gsub(gsub(file, "^@", ""), "\\", "/")
638 -- normalize paths that may include up-dir or same-dir references
639 -- if the path starts from the up-dir or reference,
640 -- prepend `basedir` to generate absolute path to keep breakpoints working.
641 -- ignore qualified relative path (`D:../`) and UNC paths (`\\?\`)
642 if find(file, "^%.%./") then file = basedir..file end
643 if find(file, "/%.%.?/") then file = normalize_path(file) end
644 -- need this conversion to be applied to relative and absolute
645 -- file names as you may write "require 'Foo'" to
646 -- load "foo.lua" (on a case insensitive file system) and breakpoints
647 -- set on foo.lua will not work if not converted to the same case.
648 if iscasepreserving then file = string.lower(file) end
649 if find(file, "^%./") then file = sub(file, 3)
650 else file = gsub(file, "^"..q(basedir), "") end
651 -- some file systems allow newlines in file names; remove these.
652 file = gsub(file, "\n", ' ')
653 else
654 file = mobdebug.line(file)
655 end
656
657 -- set to true if we got here; this only needs to be done once per
658 -- session, so do it here to at least avoid setting it for every line.
659 seen_hook = true
660 lastfile = file
661 end
662
663 if is_pending(server) then handle_breakpoint(server) end
664
665 local vars, status, res
666 if (watchescnt > 0) then
667 vars = capture_vars(1)
668 for index, value in pairs(watches) do
669 setfenv(value, vars)
670 local ok, fired = pcall(value)
671 if ok and fired then
672 status, res = cororesume(coro_debugger, events.WATCH, vars, file, line, index)
673 break -- any one watch is enough; don't check multiple times
674 end
675 end
676 end
677
678 -- need to get into the "regular" debug handler, but only if there was
679 -- no watch that was fired. If there was a watch, handle its result.
680 local getin = (status == nil) and
681 (step_into
682 -- when coroutine.running() return `nil` (main thread in Lua 5.1),
683 -- step_over will equal 'main', so need to check for that explicitly.
684 or (step_over and step_over == (coroutine.running() or 'main') and stack_level <= step_level)
685 or has_breakpoint(file, line)
686 or is_pending(server))
687
688 if getin then
689 vars = vars or capture_vars(1)
690 step_into = false
691 step_over = false
692 status, res = cororesume(coro_debugger, events.BREAK, vars, file, line)
693 end
694
695 -- handle 'stack' command that provides stack() information to the debugger
696 while status and res == 'stack' do
697 -- resume with the stack trace and variables
698 if vars then restore_vars(vars) end -- restore vars so they are reflected in stack values
699 status, res = cororesume(coro_debugger, events.STACK, stack(3), file, line)
700 end
701
702 -- need to recheck once more as resume after 'stack' command may
703 -- return something else (for example, 'exit'), which needs to be handled
704 if status and res and res ~= 'stack' then
705 if not abort and res == "exit" then mobdebug.onexit(1, true); return end
706 if not abort and res == "done" then mobdebug.done(); return end
707 abort = res
708 -- only abort if safe; if not, there is another (earlier) check inside
709 -- debug_hook, which will abort execution at the first safe opportunity
710 if is_safe(stack_level) then error(abort) end
711 elseif not status and res then
712 error(res, 2) -- report any other (internal) errors back to the application
713 end
714
715 if vars then restore_vars(vars) end
716
717 -- last command requested Step Over/Out; store the current thread
718 if step_over == true then step_over = coroutine.running() or 'main' end
719 end
720end
721
722local function stringify_results(params, status, ...)
723 if not status then return status, ... end -- on error report as it
724
725 params = params or {}
726 if params.nocode == nil then params.nocode = true end
727 if params.comment == nil then params.comment = 1 end
728
729 local t = {...}
730 for i,v in pairs(t) do -- stringify each of the returned values
731 local ok, res = pcall(mobdebug.line, v, params)
732 t[i] = ok and res or ("%q"):format(res):gsub("\010","n"):gsub("\026","\\026")
733 end
734 -- stringify table with all returned values
735 -- this is done to allow each returned value to be used (serialized or not)
736 -- intependently and to preserve "original" comments
737 return pcall(mobdebug.dump, t, {sparse = false})
738end
739
740local function isrunning()
741 return coro_debugger and (corostatus(coro_debugger) == 'suspended' or corostatus(coro_debugger) == 'running')
742end
743
744-- this is a function that removes all hooks and closes the socket to
745-- report back to the controller that the debugging is done.
746-- the script that called `done` can still continue.
747local function done()
748 if not (isrunning() and server) then return end
749
750 if not jit then
751 for co, debugged in pairs(coroutines) do
752 if debugged then debug.sethook(co) end
753 end
754 end
755
756 debug.sethook()
757 server:close()
758
759 coro_debugger = nil -- to make sure isrunning() returns `false`
760 seen_hook = nil -- to make sure that the next start() call works
761 abort = nil -- to make sure that callback calls use proper "abort" value
762end
763
764local function debugger_loop(sev, svars, sfile, sline)
765 local command
766 local app, osname
767 local eval_env = svars or {}
768 local function emptyWatch () return false end
769 local loaded = {}
770 for k in pairs(package.loaded) do loaded[k] = true end
771
772 while true do
773 local line, err
774 local wx = rawget(genv, "wx") -- use rawread to make strict.lua happy
775 if (wx or mobdebug.yield) and server.settimeout then server:settimeout(mobdebug.yieldtimeout) end
776 while true do
777 line, err = server:receive()
778 if not line and err == "timeout" then
779 -- yield for wx GUI applications if possible to avoid "busyness"
780 app = app or (wx and wx.wxGetApp and wx.wxGetApp())
781 if app then
782 local win = app:GetTopWindow()
783 local inloop = app:IsMainLoopRunning()
784 osname = osname or wx.wxPlatformInfo.Get():GetOperatingSystemFamilyName()
785 if win and not inloop then
786 -- process messages in a regular way
787 -- and exit as soon as the event loop is idle
788 if osname == 'Unix' then wx.wxTimer(app):Start(10, true) end
789 local exitLoop = function()
790 win:Disconnect(wx.wxID_ANY, wx.wxID_ANY, wx.wxEVT_IDLE)
791 win:Disconnect(wx.wxID_ANY, wx.wxID_ANY, wx.wxEVT_TIMER)
792 app:ExitMainLoop()
793 end
794 win:Connect(wx.wxEVT_IDLE, exitLoop)
795 win:Connect(wx.wxEVT_TIMER, exitLoop)
796 app:MainLoop()
797 end
798 elseif mobdebug.yield then mobdebug.yield()
799 end
800 elseif not line and err == "closed" then
801 error("Debugger connection closed", 0)
802 else
803 -- if there is something in the pending buffer, prepend it to the line
804 if buf then line = buf .. line; buf = nil end
805 break
806 end
807 end
808 if server.settimeout then server:settimeout() end -- back to blocking
809 command = string.sub(line, string.find(line, "^[A-Z]+"))
810 if command == "SETB" then
811 local _, _, _, file, line = string.find(line, "^([A-Z]+)%s+(.-)%s+(%d+)%s*$")
812 if file and line then
813 set_breakpoint(file, tonumber(line))
814 server:send("200 OK\n")
815 else
816 server:send("400 Bad Request\n")
817 end
818 elseif command == "DELB" then
819 local _, _, _, file, line = string.find(line, "^([A-Z]+)%s+(.-)%s+(%d+)%s*$")
820 if file and line then
821 remove_breakpoint(file, tonumber(line))
822 server:send("200 OK\n")
823 else
824 server:send("400 Bad Request\n")
825 end
826 elseif command == "EXEC" then
827 -- extract any optional parameters
828 local params = string.match(line, "--%s*(%b{})%s*$")
829 local _, _, chunk = string.find(line, "^[A-Z]+%s+(.+)$")
830 if chunk then
831 local func, res = mobdebug.loadstring(chunk)
832 local status
833 if func then
834 local pfunc = params and loadstring("return "..params) -- use internal function
835 params = pfunc and pfunc()
836 params = (type(params) == "table" and params or {})
837 local stack = tonumber(params.stack)
838 -- if the requested stack frame is not the current one, then use a new capture
839 -- with a specific stack frame: `capture_vars(0, coro_debugee)`
840 local env = stack and coro_debugee and capture_vars(stack-1, coro_debugee) or eval_env
841 setfenv(func, env)
842 status, res = stringify_results(params, pcall(func, unpack(env['...'] or {})))
843 end
844 if status then
845 if mobdebug.onscratch then mobdebug.onscratch(res) end
846 server:send("200 OK " .. tostring(#res) .. "\n")
847 server:send(res)
848 else
849 -- fix error if not set (for example, when loadstring is not present)
850 if not res then res = "Unknown error" end
851 server:send("401 Error in Expression " .. tostring(#res) .. "\n")
852 server:send(res)
853 end
854 else
855 server:send("400 Bad Request\n")
856 end
857 elseif command == "LOAD" then
858 local _, _, size, name = string.find(line, "^[A-Z]+%s+(%d+)%s+(%S.-)%s*$")
859 size = tonumber(size)
860
861 if abort == nil then -- no LOAD/RELOAD allowed inside start()
862 if size > 0 then server:receive(size) end
863 if sfile and sline then
864 server:send("201 Started " .. sfile .. " " .. tostring(sline) .. "\n")
865 else
866 server:send("200 OK 0\n")
867 end
868 else
869 -- reset environment to allow required modules to load again
870 -- remove those packages that weren't loaded when debugger started
871 for k in pairs(package.loaded) do
872 if not loaded[k] then package.loaded[k] = nil end
873 end
874
875 if size == 0 and name == '-' then -- RELOAD the current script being debugged
876 server:send("200 OK 0\n")
877 coroyield("load")
878 else
879 -- receiving 0 bytes blocks (at least in luasocket 2.0.2), so skip reading
880 local chunk = size == 0 and "" or server:receive(size)
881 if chunk then -- LOAD a new script for debugging
882 local func, res = mobdebug.loadstring(chunk, "@"..name)
883 if func then
884 server:send("200 OK 0\n")
885 debugee = func
886 coroyield("load")
887 else
888 server:send("401 Error in Expression " .. tostring(#res) .. "\n")
889 server:send(res)
890 end
891 else
892 server:send("400 Bad Request\n")
893 end
894 end
895 end
896 elseif command == "SETW" then
897 local _, _, exp = string.find(line, "^[A-Z]+%s+(.+)%s*$")
898 if exp then
899 local func, res = mobdebug.loadstring("return(" .. exp .. ")")
900 if func then
901 watchescnt = watchescnt + 1
902 local newidx = #watches + 1
903 watches[newidx] = func
904 server:send("200 OK " .. tostring(newidx) .. "\n")
905 else
906 server:send("401 Error in Expression " .. tostring(#res) .. "\n")
907 server:send(res)
908 end
909 else
910 server:send("400 Bad Request\n")
911 end
912 elseif command == "DELW" then
913 local _, _, index = string.find(line, "^[A-Z]+%s+(%d+)%s*$")
914 index = tonumber(index)
915 if index > 0 and index <= #watches then
916 watchescnt = watchescnt - (watches[index] ~= emptyWatch and 1 or 0)
917 watches[index] = emptyWatch
918 server:send("200 OK\n")
919 else
920 server:send("400 Bad Request\n")
921 end
922 elseif command == "RUN" then
923 server:send("200 OK\n")
924
925 local ev, vars, file, line, idx_watch = coroyield()
926 eval_env = vars
927 if ev == events.BREAK then
928 server:send("202 Paused " .. file .. " " .. tostring(line) .. "\n")
929 elseif ev == events.WATCH then
930 server:send("203 Paused " .. file .. " " .. tostring(line) .. " " .. tostring(idx_watch) .. "\n")
931 elseif ev == events.RESTART then
932 -- nothing to do
933 else
934 server:send("401 Error in Execution " .. tostring(#file) .. "\n")
935 server:send(file)
936 end
937 elseif command == "STEP" then
938 server:send("200 OK\n")
939 step_into = true
940
941 local ev, vars, file, line, idx_watch = coroyield()
942 eval_env = vars
943 if ev == events.BREAK then
944 server:send("202 Paused " .. file .. " " .. tostring(line) .. "\n")
945 elseif ev == events.WATCH then
946 server:send("203 Paused " .. file .. " " .. tostring(line) .. " " .. tostring(idx_watch) .. "\n")
947 elseif ev == events.RESTART then
948 -- nothing to do
949 else
950 server:send("401 Error in Execution " .. tostring(#file) .. "\n")
951 server:send(file)
952 end
953 elseif command == "OVER" or command == "OUT" then
954 server:send("200 OK\n")
955 step_over = true
956
957 -- OVER and OUT are very similar except for
958 -- the stack level value at which to stop
959 if command == "OUT" then step_level = stack_level - 1
960 else step_level = stack_level end
961
962 local ev, vars, file, line, idx_watch = coroyield()
963 eval_env = vars
964 if ev == events.BREAK then
965 server:send("202 Paused " .. file .. " " .. tostring(line) .. "\n")
966 elseif ev == events.WATCH then
967 server:send("203 Paused " .. file .. " " .. tostring(line) .. " " .. tostring(idx_watch) .. "\n")
968 elseif ev == events.RESTART then
969 -- nothing to do
970 else
971 server:send("401 Error in Execution " .. tostring(#file) .. "\n")
972 server:send(file)
973 end
974 elseif command == "BASEDIR" then
975 local _, _, dir = string.find(line, "^[A-Z]+%s+(.+)%s*$")
976 if dir then
977 basedir = iscasepreserving and string.lower(dir) or dir
978 -- reset cached source as it may change with basedir
979 lastsource = nil
980 server:send("200 OK\n")
981 else
982 server:send("400 Bad Request\n")
983 end
984 elseif command == "SUSPEND" then
985 -- do nothing; it already fulfilled its role
986 elseif command == "DONE" then
987 coroyield("done")
988 return -- done with all the debugging
989 elseif command == "STACK" then
990 -- first check if we can execute the stack command
991 -- as it requires yielding back to debug_hook it cannot be executed
992 -- if we have not seen the hook yet as happens after start().
993 -- in this case we simply return an empty result
994 local vars, ev = {}
995 if seen_hook then
996 ev, vars = coroyield("stack")
997 end
998 if ev and ev ~= events.STACK then
999 server:send("401 Error in Execution " .. tostring(#vars) .. "\n")
1000 server:send(vars)
1001 else
1002 local params = string.match(line, "--%s*(%b{})%s*$")
1003 local pfunc = params and loadstring("return "..params) -- use internal function
1004 params = pfunc and pfunc()
1005 params = (type(params) == "table" and params or {})
1006 if params.nocode == nil then params.nocode = true end
1007 if params.sparse == nil then params.sparse = false end
1008 -- take into account additional levels for the stack frames and data management
1009 if tonumber(params.maxlevel) then params.maxlevel = tonumber(params.maxlevel)+4 end
1010
1011 local ok, res = pcall(mobdebug.dump, vars, params)
1012 if ok then
1013 server:send("200 OK " .. tostring(res) .. "\n")
1014 else
1015 server:send("401 Error in Execution " .. tostring(#res) .. "\n")
1016 server:send(res)
1017 end
1018 end
1019 elseif command == "OUTPUT" then
1020 local _, _, stream, mode = string.find(line, "^[A-Z]+%s+(%w+)%s+([dcr])%s*$")
1021 if stream and mode and stream == "stdout" then
1022 -- assign "print" in the global environment
1023 local default = mode == 'd'
1024 genv.print = default and iobase.print or corowrap(function()
1025 -- wrapping into coroutine.wrap protects this function from
1026 -- being stepped through in the debugger.
1027 -- don't use vararg (...) as it adds a reference for its values,
1028 -- which may affect how they are garbage collected
1029 while true do
1030 local tbl = {coroutine.yield()}
1031 if mode == 'c' then iobase.print(unpack(tbl)) end
1032 for n = 1, #tbl do
1033 tbl[n] = select(2, pcall(mobdebug.line, tbl[n], {nocode = true, comment = false})) end
1034 local file = table.concat(tbl, "\t").."\n"
1035 server:send("204 Output " .. stream .. " " .. tostring(#file) .. "\n" .. file)
1036 end
1037 end)
1038 if not default then genv.print() end -- "fake" print to start printing loop
1039 server:send("200 OK\n")
1040 else
1041 server:send("400 Bad Request\n")
1042 end
1043 elseif command == "EXIT" then
1044 server:send("200 OK\n")
1045 coroyield("exit")
1046 else
1047 server:send("400 Bad Request\n")
1048 end
1049 end
1050end
1051
1052local function output(stream, data)
1053 if server then return server:send("204 Output "..stream.." "..tostring(#data).."\n"..data) end
1054end
1055
1056local function connect(controller_host, controller_port)
1057 local sock, err = socket.tcp()
1058 if not sock then return nil, err end
1059
1060 if sock.settimeout then sock:settimeout(mobdebug.connecttimeout) end
1061 local res, err = sock:connect(controller_host, tostring(controller_port))
1062 if sock.settimeout then sock:settimeout() end
1063
1064 if not res then return nil, err end
1065 return sock
1066end
1067
1068local lasthost, lastport
1069
1070-- Starts a debug session by connecting to a controller
1071local function start(controller_host, controller_port)
1072 -- only one debugging session can be run (as there is only one debug hook)
1073 if isrunning() then return end
1074
1075 lasthost = controller_host or lasthost
1076 lastport = controller_port or lastport
1077
1078 controller_host = lasthost or "localhost"
1079 controller_port = lastport or mobdebug.port
1080
1081 local err
1082 server, err = mobdebug.connect(controller_host, controller_port)
1083 if server then
1084 -- correct stack depth which already has some calls on it
1085 -- so it doesn't go into negative when those calls return
1086 -- as this breaks subsequence checks in stack_depth().
1087 -- start from 16th frame, which is sufficiently large for this check.
1088 stack_level = stack_depth(16)
1089
1090 -- provide our own traceback function to report errors remotely
1091 -- but only under Lua 5.1/LuaJIT as it's not called under Lua 5.2+
1092 -- (http://lua-users.org/lists/lua-l/2016-05/msg00297.html)
1093 local function f() return function()end end
1094 if f() ~= f() then -- Lua 5.1 or LuaJIT
1095 local dtraceback = debug.traceback
1096 debug.traceback = function (...)
1097 if select('#', ...) >= 1 then
1098 local thr, err, lvl = ...
1099 if type(thr) ~= 'thread' then err, lvl = thr, err end
1100 local trace = dtraceback(err, (lvl or 1)+1)
1101 if genv.print == iobase.print then -- no remote redirect
1102 return trace
1103 else
1104 genv.print(trace) -- report the error remotely
1105 return -- don't report locally to avoid double reporting
1106 end
1107 end
1108 -- direct call to debug.traceback: return the original.
1109 -- debug.traceback(nil, level) doesn't work in Lua 5.1
1110 -- (http://lua-users.org/lists/lua-l/2011-06/msg00574.html), so
1111 -- simply remove first frame from the stack trace
1112 local tb = dtraceback("", 2) -- skip debugger frames
1113 -- if the string is returned, then remove the first new line as it's not needed
1114 return type(tb) == "string" and tb:gsub("^\n","") or tb
1115 end
1116 end
1117 coro_debugger = corocreate(debugger_loop)
1118 debug.sethook(debug_hook, HOOKMASK)
1119 seen_hook = nil -- reset in case the last start() call was refused
1120 step_into = true -- start with step command
1121 return true
1122 else
1123 print(("Could not connect to %s:%s: %s")
1124 :format(controller_host, controller_port, err or "unknown error"))
1125 end
1126end
1127
1128local function controller(controller_host, controller_port, scratchpad)
1129 -- only one debugging session can be run (as there is only one debug hook)
1130 if isrunning() then return end
1131
1132 lasthost = controller_host or lasthost
1133 lastport = controller_port or lastport
1134
1135 controller_host = lasthost or "localhost"
1136 controller_port = lastport or mobdebug.port
1137
1138 local exitonerror = not scratchpad
1139 local err
1140 server, err = mobdebug.connect(controller_host, controller_port)
1141 if server then
1142 local function report(trace, err)
1143 local msg = err .. "\n" .. trace
1144 server:send("401 Error in Execution " .. tostring(#msg) .. "\n")
1145 server:send(msg)
1146 return err
1147 end
1148
1149 seen_hook = true -- allow to accept all commands
1150 coro_debugger = corocreate(debugger_loop)
1151
1152 while true do
1153 step_into = true -- start with step command
1154 abort = false -- reset abort flag from the previous loop
1155 if scratchpad then checkcount = mobdebug.checkcount end -- force suspend right away
1156
1157 coro_debugee = corocreate(debugee)
1158 debug.sethook(coro_debugee, debug_hook, HOOKMASK)
1159 local status, err = cororesume(coro_debugee, unpack(arg or {}))
1160
1161 -- was there an error or is the script done?
1162 -- 'abort' state is allowed here; ignore it
1163 if abort then
1164 if tostring(abort) == 'exit' then break end
1165 else
1166 if status then -- no errors
1167 if corostatus(coro_debugee) == "suspended" then
1168 -- the script called `coroutine.yield` in the "main" thread
1169 error("attempt to yield from the main thread", 3)
1170 end
1171 break -- normal execution is done
1172 elseif err and not string.find(tostring(err), deferror) then
1173 -- report the error back
1174 -- err is not necessarily a string, so convert to string to report
1175 report(debug.traceback(coro_debugee), tostring(err))
1176 if exitonerror then break end
1177 -- check if the debugging is done (coro_debugger is nil)
1178 if not coro_debugger then break end
1179 -- resume once more to clear the response the debugger wants to send
1180 -- need to use capture_vars(0) to capture only two (default) level,
1181 -- as even though there is controller() call, because of the tail call,
1182 -- the caller may not exist for it;
1183 -- This is not entirely safe as the user may see the local
1184 -- variable from console, but they will be reset anyway.
1185 -- This functionality is used when scratchpad is paused to
1186 -- gain access to remote console to modify global variables.
1187 local status, err = cororesume(coro_debugger, events.RESTART, capture_vars(0))
1188 if not status or status and err == "exit" then break end
1189 end
1190 end
1191 end
1192 else
1193 print(("Could not connect to %s:%s: %s")
1194 :format(controller_host, controller_port, err or "unknown error"))
1195 return false
1196 end
1197 return true
1198end
1199
1200local function scratchpad(controller_host, controller_port)
1201 return controller(controller_host, controller_port, true)
1202end
1203
1204local function loop(controller_host, controller_port)
1205 return controller(controller_host, controller_port, false)
1206end
1207
1208local function on()
1209 if not (isrunning() and server) then return end
1210
1211 -- main is set to true under Lua5.2 for the "main" chunk.
1212 -- Lua5.1 returns co as `nil` in that case.
1213 local co, main = coroutine.running()
1214 if main then co = nil end
1215 if co then
1216 coroutines[co] = true
1217 debug.sethook(co, debug_hook, HOOKMASK)
1218 else
1219 if jit then coroutines.main = true end
1220 debug.sethook(debug_hook, HOOKMASK)
1221 end
1222end
1223
1224local function off()
1225 if not (isrunning() and server) then return end
1226
1227 -- main is set to true under Lua5.2 for the "main" chunk.
1228 -- Lua5.1 returns co as `nil` in that case.
1229 local co, main = coroutine.running()
1230 if main then co = nil end
1231
1232 -- don't remove coroutine hook under LuaJIT as there is only one (global) hook
1233 if co then
1234 coroutines[co] = false
1235 if not jit then debug.sethook(co) end
1236 else
1237 if jit then coroutines.main = false end
1238 if not jit then debug.sethook() end
1239 end
1240
1241 -- check if there is any thread that is still being debugged under LuaJIT;
1242 -- if not, turn the debugging off
1243 if jit then
1244 local remove = true
1245 for _, debugged in pairs(coroutines) do
1246 if debugged then remove = false; break end
1247 end
1248 if remove then debug.sethook() end
1249 end
1250end
1251
1252-- Handles server debugging commands
1253local function handle(params, client, options)
1254 -- when `options.verbose` is not provided, use normal `print`; verbose output can be
1255 -- disabled (`options.verbose == false`) or redirected (`options.verbose == function()...end`)
1256 local verbose = not options or options.verbose ~= nil and options.verbose
1257 local print = verbose and (type(verbose) == "function" and verbose or print) or function() end
1258 local file, line, watch_idx
1259 local _, _, command = string.find(params, "^([a-z]+)")
1260 if command == "run" or command == "step" or command == "out"
1261 or command == "over" or command == "exit" then
1262 client:send(string.upper(command) .. "\n")
1263 client:receive() -- this should consume the first '200 OK' response
1264 while true do
1265 local done = true
1266 local breakpoint = client:receive()
1267 if not breakpoint then
1268 print("Program finished")
1269 return nil, nil, false
1270 end
1271 local _, _, status = string.find(breakpoint, "^(%d+)")
1272 if status == "200" then
1273 -- don't need to do anything
1274 elseif status == "202" then
1275 _, _, file, line = string.find(breakpoint, "^202 Paused%s+(.-)%s+(%d+)%s*$")
1276 if file and line then
1277 print("Paused at file " .. file .. " line " .. line)
1278 end
1279 elseif status == "203" then
1280 _, _, file, line, watch_idx = string.find(breakpoint, "^203 Paused%s+(.-)%s+(%d+)%s+(%d+)%s*$")
1281 if file and line and watch_idx then
1282 print("Paused at file " .. file .. " line " .. line .. " (watch expression " .. watch_idx .. ": [" .. watches[watch_idx] .. "])")
1283 end
1284 elseif status == "204" then
1285 local _, _, stream, size = string.find(breakpoint, "^204 Output (%w+) (%d+)$")
1286 if stream and size then
1287 local size = tonumber(size)
1288 local msg = size > 0 and client:receive(size) or ""
1289 print(msg)
1290 if outputs[stream] then outputs[stream](msg) end
1291 -- this was just the output, so go back reading the response
1292 done = false
1293 end
1294 elseif status == "401" then
1295 local _, _, size = string.find(breakpoint, "^401 Error in Execution (%d+)$")
1296 if size then
1297 local msg = client:receive(tonumber(size))
1298 print("Error in remote application: " .. msg)
1299 return nil, nil, msg
1300 end
1301 else
1302 print("Unknown error")
1303 return nil, nil, "Debugger error: unexpected response '" .. breakpoint .. "'"
1304 end
1305 if done then break end
1306 end
1307 elseif command == "done" then
1308 client:send(string.upper(command) .. "\n")
1309 -- no response is expected
1310 elseif command == "setb" or command == "asetb" then
1311 _, _, _, file, line = string.find(params, "^([a-z]+)%s+(.-)%s+(%d+)%s*$")
1312 if file and line then
1313 -- if this is a file name, and not a file source
1314 if not file:find('^".*"$') then
1315 file = string.gsub(file, "\\", "/") -- convert slash
1316 file = removebasedir(file, basedir)
1317 end
1318 client:send("SETB " .. file .. " " .. line .. "\n")
1319 if command == "asetb" or client:receive() == "200 OK" then
1320 set_breakpoint(file, line)
1321 else
1322 print("Error: breakpoint not inserted")
1323 end
1324 else
1325 print("Invalid command")
1326 end
1327 elseif command == "setw" then
1328 local _, _, exp = string.find(params, "^[a-z]+%s+(.+)$")
1329 if exp then
1330 client:send("SETW " .. exp .. "\n")
1331 local answer = client:receive()
1332 local _, _, watch_idx = string.find(answer, "^200 OK (%d+)%s*$")
1333 if watch_idx then
1334 watches[watch_idx] = exp
1335 print("Inserted watch exp no. " .. watch_idx)
1336 else
1337 local _, _, size = string.find(answer, "^401 Error in Expression (%d+)$")
1338 if size then
1339 local err = client:receive(tonumber(size)):gsub(".-:%d+:%s*","")
1340 print("Error: watch expression not set: " .. err)
1341 else
1342 print("Error: watch expression not set")
1343 end
1344 end
1345 else
1346 print("Invalid command")
1347 end
1348 elseif command == "delb" or command == "adelb" then
1349 _, _, _, file, line = string.find(params, "^([a-z]+)%s+(.-)%s+(%d+)%s*$")
1350 if file and line then
1351 -- if this is a file name, and not a file source
1352 if not file:find('^".*"$') then
1353 file = string.gsub(file, "\\", "/") -- convert slash
1354 file = removebasedir(file, basedir)
1355 end
1356 client:send("DELB " .. file .. " " .. line .. "\n")
1357 if command == "adelb" or client:receive() == "200 OK" then
1358 remove_breakpoint(file, line)
1359 else
1360 print("Error: breakpoint not removed")
1361 end
1362 else
1363 print("Invalid command")
1364 end
1365 elseif command == "delallb" then
1366 local file, line = "*", 0
1367 client:send("DELB " .. file .. " " .. tostring(line) .. "\n")
1368 if client:receive() == "200 OK" then
1369 remove_breakpoint(file, line)
1370 else
1371 print("Error: all breakpoints not removed")
1372 end
1373 elseif command == "delw" then
1374 local _, _, index = string.find(params, "^[a-z]+%s+(%d+)%s*$")
1375 if index then
1376 client:send("DELW " .. index .. "\n")
1377 if client:receive() == "200 OK" then
1378 watches[index] = nil
1379 else
1380 print("Error: watch expression not removed")
1381 end
1382 else
1383 print("Invalid command")
1384 end
1385 elseif command == "delallw" then
1386 for index, exp in pairs(watches) do
1387 client:send("DELW " .. index .. "\n")
1388 if client:receive() == "200 OK" then
1389 watches[index] = nil
1390 else
1391 print("Error: watch expression at index " .. index .. " [" .. exp .. "] not removed")
1392 end
1393 end
1394 elseif command == "eval" or command == "exec"
1395 or command == "load" or command == "loadstring"
1396 or command == "reload" then
1397 local _, _, exp = string.find(params, "^[a-z]+%s+(.+)$")
1398 if exp or (command == "reload") then
1399 if command == "eval" or command == "exec" then
1400 exp = (exp:gsub("%-%-%[(=*)%[.-%]%1%]", "") -- remove comments
1401 :gsub("%-%-.-\n", " ") -- remove line comments
1402 :gsub("\n", " ")) -- convert new lines
1403 if command == "eval" then exp = "return " .. exp end
1404 client:send("EXEC " .. exp .. "\n")
1405 elseif command == "reload" then
1406 client:send("LOAD 0 -\n")
1407 elseif command == "loadstring" then
1408 local _, _, _, file, lines = string.find(exp, "^([\"'])(.-)%1%s(.+)")
1409 if not file then
1410 _, _, file, lines = string.find(exp, "^(%S+)%s(.+)")
1411 end
1412 client:send("LOAD " .. tostring(#lines) .. " " .. file .. "\n")
1413 client:send(lines)
1414 else
1415 local file = io.open(exp, "r")
1416 if not file and pcall(require, "winapi") then
1417 -- if file is not open and winapi is there, try with a short path;
1418 -- this may be needed for unicode paths on windows
1419 winapi.set_encoding(winapi.CP_UTF8)
1420 local shortp = winapi.short_path(exp)
1421 file = shortp and io.open(shortp, "r")
1422 end
1423 if not file then return nil, nil, "Cannot open file " .. exp end
1424 -- read the file and remove the shebang line as it causes a compilation error
1425 local lines = file:read("*all"):gsub("^#!.-\n", "\n")
1426 file:close()
1427
1428 local file = string.gsub(exp, "\\", "/") -- convert slash
1429 file = removebasedir(file, basedir)
1430 client:send("LOAD " .. tostring(#lines) .. " " .. file .. "\n")
1431 if #lines > 0 then client:send(lines) end
1432 end
1433 while true do
1434 local params, err = client:receive()
1435 if not params then
1436 return nil, nil, "Debugger connection " .. (err or "error")
1437 end
1438 local done = true
1439 local _, _, status, len = string.find(params, "^(%d+).-%s+(%d+)%s*$")
1440 if status == "200" then
1441 len = tonumber(len)
1442 if len > 0 then
1443 local status, res
1444 local str = client:receive(len)
1445 -- handle serialized table with results
1446 local func, err = loadstring(str)
1447 if func then
1448 status, res = pcall(func)
1449 if not status then err = res
1450 elseif type(res) ~= "table" then
1451 err = "received "..type(res).." instead of expected 'table'"
1452 end
1453 end
1454 if err then
1455 print("Error in processing results: " .. err)
1456 return nil, nil, "Error in processing results: " .. err
1457 end
1458 print(unpack(res))
1459 return res[1], res
1460 end
1461 elseif status == "201" then
1462 _, _, file, line = string.find(params, "^201 Started%s+(.-)%s+(%d+)%s*$")
1463 elseif status == "202" or params == "200 OK" then
1464 -- do nothing; this only happens when RE/LOAD command gets the response
1465 -- that was for the original command that was aborted
1466 elseif status == "204" then
1467 local _, _, stream, size = string.find(params, "^204 Output (%w+) (%d+)$")
1468 if stream and size then
1469 local size = tonumber(size)
1470 local msg = size > 0 and client:receive(size) or ""
1471 print(msg)
1472 if outputs[stream] then outputs[stream](msg) end
1473 -- this was just the output, so go back reading the response
1474 done = false
1475 end
1476 elseif status == "401" then
1477 len = tonumber(len)
1478 local res = client:receive(len)
1479 print("Error in expression: " .. res)
1480 return nil, nil, res
1481 else
1482 print("Unknown error")
1483 return nil, nil, "Debugger error: unexpected response after EXEC/LOAD '" .. params .. "'"
1484 end
1485 if done then break end
1486 end
1487 else
1488 print("Invalid command")
1489 end
1490 elseif command == "listb" then
1491 for l, v in pairs(breakpoints) do
1492 for f in pairs(v) do
1493 print(f .. ": " .. l)
1494 end
1495 end
1496 elseif command == "listw" then
1497 for i, v in pairs(watches) do
1498 print("Watch exp. " .. i .. ": " .. v)
1499 end
1500 elseif command == "suspend" then
1501 client:send("SUSPEND\n")
1502 elseif command == "stack" then
1503 local opts = string.match(params, "^[a-z]+%s+(.+)$")
1504 client:send("STACK" .. (opts and " "..opts or "") .."\n")
1505 local resp = client:receive()
1506 local _, _, status, res = string.find(resp, "^(%d+)%s+%w+%s+(.+)%s*$")
1507 if status == "200" then
1508 local func, err = loadstring(res)
1509 if func == nil then
1510 print("Error in stack information: " .. err)
1511 return nil, nil, err
1512 end
1513 local ok, stack = pcall(func)
1514 if not ok then
1515 print("Error in stack information: " .. stack)
1516 return nil, nil, stack
1517 end
1518 for _,frame in ipairs(stack) do
1519 print(mobdebug.line(frame[1], {comment = false}))
1520 end
1521 return stack
1522 elseif status == "401" then
1523 local _, _, len = string.find(resp, "%s+(%d+)%s*$")
1524 len = tonumber(len)
1525 local res = len > 0 and client:receive(len) or "Invalid stack information."
1526 print("Error in expression: " .. res)
1527 return nil, nil, res
1528 else
1529 print("Unknown error")
1530 return nil, nil, "Debugger error: unexpected response after STACK"
1531 end
1532 elseif command == "output" then
1533 local _, _, stream, mode = string.find(params, "^[a-z]+%s+(%w+)%s+([dcr])%s*$")
1534 if stream and mode then
1535 client:send("OUTPUT "..stream.." "..mode.."\n")
1536 local resp, err = client:receive()
1537 if not resp then
1538 print("Unknown error: "..err)
1539 return nil, nil, "Debugger connection error: "..err
1540 end
1541 local _, _, status = string.find(resp, "^(%d+)%s+%w+%s*$")
1542 if status == "200" then
1543 print("Stream "..stream.." redirected")
1544 outputs[stream] = type(options) == 'table' and options.handler or nil
1545 -- the client knows when she is doing, so install the handler
1546 elseif type(options) == 'table' and options.handler then
1547 outputs[stream] = options.handler
1548 else
1549 print("Unknown error")
1550 return nil, nil, "Debugger error: can't redirect "..stream
1551 end
1552 else
1553 print("Invalid command")
1554 end
1555 elseif command == "basedir" then
1556 local _, _, dir = string.find(params, "^[a-z]+%s+(.+)$")
1557 if dir then
1558 dir = string.gsub(dir, "\\", "/") -- convert slash
1559 if not string.find(dir, "/$") then dir = dir .. "/" end
1560
1561 local remdir = dir:match("\t(.+)")
1562 if remdir then dir = dir:gsub("/?\t.+", "/") end
1563 basedir = dir
1564
1565 client:send("BASEDIR "..(remdir or dir).."\n")
1566 local resp, err = client:receive()
1567 if not resp then
1568 print("Unknown error: "..err)
1569 return nil, nil, "Debugger connection error: "..err
1570 end
1571 local _, _, status = string.find(resp, "^(%d+)%s+%w+%s*$")
1572 if status == "200" then
1573 print("New base directory is " .. basedir)
1574 else
1575 print("Unknown error")
1576 return nil, nil, "Debugger error: unexpected response after BASEDIR"
1577 end
1578 else
1579 print(basedir)
1580 end
1581 elseif command == "help" then
1582 print("setb <file> <line> -- sets a breakpoint")
1583 print("delb <file> <line> -- removes a breakpoint")
1584 print("delallb -- removes all breakpoints")
1585 print("setw <exp> -- adds a new watch expression")
1586 print("delw <index> -- removes the watch expression at index")
1587 print("delallw -- removes all watch expressions")
1588 print("run -- runs until next breakpoint")
1589 print("step -- runs until next line, stepping into function calls")
1590 print("over -- runs until next line, stepping over function calls")
1591 print("out -- runs until line after returning from current function")
1592 print("listb -- lists breakpoints")
1593 print("listw -- lists watch expressions")
1594 print("eval <exp> -- evaluates expression on the current context and returns its value")
1595 print("exec <stmt> -- executes statement on the current context")
1596 print("load <file> -- loads a local file for debugging")
1597 print("reload -- restarts the current debugging session")
1598 print("stack -- reports stack trace")
1599 print("output stdout <d|c|r> -- capture and redirect io stream (default|copy|redirect)")
1600 print("basedir [<path>] -- sets the base path of the remote application, or shows the current one")
1601 print("done -- stops the debugger and continues application execution")
1602 print("exit -- exits debugger and the application")
1603 else
1604 local _, _, spaces = string.find(params, "^(%s*)$")
1605 if spaces then
1606 return nil, nil, "Empty command"
1607 else
1608 print("Invalid command")
1609 return nil, nil, "Invalid command"
1610 end
1611 end
1612 return file, line
1613end
1614
1615-- Starts debugging server
1616local function listen(host, port)
1617 host = host or "*"
1618 port = port or mobdebug.port
1619
1620 local socket = require "socket"
1621
1622 print("Lua Remote Debugger")
1623 print("Run the program you wish to debug")
1624
1625 local server = socket.bind(host, port)
1626 local client = server:accept()
1627
1628 client:send("STEP\n")
1629 client:receive()
1630
1631 local breakpoint = client:receive()
1632 local _, _, file, line = string.find(breakpoint, "^202 Paused%s+(.-)%s+(%d+)%s*$")
1633 if file and line then
1634 print("Paused at file " .. file )
1635 print("Type 'help' for commands")
1636 else
1637 local _, _, size = string.find(breakpoint, "^401 Error in Execution (%d+)%s*$")
1638 if size then
1639 print("Error in remote application: ")
1640 print(client:receive(size))
1641 end
1642 end
1643
1644 while true do
1645 io.write("> ")
1646 local file, line, err = handle(io.read("*line"), client)
1647 if not file and err == false then break end -- completed debugging
1648 end
1649
1650 client:close()
1651end
1652
1653local cocreate
1654local function coro()
1655 if cocreate then return end -- only set once
1656 cocreate = cocreate or coroutine.create
1657 coroutine.create = function(f, ...)
1658 return cocreate(function(...)
1659 mobdebug.on()
1660 return f(...)
1661 end, ...)
1662 end
1663end
1664
1665local moconew
1666local function moai()
1667 if moconew then return end -- only set once
1668 moconew = moconew or (MOAICoroutine and MOAICoroutine.new)
1669 if not moconew then return end
1670 MOAICoroutine.new = function(...)
1671 local thread = moconew(...)
1672 -- need to support both thread.run and getmetatable(thread).run, which
1673 -- was used in earlier MOAI versions
1674 local mt = thread.run and thread or getmetatable(thread)
1675 local patched = mt.run
1676 mt.run = function(self, f, ...)
1677 return patched(self, function(...)
1678 mobdebug.on()
1679 return f(...)
1680 end, ...)
1681 end
1682 return thread
1683 end
1684end
1685
1686-- make public functions available
1687mobdebug.setbreakpoint = set_breakpoint
1688mobdebug.removebreakpoint = remove_breakpoint
1689mobdebug.listen = listen
1690mobdebug.loop = loop
1691mobdebug.scratchpad = scratchpad
1692mobdebug.handle = handle
1693mobdebug.connect = connect
1694mobdebug.start = start
1695mobdebug.on = on
1696mobdebug.off = off
1697mobdebug.moai = moai
1698mobdebug.coro = coro
1699mobdebug.done = done
1700mobdebug.pause = function() step_into = true end
1701mobdebug.yield = nil -- callback
1702mobdebug.output = output
1703mobdebug.onexit = os and os.exit or done
1704mobdebug.onscratch = nil -- callback
1705mobdebug.basedir = function(b) if b then basedir = b end return basedir end
1706
1707return mobdebug
Note: See TracBrowser for help on using the repository browser.