Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: code/branches/gamestate/data/lua/Debugger.lua @ 6624

Last change on this file since 6624 was 6624, checked in by rgrieder, 14 years ago

Added Debugger.lua by Dave Nichols of Match-IT Limited.
It is a command line debugger written in Lua.
You will have to disable the IOConsole upon startup if you want to use the Lua debugger ("—noIOConsole" will do the trick, or set the config value Core::bStartIOConsole_).

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