Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: code/trunk/data/lua/Debugger.lua @ 7625

Last change on this file since 7625 was 6746, checked in by rgrieder, 15 years ago

Merged gamestates2 branch back to trunk.
This brings in some heavy changes in the GUI framework.
It should also fix problems with triggered asserts in the InputManager.

Note: PickupInventory does not seem to work —> Segfault when showing because before, the owner in GUIOverlay::setGUIName is already NULL.
I haven't tested it before, so I can't tell whether it's my changes.

  • Property svn:eol-style set to native
File size: 38.2 KB
RevLine 
[6663]1
2--{{{  history
3
4--28/03/10 ORX Adjusted show() to work with the Orxonox resource system
5--15/03/06 DCN Created based on RemDebug
6--28/04/06 DCN Update for Lua 5.1
7--01/06/06 DCN Fix command argument parsing
8--             Add step/over N facility
9--             Add trace lines facility
10--05/06/06 DCN Add trace call/return facility
11--06/06/06 DCN Make it behave when stepping through the creation of a coroutine
12--06/06/06 DCN Integrate the simple debugger into the main one
13--07/06/06 DCN Provide facility to step into coroutines
14--13/06/06 DCN Fix bug that caused the function environment to get corrupted with the global one
15--14/06/06 DCN Allow 'sloppy' file names when setting breakpoints
16--04/08/06 DCN Allow for no space after command name
17--11/08/06 DCN Use io.write not print
18--30/08/06 DCN Allow access to array elements in 'dump'
19--10/10/06 DCN Default to breakfile for all commands that require a filename and give '-'
20--06/12/06 DCN Allow for punctuation characters in DUMP variable names
21--03/01/07 DCN Add pause on/off facility
22--19/06/07 DCN Allow for duff commands being typed in the debugger (thanks to Michael.Bringmann@lsi.com)
23--             Allow for case sensitive file systems               (thanks to Michael.Bringmann@lsi.com)
24
25--}}}
26--{{{  description
27
28--A simple command line debug system for Lua written by Dave Nichols of
29--Match-IT Limited. Its public domain software. Do with it as you wish.
30
31--This debugger was inspired by:
32-- RemDebug 1.0 Beta
33-- Copyright Kepler Project 2005 (http://www.keplerproject.org/remdebug)
34
35--Usage:
36--  require('debugger')        --load the debug library
37--  pause(message)             --start/resume a debug session
38
39--An assert() failure will also invoke the debugger.
40
41--}}}
42
43local IsWindows = string.find(string.lower(os.getenv('OS') or ''),'^windows')
44
45local coro_debugger
46local events = { BREAK = 1, WATCH = 2, STEP = 3, SET = 4 }
47local breakpoints = {}
48local watches = {}
49local step_into   = false
50local step_over   = false
51local step_lines  = 0
52local step_level  = {main=0}
53local stack_level = {main=0}
54local trace_level = {main=0}
55local trace_calls = false
56local trace_returns = false
57local trace_lines = false
58local ret_file, ret_line, ret_name
59local current_thread = 'main'
60local started = false
61local pause_off = false
62local _g      = _G
63local cocreate, cowrap = coroutine.create, coroutine.wrap
64local pausemsg = 'pause'
65
66--{{{  local hints -- command help
67--The format in here is name=summary|description
68local hints = {
69
70pause =   [[
71pause(msg)          -- start/resume a debugger session|
72
73This can only be used in your code or from the console as a means to
74start/resume a debug session.
75If msg is given that is shown when the session starts/resumes. Useful to
76give a context if you've instrumented your code with pause() statements.
77]],
78
79poff =    [[
80poff                -- turn off pause() command|
81
82This causes all pause() commands to be ignored. This is useful if you have
83instrumented your code in a busy loop and want to continue normal execution
84with no further interruption.
85]],
86
87pon =     [[
88pon                 -- turn on pause() command|
89
90This re-instates honouring the pause() commands you may have instrumented
91your code with.
92]],
93
94setb =    [[
95setb [line file]    -- set a breakpoint to line/file|
96
97If file is omitted or is "-" the breakpoint is set at the file for the
98currently set level (see "set"). Execution pauses when this line is about
99to be executed and the debugger session is re-activated.
100
101The file can be given as the fully qualified name, partially qualified or
102just the file name. E.g. if file is set as "myfile.lua", then whenever
103execution reaches any file that ends with "myfile.lua" it will pause.
104]],
105
106delb =    [[
107delb [line file]    -- removes a breakpoint|
108
109If file is omitted or is "-" the breakpoint is removed for the file of the
110currently set level (see "set").
111]],
112
113delallb = [[
114delallb             -- removes all breakpoints|
115]],
116
117setw =    [[
118setw <exp>          -- adds a new watch expression|
119
120The expression is evaluated before each line is executed. If the expression
121yields true then execution is paused and the debugger session re-activated.
122The expression is executed in the context of the line about to be executed.
123]],
124
125delw =    [[
126delw <index>        -- removes the watch expression at index|
127
128The index is that returned when the watch expression was set by setw.
129]],
130
131delallw = [[
132delallw             -- removes all watch expressions|
133]],
134
135run     = [[
136run                 -- run until next breakpoint or watch expression|
137]],
138
139step    = [[
140step [N]            -- run next N lines, stepping into function calls|
141
142If N is omitted, use 1.
143]],
144
145over    = [[
146over [N]            -- run next N lines, stepping over function calls|
147
148If N is omitted, use 1.
149]],
150
151out     = [[
152out [N]             -- run lines until stepped out of N functions|
153
154If N is omitted, use 1.
155If you are inside a function, using "out 1" will run until you return
156from that function to the caller.
157]],
158
159goto    = [[
160goto <line>         -- step to line number <line> in the current file|
161
162The line and current file are those in the currently set context level.
163]],
164
165listb   = [[
166listb               -- lists breakpoints|
167]],
168
169listw   = [[
170listw               -- lists watch expressions|
171]],
172
173set     = [[
174set [level]         -- set context to stack level, omitted=show|
175
176If level is omitted it just prints the current level set.
177This sets the current context to the level given. This affects the
178context used for several other functions (e.g. vars). The possible
179levels are those shown by trace.
180]],
181
182vars    = [[
183vars [depth]        -- list context locals to depth, omitted=1|
184
185If depth is omitted then uses 1.
186Use a depth of 0 for the maximum.
187Lists all non-nil local variables and all non-nil upvalues in the
188currently set context. For variables that are tables, lists all fields
189to the given depth.
190]],
191
192fenv    = [[
193fenv [depth]        -- list context function env to depth, omitted=1|
194
195If depth is omitted then uses 1.
196Use a depth of 0 for the maximum.
197Lists all function environment variables in the currently set context.
198For variables that are tables, lists all fields to the given depth.
199]],
200
201glob    = [[
202glob [depth]        -- list globals to depth, omitted=1|
203
204If depth is omitted then uses 1.
205Use a depth of 0 for the maximum.
206Lists all global variables.
207For variables that are tables, lists all fields to the given depth.
208]],
209
210ups     = [[
211ups                 -- list all the upvalue names|
212
213These names will also be in the "vars" list unless their value is nil.
214This provides a means to identify which vars are upvalues and which are
215locals. If a name is both an upvalue and a local, the local value takes
216precedance.
217]],
218
219locs    = [[
220locs                -- list all the locals names|
221
222These names will also be in the "vars" list unless their value is nil.
223This provides a means to identify which vars are upvalues and which are
224locals. If a name is both an upvalue and a local, the local value takes
225precedance.
226]],
227
228dump    = [[
229dump <var> [depth]  -- dump all fields of variable to depth|
230
231If depth is omitted then uses 1.
232Use a depth of 0 for the maximum.
233Prints the value of <var> in the currently set context level. If <var>
234is a table, lists all fields to the given depth. <var> can be just a
235name, or name.field or name.# to any depth, e.g. t.1.f accesses field
236'f' in array element 1 in table 't'.
237
238Can also be called from a script as dump(var,depth).
239]],
240
241tron    = [[
242tron [crl]          -- turn trace on for (c)alls, (r)etuns, (l)lines|
243
244If no parameter is given then tracing is turned off.
245When tracing is turned on a line is printed to the console for each
246debug 'event' selected. c=function calls, r=function returns, l=lines.
247]],
248
249trace   = [[
250trace               -- dumps a stack trace|
251
252Format is [level] = file,line,name
253The level is a candidate for use by the 'set' command.
254]],
255
256info    = [[
257info                -- dumps the complete debug info captured|
258
259Only useful as a diagnostic aid for the debugger itself. This information
260can be HUGE as it dumps all variables to the maximum depth, so be careful.
261]],
262
263show    = [[
264show line file X Y  -- show X lines before and Y after line in file|
265
266If line is omitted or is '-' then the current set context line is used.
267If file is omitted or is '-' then the current set context file is used.
268If file is not fully qualified and cannot be opened as specified, then
269a search for the file in the package[path] is performed using the usual
270"require" searching rules. If no file extension is given, .lua is used.
271Prints the lines from the source file around the given line.
272]],
273
274exit    = [[
275exit                -- exits debugger, re-start it using pause()|
276]],
277
278help    = [[
279help [command]      -- show this list or help for command|
280]],
281
282["<statement>"] = [[
283<statement>         -- execute a statement in the current context|
284
285The statement can be anything that is legal in the context, including
286assignments. Such assignments affect the context and will be in force
287immediately. Any results returned are printed. Use '=' as a short-hand
288for 'return', e.g. "=func(arg)" will call 'func' with 'arg' and print
289the results, and "=var" will just print the value of 'var'.
290]],
291
292what    = [[
293what <func>         -- show where <func> is defined (if known)|
294]],
295
296}
297--}}}
298
299--{{{  local function getinfo(level,field)
300
301--like debug.getinfo but copes with no activation record at the given level
302--and knows how to get 'field'. 'field' can be the name of any of the
303--activation record fields or any of the 'what' names or nil for everything.
304--only valid when using the stack level to get info, not a function name.
305
306local function getinfo(level,field)
307  level = level + 1  --to get to the same relative level as the caller
308  if not field then return debug.getinfo(level) end
309  local what
310  if field == 'name' or field == 'namewhat' then
311    what = 'n'
312  elseif field == 'what' or field == 'source' or field == 'linedefined' or field == 'lastlinedefined' or field == 'short_src' then
313    what = 'S'
314  elseif field == 'currentline' then
315    what = 'l'
316  elseif field == 'nups' then
317    what = 'u'
318  elseif field == 'func' then
319    what = 'f'
320  else
321    return debug.getinfo(level,field)
322  end
323  local ar = debug.getinfo(level,what)
324  if ar then return ar[field] else return nil end
325end
326
327--}}}
328--{{{  local function indented( level, ... )
329
330local function indented( level, ... )
331  io.write( string.rep('  ',level), table.concat({...}), '\n' )
332end
333
334--}}}
335--{{{  local function dumpval( level, name, value, limit )
336
337local dumpvisited
338
339local function dumpval( level, name, value, limit )
340  local index
341  if type(name) == 'number' then
342    index = string.format('[%d] = ',name)
343  elseif type(name) == 'string'
344     and (name == '__VARSLEVEL__' or name == '__ENVIRONMENT__' or name == '__GLOBALS__' or name == '__UPVALUES__' or name == '__LOCALS__') then
345    --ignore these, they are debugger generated
346    return
347  elseif type(name) == 'string' and string.find(name,'^[_%a][_.%w]*$') then
348    index = name ..' = '
349  else
350    index = string.format('[%q] = ',tostring(name))
351  end
352  if type(value) == 'table' then
353    if dumpvisited[value] then
354      indented( level, index, string.format('ref%q;',dumpvisited[value]) )
355    else
356      dumpvisited[value] = tostring(value)
357      if (limit or 0) > 0 and level+1 >= limit then
358        indented( level, index, dumpvisited[value] )
359      else
360        indented( level, index, '{  -- ', dumpvisited[value] )
361        for n,v in pairs(value) do
362          dumpval( level+1, n, v, limit )
363        end
364        indented( level, '};' )
365      end
366    end
367  else
368    if type(value) == 'string' then
369      if string.len(value) > 40 then
370        indented( level, index, '[[', value, ']];' )
371      else
372        indented( level, index, string.format('%q',value), ';' )
373      end
374    else
375      indented( level, index, tostring(value), ';' )
376    end
377  end
378end
379
380--}}}
381--{{{  local function dumpvar( value, limit, name )
382
383local function dumpvar( value, limit, name )
384  dumpvisited = {}
385  dumpval( 0, name or tostring(value), value, limit )
386end
387
388--}}}
389--{{{  local function show(file,line,before,after)
390
391--show +/-N lines of a file around line M
392
393local function show(file,line,before,after)
394
395  line   = tonumber(line   or 1)
396  before = tonumber(before or 10)
397  after  = tonumber(after  or before)
398
399  -- Try to find the file in the Orxonox resources
400  local text = luaState:getSourceCode(file)
401
402  if text == "" then
403    if not string.find(file,'%.') then file = file..'.lua' end
404
405    local f = io.open(file,'r')
406    if not f then
407      --{{{  try to find the file in the path
408   
409      --
410      -- looks for a file in the package path
411      --
412      local path = package.path or LUA_PATH or ''
413      for c in string.gmatch (path, "[^;]+") do
414        local c = string.gsub (c, "%?%.lua", file)
415        f = io.open (c,'r')
416        if f then
417          break
418        end
419      end
420   
421      --}}}
422
423      if f then
424        -- Read file into 'text'
425        text = f:read("*a")
426        f:close()
427      else
428        io.write('Cannot find '..file..'\n')
429        return
430      end
431    end
432  end
433
434  -- Transform line endings to \n
435  text:gsub("\r\n", "\n") -- Windows to Unix
436  text:gsub("\r", "\n")   -- Mac to Unix
437  if text[-1] ~= "\n" then
438      text = text.."\n"
439  end
440  -- Print requested lines
441  local i = 0
442  for l in text:gmatch("[^\n]*[\n]") do
443    i = i + 1
444    if i >= (line-before) then
445      if i > (line+after) then break end
446      if i == line then
447        io.write(i..'***\t'..l)
448      else
449        io.write(i..'\t'..l)
450      end
451    end
452  end
453end
454
455--}}}
456--{{{  local function tracestack(l)
457
458local function gi( i )
459  return function() i=i+1 return debug.getinfo(i),i end
460end
461
462local function gl( level, j )
463  return function() j=j+1 return debug.getlocal( level, j ) end
464end
465
466local function gu( func, k )
467  return function() k=k+1 return debug.getupvalue( func, k ) end
468end
469
470local  traceinfo
471
472local function tracestack(l)
473  local l = l + 1                        --NB: +1 to get level relative to caller
474  traceinfo = {}
475  traceinfo.pausemsg = pausemsg
476  for ar,i in gi(l) do
477    table.insert( traceinfo, ar )
478    local names  = {}
479    local values = {}
480    for n,v in gl(i,0) do
481      if string.sub(n,1,1) ~= '(' then   --ignore internal control variables
482        table.insert( names, n )
483        table.insert( values, v )
484      end
485    end
486    if #names > 0 then
487      ar.lnames  = names
488      ar.lvalues = values
489    end
490    if ar.func then
491      local names  = {}
492      local values = {}
493      for n,v in gu(ar.func,0) do
494        if string.sub(n,1,1) ~= '(' then   --ignore internal control variables
495          table.insert( names, n )
496          table.insert( values, v )
497        end
498      end
499      if #names > 0 then
500        ar.unames  = names
501        ar.uvalues = values
502      end
503    end
504  end
505end
506
507--}}}
508--{{{  local function trace()
509
510local function trace(set)
511  local mark
512  for level,ar in ipairs(traceinfo) do
513    if level == set then
514      mark = '***'
515    else
516      mark = ''
517    end
518    io.write('['..level..']'..mark..'\t'..(ar.name or ar.what)..' in '..ar.short_src..':'..ar.currentline..'\n')
519  end
520end
521
522--}}}
523--{{{  local function info()
524
525local function info() dumpvar( traceinfo, 0, 'traceinfo' ) end
526
527--}}}
528
529--{{{  local function set_breakpoint(file, line)
530
531local function set_breakpoint(file, line)
532  if not breakpoints[line] then
533    breakpoints[line] = {}
534  end
535  breakpoints[line][file] = true
536end
537
538--}}}
539--{{{  local function remove_breakpoint(file, line)
540
541local function remove_breakpoint(file, line)
542  if breakpoints[line] then
543    breakpoints[line][file] = nil
544  end
545end
546
547--}}}
548--{{{  local function has_breakpoint(file, line)
549
550--allow for 'sloppy' file names
551--search for file and all variations walking up its directory hierachy
552--ditto for the file with no extension
553
554local function has_breakpoint(file, line)
555  if not breakpoints[line] then return false end
556  local noext = string.gsub(file,"(%..-)$",'',1)
557  if noext == file then noext = nil end
558  while file do
559    if breakpoints[line][file] then return true end
560    file = string.match(file,"[:/\](.+)$")
561  end
562  while noext do
563    if breakpoints[line][noext] then return true end
564    noext = string.match(noext,"[:/\](.+)$")
565  end
566  return false
567end
568
569--}}}
570--{{{  local function capture_vars(ref,level,line)
571
572local function capture_vars(ref,level,line)
573  --get vars, file and line for the given level relative to debug_hook offset by ref
574
575  local lvl = ref + level                --NB: This includes an offset of +1 for the call to here
576
577  --{{{  capture variables
578 
579  local ar = debug.getinfo(lvl, "f")
580  if not ar then return {},'?',0 end
581 
582  local vars = {__UPVALUES__={}, __LOCALS__={}}
583  local i
584 
585  local func = ar.func
586  if func then
587    i = 1
588    while true do
589      local name, value = debug.getupvalue(func, i)
590      if not name then break end
591      if string.sub(name,1,1) ~= '(' then  --NB: ignoring internal control variables
592        vars[name] = value
593        vars.__UPVALUES__[i] = name
594      end
595      i = i + 1
596    end
597    vars.__ENVIRONMENT__ = getfenv(func)
598  end
599 
600  vars.__GLOBALS__ = getfenv(0)
601 
602  i = 1
603  while true do
604    local name, value = debug.getlocal(lvl, i)
605    if not name then break end
606    if string.sub(name,1,1) ~= '(' then    --NB: ignoring internal control variables
607      vars[name] = value
608      vars.__LOCALS__[i] = name
609    end
610    i = i + 1
611  end
612 
613  vars.__VARSLEVEL__ = level
614 
615  if func then
616    --NB: Do not do this until finished filling the vars table
617    setmetatable(vars, { __index = getfenv(func), __newindex = getfenv(func) })
618  end
619 
620  --NB: Do not read or write the vars table anymore else the metatable functions will get invoked!
621 
622  --}}}
623
624  local file = getinfo(lvl, "source")
625  if string.find(file, "@") == 1 then
626    file = string.sub(file, 2)
627  end
628  -- Orxonox changes: Our resource system is case sensisive, even on Windows
629  --if IsWindows then file = string.lower(file) end
630
631  if not line then
632    line = getinfo(lvl, "currentline")
633  end
634
635  return vars,file,line
636
637end
638
639--}}}
640--{{{  local function restore_vars(ref,vars)
641
642local function restore_vars(ref,vars)
643
644  if type(vars) ~= 'table' then return end
645
646  local level = vars.__VARSLEVEL__       --NB: This level is relative to debug_hook offset by ref
647  if not level then return end
648
649  level = level + ref                    --NB: This includes an offset of +1 for the call to here
650
651  local i
652  local written_vars = {}
653
654  i = 1
655  while true do
656    local name, value = debug.getlocal(level, i)
657    if not name then break end
658    if vars[name] and string.sub(name,1,1) ~= '(' then     --NB: ignoring internal control variables
659      debug.setlocal(level, i, vars[name])
660      written_vars[name] = true
661    end
662    i = i + 1
663  end
664
665  local ar = debug.getinfo(level, "f")
666  if not ar then return end
667
668  local func = ar.func
669  if func then
670
671    i = 1
672    while true do
673      local name, value = debug.getupvalue(func, i)
674      if not name then break end
675      if vars[name] and string.sub(name,1,1) ~= '(' then   --NB: ignoring internal control variables
676        if not written_vars[name] then
677          debug.setupvalue(func, i, vars[name])
678        end
679        written_vars[name] = true
680      end
681      i = i + 1
682    end
683
684  end
685
686end
687
688--}}}
689--{{{  local function trace_event(event, line, level)
690
691local function print_trace(level,depth,event,file,line,name)
692
693  --NB: level here is relative to the caller of trace_event, so offset by 2 to get to there
694  level = level + 2
695
696  local file = file or getinfo(level,'short_src')
697  local line = line or getinfo(level,'currentline')
698  local name = name or getinfo(level,'name')
699
700  local prefix = ''
701  if current_thread ~= 'main' then prefix = '['..tostring(current_thread)..'] ' end
702
703  io.write(prefix..
704           string.format('%08.2f:%02i.',os.clock(),depth)..
705           string.rep('.',depth%32)..
706           (file or '')..' ('..(line or '')..') '..
707           (name or '')..
708           ' ('..event..')\n')
709
710end
711
712local function trace_event(event, line, level)
713
714  if event == 'return' and trace_returns then
715    --note the line info for later
716    ret_file = getinfo(level+1,'short_src')
717    ret_line = getinfo(level+1,'currentline')
718    ret_name = getinfo(level+1,'name')
719  end
720
721  if event ~= 'line' then return end
722
723  local slevel = stack_level[current_thread]
724  local tlevel = trace_level[current_thread]
725
726  if trace_calls and slevel > tlevel then
727    --we are now in the function called, so look back 1 level further to find the calling file and line
728    print_trace(level+1,slevel-1,'c',nil,nil,getinfo(level+1,'name'))
729  end
730
731  if trace_returns and slevel < tlevel then
732    print_trace(level,slevel,'r',ret_file,ret_line,ret_name)
733  end
734
735  if trace_lines then
736    print_trace(level,slevel,'l')
737  end
738
739  trace_level[current_thread] = stack_level[current_thread]
740
741end
742
743--}}}
744--{{{  local function debug_hook(event, line, level, thread)
745
746local function debug_hook(event, line, level, thread)
747  if not started then debug.sethook() return end
748  current_thread = thread or 'main'
749  local level = level or 2
750  trace_event(event,line,level)
751  if event == "call" then
752    stack_level[current_thread] = stack_level[current_thread] + 1
753  elseif event == "return" then
754    stack_level[current_thread] = stack_level[current_thread] - 1
755    if stack_level[current_thread] < 0 then stack_level[current_thread] = 0 end
756  else
757    local vars,file,line = capture_vars(level,1,line)
758    local stop, ev, idx = false, events.STEP, 0
759    while true do
760      for index, value in pairs(watches) do
761        setfenv(value.func, vars)
762        local status, res = pcall(value.func)
763        if status and res then
764          ev, idx = events.WATCH, index
765          stop = true
766          break
767        end
768      end
769      if stop then break end
770      if (step_into)
771      or (step_over and (stack_level[current_thread] <= step_level[current_thread] or stack_level[current_thread] == 0)) then
772        step_lines = step_lines - 1
773        if step_lines < 1 then
774          ev, idx = events.STEP, 0
775          break
776        end
777      end
778      if has_breakpoint(file, line) then
779        ev, idx = events.BREAK, 0
780        break
781      end
782      return
783    end
784    tracestack(level)
785    local last_next = 1
786    local err, next = assert(coroutine.resume(coro_debugger, ev, vars, file, line, idx))
787    while true do
788      if next == 'cont' then
789        return
790      elseif next == 'stop' then
791        started = false
792        debug.sethook()
793        return
794      elseif tonumber(next) then --get vars for given level or last level
795        next = tonumber(next)
796        if next == 0 then next = last_next end
797        last_next = next
798        restore_vars(level,vars)
799        vars, file, line = capture_vars(level,next)
800        err, next = assert(coroutine.resume(coro_debugger, events.SET, vars, file, line, idx))
801      else
802        io.write('Unknown command from debugger_loop: '..tostring(next)..'\n')
803        io.write('Stopping debugger\n')
804        next = 'stop'
805      end
806    end
807  end
808end
809
810--}}}
811--{{{  local function report(ev, vars, file, line, idx_watch)
812
813local function report(ev, vars, file, line, idx_watch)
814  local vars = vars or {}
815  local file = file or '?'
816  local line = line or 0
817  local prefix = ''
818  if current_thread ~= 'main' then prefix = '['..tostring(current_thread)..'] ' end
819  if ev == events.STEP then
820    io.write(prefix.."Paused at file "..file.." line "..line..' ('..stack_level[current_thread]..')\n')
821  elseif ev == events.BREAK then
822    io.write(prefix.."Paused at file "..file.." line "..line..' ('..stack_level[current_thread]..') (breakpoint)\n')
823  elseif ev == events.WATCH then
824    io.write(prefix.."Paused at file "..file.." line "..line..' ('..stack_level[current_thread]..')'.." (watch expression "..idx_watch.. ": ["..watches[idx_watch].exp.."])\n")
825  elseif ev == events.SET then
826    --do nothing
827  else
828    io.write(prefix.."Error in application: "..file.." line "..line.."\n")
829  end
830  if ev ~= events.SET then
831    if pausemsg and pausemsg ~= '' then io.write('Message: '..pausemsg..'\n') end
832    pausemsg = ''
833  end
834  return vars, file, line
835end
836
837--}}}
838
839--{{{  local function debugger_loop(server)
840
841local function debugger_loop(ev, vars, file, line, idx_watch)
842
843  io.write("Lua Debugger\n")
844  local eval_env, breakfile, breakline = report(ev, vars, file, line, idx_watch)
845  io.write("Type 'help' for commands\n")
846
847  local command, args
848
849  --{{{  local function getargs(spec)
850 
851  --get command arguments according to the given spec from the args string
852  --the spec has a single character for each argument, arguments are separated
853  --by white space, the spec characters can be one of:
854  -- F for a filename    (defaults to breakfile if - given in args)
855  -- L for a line number (defaults to breakline if - given in args)
856  -- N for a number
857  -- V for a variable name
858  -- S for a string
859 
860  local function getargs(spec)
861    local res={}
862    local char,arg
863    local ptr=1
864    for i=1,string.len(spec) do
865      char = string.sub(spec,i,i)
866      if     char == 'F' then
867        _,ptr,arg = string.find(args..' ',"%s*([%w%p]*)%s*",ptr)
868        if not arg or arg == '' then arg = '-' end
869        if arg == '-' then arg = breakfile end
870      elseif char == 'L' then
871        _,ptr,arg = string.find(args..' ',"%s*([%w%p]*)%s*",ptr)
872        if not arg or arg == '' then arg = '-' end
873        if arg == '-' then arg = breakline end
874        arg = tonumber(arg) or 0
875      elseif char == 'N' then
876        _,ptr,arg = string.find(args..' ',"%s*([%w%p]*)%s*",ptr)
877        if not arg or arg == '' then arg = '0' end
878        arg = tonumber(arg) or 0
879      elseif char == 'V' then
880        _,ptr,arg = string.find(args..' ',"%s*([%w%p]*)%s*",ptr)
881        if not arg or arg == '' then arg = '' end
882      elseif char == 'S' then
883        _,ptr,arg = string.find(args..' ',"%s*([%w%p]*)%s*",ptr)
884        if not arg or arg == '' then arg = '' end
885      else
886        arg = ''
887      end
888      table.insert(res,arg or '')
889    end
890    return unpack(res)
891  end
892 
893  --}}}
894
895  while true do
896    io.write("[DEBUG]> ")
897    local line = io.read("*line")
898    if line == nil then io.write('\n'); line = 'exit' end
899
900    if string.find(line, "^[a-z]+") then
901      command = string.sub(line, string.find(line, "^[a-z]+"))
902      args    = string.gsub(line,"^[a-z]+%s*",'',1)            --strip command off line
903    else
904      command = ''
905    end
906
907    if command == "setb" then
908      --{{{  set breakpoint
909     
910      local line, filename  = getargs('LF')
911      if filename ~= '' and line ~= '' then
912        set_breakpoint(filename,line)
913        io.write("Breakpoint set in file "..filename..' line '..line..'\n')
914      else
915        io.write("Bad request\n")
916      end
917     
918      --}}}
919
920    elseif command == "delb" then
921      --{{{  delete breakpoint
922     
923      local line, filename = getargs('LF')
924      if filename ~= '' and line ~= '' then
925        remove_breakpoint(filename, line)
926        io.write("Breakpoint deleted from file "..filename..' line '..line.."\n")
927      else
928        io.write("Bad request\n")
929      end
930     
931      --}}}
932
933    elseif command == "delallb" then
934      --{{{  delete all breakpoints
935      breakpoints = {}
936      io.write('All breakpoints deleted\n')
937      --}}}
938
939    elseif command == "listb" then
940      --{{{  list breakpoints
941      for i, v in pairs(breakpoints) do
942        for ii, vv in pairs(v) do
943          io.write("Break at: "..i..' in '..ii..'\n')
944        end
945      end
946      --}}}
947
948    elseif command == "setw" then
949      --{{{  set watch expression
950     
951      if args and args ~= '' then
952        local func = loadstring("return(" .. args .. ")")
953        local newidx = #watches + 1
954        watches[newidx] = {func = func, exp = args}
955        io.write("Set watch exp no. " .. newidx..'\n')
956      else
957        io.write("Bad request\n")
958      end
959     
960      --}}}
961
962    elseif command == "delw" then
963      --{{{  delete watch expression
964     
965      local index = tonumber(args)
966      if index then
967        watches[index] = nil
968        io.write("Watch expression deleted\n")
969      else
970        io.write("Bad request\n")
971      end
972     
973      --}}}
974
975    elseif command == "delallw" then
976      --{{{  delete all watch expressions
977      watches = {}
978      io.write('All watch expressions deleted\n')
979      --}}}
980
981    elseif command == "listw" then
982      --{{{  list watch expressions
983      for i, v in pairs(watches) do
984        io.write("Watch exp. " .. i .. ": " .. v.exp..'\n')
985      end
986      --}}}
987
988    elseif command == "run" then
989      --{{{  run until breakpoint
990      step_into = false
991      step_over = false
992      eval_env, breakfile, breakline = report(coroutine.yield('cont'))
993      --}}}
994
995    elseif command == "step" then
996      --{{{  step N lines (into functions)
997      local N = tonumber(args) or 1
998      step_over  = false
999      step_into  = true
1000      step_lines = tonumber(N or 1)
1001      eval_env, breakfile, breakline = report(coroutine.yield('cont'))
1002      --}}}
1003
1004    elseif command == "over" then
1005      --{{{  step N lines (over functions)
1006      local N = tonumber(args) or 1
1007      step_into  = false
1008      step_over  = true
1009      step_lines = tonumber(N or 1)
1010      step_level[current_thread] = stack_level[current_thread]
1011      eval_env, breakfile, breakline = report(coroutine.yield('cont'))
1012      --}}}
1013
1014    elseif command == "out" then
1015      --{{{  step N lines (out of functions)
1016      local N = tonumber(args) or 1
1017      step_into  = false
1018      step_over  = true
1019      step_lines = 1
1020      step_level[current_thread] = stack_level[current_thread] - tonumber(N or 1)
1021      eval_env, breakfile, breakline = report(coroutine.yield('cont'))
1022      --}}}
1023
1024    elseif command == "goto" then
1025      --{{{  step until reach line
1026      local N = tonumber(args)
1027      if N then
1028        step_over  = false
1029        step_into  = false
1030        if has_breakpoint(breakfile,N) then
1031          eval_env, breakfile, breakline = report(coroutine.yield('cont'))
1032        else
1033          local bf = breakfile
1034          set_breakpoint(breakfile,N)
1035          eval_env, breakfile, breakline = report(coroutine.yield('cont'))
1036          if breakfile == bf and breakline == N then remove_breakpoint(breakfile,N) end
1037        end
1038      else
1039        io.write("Bad request\n")
1040      end
1041      --}}}
1042
1043    elseif command == "set" then
1044      --{{{  set/show context level
1045      local level = args
1046      if level and level == '' then level = nil end
1047      if level then
1048        eval_env, breakfile, breakline = report(coroutine.yield(level))
1049      end
1050      if eval_env.__VARSLEVEL__ then
1051        io.write('Level: '..eval_env.__VARSLEVEL__..'\n')
1052      else
1053        io.write('No level set\n')
1054      end
1055      --}}}
1056
1057    elseif command == "vars" then
1058      --{{{  list context variables
1059      local depth = args
1060      if depth and depth == '' then depth = nil end
1061      depth = tonumber(depth) or 1
1062      dumpvar(eval_env, depth+1, 'variables')
1063      --}}}
1064
1065    elseif command == "glob" then
1066      --{{{  list global variables
1067      local depth = args
1068      if depth and depth == '' then depth = nil end
1069      depth = tonumber(depth) or 1
1070      dumpvar(eval_env.__GLOBALS__,depth+1,'globals')
1071      --}}}
1072
1073    elseif command == "fenv" then
1074      --{{{  list function environment variables
1075      local depth = args
1076      if depth and depth == '' then depth = nil end
1077      depth = tonumber(depth) or 1
1078      dumpvar(eval_env.__ENVIRONMENT__,depth+1,'environment')
1079      --}}}
1080
1081    elseif command == "ups" then
1082      --{{{  list upvalue names
1083      dumpvar(eval_env.__UPVALUES__,2,'upvalues')
1084      --}}}
1085
1086    elseif command == "locs" then
1087      --{{{  list locals names
1088      dumpvar(eval_env.__LOCALS__,2,'upvalues')
1089      --}}}
1090
1091    elseif command == "what" then
1092      --{{{  show where a function is defined
1093      if args and args ~= '' then
1094        local v = eval_env
1095        local n = nil
1096        for w in string.gmatch(args,"[%w_]+") do
1097          v = v[w]
1098          if n then n = n..'.'..w else n = w end
1099          if not v then break end
1100        end
1101        if type(v) == 'function' then
1102          local def = debug.getinfo(v,'S')
1103          if def then
1104            io.write(def.what..' in '..def.short_src..' '..def.linedefined..'..'..def.lastlinedefined..'\n')
1105          else
1106            io.write('Cannot get info for '..v..'\n')
1107          end
1108        else
1109          io.write(v..' is not a function\n')
1110        end
1111      else
1112        io.write("Bad request\n")
1113      end
1114      --}}}
1115
1116    elseif command == "dump" then
1117      --{{{  dump a variable
1118      local name, depth = getargs('VN')
1119      if name ~= '' then
1120        if depth == '' or depth == 0 then depth = nil end
1121        depth = tonumber(depth or 1)
1122        local v = eval_env
1123        local n = nil
1124        for w in string.gmatch(name,"[^%.]+") do     --get everything between dots
1125          if tonumber(w) then
1126            v = v[tonumber(w)]
1127          else
1128            v = v[w]
1129          end
1130          if n then n = n..'.'..w else n = w end
1131          if not v then break end
1132        end
1133        dumpvar(v,depth+1,n)
1134      else
1135        io.write("Bad request\n")
1136      end
1137      --}}}
1138
1139    elseif command == "show" then
1140      --{{{  show file around a line or the current breakpoint
1141     
1142      local line, file, before, after = getargs('LFNN')
1143      if before == 0 then before = 10     end
1144      if after  == 0 then after  = before end
1145     
1146      if file ~= '' and file ~= "=stdin" then
1147        show(file,line,before,after)
1148      else
1149        io.write('Nothing to show\n')
1150      end
1151     
1152      --}}}
1153
1154    elseif command == "poff" then
1155      --{{{  turn pause command off
1156      pause_off = true
1157      --}}}
1158
1159    elseif command == "pon" then
1160      --{{{  turn pause command on
1161      pause_off = false
1162      --}}}
1163
1164    elseif command == "tron" then
1165      --{{{  turn tracing on/off
1166      local option = getargs('S')
1167      trace_calls   = false
1168      trace_returns = false
1169      trace_lines   = false
1170      if string.find(option,'c') then trace_calls   = true end
1171      if string.find(option,'r') then trace_returns = true end
1172      if string.find(option,'l') then trace_lines   = true end
1173      --}}}
1174
1175    elseif command == "trace" then
1176      --{{{  dump a stack trace
1177      trace(eval_env.__VARSLEVEL__)
1178      --}}}
1179
1180    elseif command == "info" then
1181      --{{{  dump all debug info captured
1182      info()
1183      --}}}
1184
1185    elseif command == "pause" then
1186      --{{{  not allowed in here
1187      io.write('pause() should only be used in the script you are debugging\n')
1188      --}}}
1189
1190    elseif command == "help" then
1191      --{{{  help
1192      local command = getargs('S')
1193      if command ~= '' and hints[command] then
1194        io.write(hints[command]..'\n')
1195      else
1196        for _,v in pairs(hints) do
1197          local _,_,h = string.find(v,"(.+)|")
1198          io.write(h..'\n')
1199        end
1200      end
1201      --}}}
1202
1203    elseif command == "exit" then
1204      --{{{  exit debugger
1205      return 'stop'
1206      --}}}
1207
1208    elseif line ~= '' then
1209      --{{{  just execute whatever it is in the current context
1210     
1211      --map line starting with "=..." to "return ..."
1212      if string.sub(line,1,1) == '=' then line = string.gsub(line,'=','return ',1) end
1213     
1214      local ok, func = pcall(loadstring,line)
1215      if func == nil then                             --Michael.Bringmann@lsi.com
1216        io.write("Compile error: "..line..'\n')
1217      elseif not ok then
1218        io.write("Compile error: "..func..'\n')
1219      else
1220        setfenv(func, eval_env)
1221        local res = {pcall(func)}
1222        if res[1] then
1223          if res[2] then
1224            table.remove(res,1)
1225            for _,v in ipairs(res) do
1226              io.write(tostring(v))
1227              io.write('\t')
1228            end
1229            io.write('\n')
1230          end
1231          --update in the context
1232          eval_env, breakfile, breakline = report(coroutine.yield(0))
1233        else
1234          io.write("Run error: "..res[2]..'\n')
1235        end
1236      end
1237     
1238      --}}}
1239    end
1240  end
1241
1242end
1243
1244--}}}
1245
1246--{{{  coroutine.create
1247
1248--This function overrides the built-in for the purposes of propagating
1249--the debug hook settings from the creator into the created coroutine.
1250
1251_G.coroutine.create = function(f)
1252  local thread
1253  local hook, mask, count = debug.gethook()
1254  if hook then
1255    local function thread_hook(event,line)
1256      hook(event,line,3,thread)
1257    end
1258    thread = cocreate(function(...)
1259                        stack_level[thread] = 0
1260                        trace_level[thread] = 0
1261                        step_level [thread] = 0
1262                        debug.sethook(thread_hook,mask,count)
1263                        return f(...)
1264                      end)
1265    return thread
1266  else
1267    return cocreate(f)
1268  end
1269end
1270
1271--}}}
1272--{{{  coroutine.wrap
1273
1274--This function overrides the built-in for the purposes of propagating
1275--the debug hook settings from the creator into the created coroutine.
1276
1277_G.coroutine.wrap = function(f)
1278  local thread
1279  local hook, mask, count = debug.gethook()
1280  if hook then
1281    local function thread_hook(event,line)
1282      hook(event,line,3,thread)
1283    end
1284    thread = cowrap(function(...)
1285                      stack_level[thread] = 0
1286                      trace_level[thread] = 0
1287                      step_level [thread] = 0
1288                      debug.sethook(thread_hook,mask,count)
1289                      return f(...)
1290                    end)
1291    return thread
1292  else
1293    return cowrap(f)
1294  end
1295end
1296
1297--}}}
1298
1299--{{{  function pause()
1300
1301--
1302-- Starts/resumes a debug session
1303--
1304
1305function pause(x)
1306  if pause_off then return end               --being told to ignore pauses
1307  pausemsg = x or 'pause'
1308  local lines
1309  local src = getinfo(2,'short_src')
1310  if src == "stdin" then
1311    lines = 1   --if in a console session, stop now
1312  else
1313    lines = 2   --if in a script, stop when get out of pause()
1314  end
1315  if started then
1316    --we'll stop now 'cos the existing debug hook will grab us
1317    step_lines = lines
1318    step_into  = true
1319  else
1320    coro_debugger = cocreate(debugger_loop)  --NB: Use original coroutune.create
1321    --set to stop when get out of pause()
1322    trace_level[current_thread] = 0
1323    step_level [current_thread] = 0
1324    stack_level[current_thread] = 1
1325    step_lines = lines
1326    step_into  = true
1327    started    = true
1328    debug.sethook(debug_hook, "crl")         --NB: this will cause an immediate entry to the debugger_loop
1329  end
1330end
1331
1332--}}}
1333--{{{  function dump()
1334
1335--shows the value of the given variable, only really useful
1336--when the variable is a table
1337--see dump debug command hints for full semantics
1338
1339function dump(v,depth)
1340  dumpvar(v,(depth or 1)+1,tostring(v))
1341end
1342
1343--}}}
1344--{{{  function debug.traceback(x)
1345
1346local _traceback = debug.traceback       --note original function
1347
1348--override standard function
1349debug.traceback = function(x)
1350  local assertmsg = _traceback(x)        --do original function
1351  pause(x)                               --let user have a look at stuff
1352  return assertmsg                       --carry on
1353end
1354
1355_TRACEBACK = debug.traceback             --Lua 5.0 function
1356
1357--}}}
1358
Note: See TracBrowser for help on using the repository browser.