MAL debuggerMAL debugger mk Tue, 03/30/2010 - 12:12
In practice it is hard to write a correct MAL program the first time around. Instead, it is more often constructed by trial-and-error. As long as there are syntax and semantic errors the MAL compiler provides a sufficient handle to proceed. Once it passes the compiler we have to resort to a debugger to assess its behavior.
Note, the MAL debugger described here can be used in conjunction with the textual interface client mclient only. The JDBC protocol does not permit passing through information that 'violates' the protocol.
BreakpointsBreakpoints mk Tue, 03/30/2010 - 12:14
A powerful mechanism for debugging a program is to set breakpoints during the debugging session. The breakpoints are designated by a target variable name, a [module.]function name, or a MAL line number (#<number>).
The snippet below illustrates the reaction to set a break point on assignment to variable 'i'.
mal>mdb.start(); #end main; mdb> mal>user.test(1); # user.test(1); mdb>break i breakpoint on 'i' not set mdb>n # io.print(i); mdb>break i mdb>c [ 1 ] # i := calc.*(i,2); mdb>
The breakpoints remain in effect over multiple function calls. They can be removed with the delete statement. A list of all remaining breakpoints is obtained with breakpoints.
The interpreter can be instructed to call the debugger as soon as an exception is raised. Simply add the instruction mdb.setCatch(true).
Debugger featuresDebugger features mk Tue, 03/30/2010 - 12:13
To ease debugging and performance monitoring, the MAL interpreter comes with a gdb-like debugger. An illustrative session elicits the functionality offered.
mal>function test(i:int):str; mal> io.print(i); mal> i:= i*2; mal> b:= bat.new(:int,:int); mal> bat.insert(b,1,i); mal> io.print(b); mal> return test:= "ok"; mal>end test; mal>user.test(1); [ 1 ] #-----------------# # h t # name # int int # type #-----------------# [ 1, 2 ]
The debugger can be entered at any time using the call mdb.start(). An overview of the available commands is readily available.
mal>mdb.start(); #mdb !end main; mdb>help next -- Advance to next statement continue -- Continue program being debugged catch -- Catch the next exception break [<var>] -- set breakpoint on current instruction or <var> delete [<var>] -- remove break/trace point <var> debug <int> -- set kernel debugging mask dot <obj> [<file>] -- generate the dependency graph step -- advance to next MAL instruction module -- display a module signatures atom -- show atom list finish -- finish current call exit -- terminate executionr quit -- turn off debugging list <obj> -- list current program block List <obj> -- list with type information span -- list the life span of variables var <obj> -- print symbol table for module optimizer <obj> -- display optimizer steps print <var> -- display value of a variable print <var> <cnt>[<first>] -- display BAT chunk info <var> -- display bat variable properties run -- restart current procedure where -- print stack trace down -- go down the stack up -- go up the stack trace <var> -- trace assignment to variables trap <mod>.<fcn> -- catch MAL function call in console help -- this message mdb>
The term <obj> is an abbreviation for a MAL operation <mod>.<fcn>, optionally extended with a version number, i.e. [<nr>]. The var denotes a variable in the current stack frame. Debugger commands may be abbreviated.
We walk our way through a debugging session, highlighting the effects of the debugger commands. The call to mdb.start() has been encapsulated in a complete MAL function, as shown by issuing the list command. A more detailed listing shows the binding to the C-routine and the result of type resolution.
mal>mdb.start(); #end main; mdb>l function user.main():int; mdb.start(); end main; mdb>L function user.main():int; # 0 (main:int) mdb.start(); # 1 MDBstart (_1:void) end main; # 2
The user module is the default place for function defined at the console. The modules loaded can be shown typeing the command 'module' (or 'm' for short). The function signatures become visible using the module and optionally the function name.
mdb>m alarm #command alarm.alarm(secs:int,action:str):void address ALARMsetalarm; #command alarm.ctime():str address ALARMctime; #command alarm.epilogue():void address ALARMepilogue; #command alarm.epoch():int address ALARMepoch; #command alarm.prelude():void address ALARMprelude; #command alarm.sleep(secs:int):void address ALARMsleep; #command alarm.time():int address ALARMtime; #command alarm.timers():bat[:str,:str] address ALARMtimers; #command alarm.usec():lng address ALARMusec; mdb>m alarm.sleep #command alarm.sleep(secs:int):void address ALARMsleep; mdb>
The debugger mode is left with a <return>. Any subsequent MAL instruction re-activates the debugger to await for commands. The default operation is to step through the execution using the 'next' ('n') or 'step' ('s) commands, as shown below.
mal>user.test(1); # user.test(1); mdb>n # io.print(i); mdb> [ 1 ] # i := calc.*(i,2); mdb> # b := bat.new(:int,:int); mdb>
The last instruction shown is next to be executed. The result can be shown using a print statement, which contains the location of the variable on the stack frame, its name, its value and type. The complete stack frame becomes visible with 'values' ('v') command:
# bat.insert(b,1,i); mdb> # io.print(b); mdb>v #Stack for 'test' size=32 top=11 # test = nil:str # i = 4:int # _2 = 0:int unused # _3 = 2:int constant # b = <tmp_1226>:bat[:int,:int] count=1 lrefs=1 refs=0 # _5 = 0:int type variable # _6 = nil:bat[:int,:int] unused # _7 = 1:int constant # _8 = 0:int unused # _9 = "ok":str constant
The variables marked 'unused' have been introduced as temporary variables, but which are not referenced in the remainder of the program. It also illustrates basic BAT properties, a complete description of which can be obtained using the 'info' ('i') command. A sample of the BAT content can be printed passing tuple indices, e.g. 'print b 10 10' prints the second batch of ten tuples.
InspectionInspection mk Tue, 03/30/2010 - 12:16
The debugger commands available for inspection of the program and symbol tables are:
- list (List) [<mod>.<fcn>['['<nr>']']]
- A listing of the current MAL block, or one designated by the <mod>.<fcn> is produced. The [<nr>] extension provides access to an element in the MAL block history. The alternative name 'List' also produces the type information.
- optimizer [<mod>.<fcn>['['<nr>']']]
- Gives an overview of the optimizer actions in the history of a SQL query. Intermediate results can be accessed using the list command.
- Lists the atoms currently known
- modules [<mod>]
- Lists the modules currently known. An optional <mod> argument produces a list of all signatures within the module identified.
- dot <mod>.<fcn>['['<nr>']'] [<file>]
- A dataflow diagram can be produced using the dot command. It expects a function identifier with an optional history index and produces a file for the Linux program dot, which can produce a nice, multi-page graph to illustrate plan complexity.
This example produces the user.main.dot in the current working directory. The program call
dot -Tpdf user-tst-0.dot -o user-tst-0.pdf
creates a PDF file with the graphs. The Linux pdfposter utility can be used to produce a proper printing. Alternatively, the Adobe reader professional can break it up into multiple pages.
Runtime statusRuntime status mk Tue, 03/30/2010 - 12:16
Part of the debugger functionality can also be used directly with MAL instructions. The execution trace of a snippet of code can be visualized encapsulation with mdb.setTrace(true) and mdb.setTrace(false). The following snippet shows the effect of patching the test case.
mal> function test(i:int):str; mal> mdb.setTrace(true); mal> io.print(i); mal> i:= i*2; mal> b:= bat.new(:oid,:int); mal> bat.insert(b,0@0,i); mal> io.print(b); mal> mdb.setTrace(false); mal> return test:= "ok"; mal> end test; mal> user.test(1); # io.print(i=1); [ 1 ] # i := calc.*(i=1,2); # b := bat.new(:oid,:int); # bat.insert(b=<tmp_1001>,0@0,i=2); # io.print(b=<tmp_1001>); #-----------------# # h t # name # void int # type #-----------------# [ 0@0, 2 ] mal>
It is also possible to activate the debugger from within a program using mdb.start(). It remains in this mode until you either issue a quit command, or the command mdb.stop() instruction is encountered. The debugger is only activated when the user can direct its execution from the client interface. Otherwise, there is no proper input channel and the debugger will run in trace mode.
The program listing functionality of the debugger is also captured in the MAL debugger module. The current code block can be listed using mdb.list() and mdb.List(). An arbitrary code block can be shown with mdb.list(module,function) and mdb.List(module,function). A BAT representation of the current function is return by mdb.getDefinition().
The symbol table and stack content, if available, can be shown with the operations mdb.var() and mdb.list(module,function) Access to the stack frames may be helpful in the context of exception handling. The operation mdb.getStackDepth() gives the depth and individual elements can be accessed as BATs using mdb.getStackFrame(n). The top stack frame is accessed using mdb.getStackFrame().