Dbg – an easy-to-use debugger

Dr. David Bailey, Salford Software
david.bailey@salfordsoftware.co.uk

Introduction

This paper describes the rationale behind Dbg, an interactive debugger for Mathematica code 
(MathSource item 0211-037). This package is designed to make debugging easy and effective for beginner Mathematica programmers - particularly those who prefer to program in a C or Fortran idiom.
 
The built-in debug facilities (Trace, TracePrint, TraceScan, etc.) are rather hard to use effectively on anything but toy examples. One indication of the lack of use of these facilities is the fact that version 4.1 actually contains a broken version of TraceScan - the 4-argument form breaks the kernel!

TraceScan[F, 1, _, G]

A major problem with the Trace family of commands is that  by default they generate far too much information and although it is possible to restrict the output by means of a pattern, this all too frequently hides the very information being sought. Dbg avoids printing steps that are unlikely to be of interest. Thus it does not print trivial evaluations, such as 'evaluations' of constants or symbolic objects which evaluate to themselves. Dbg also prints constructs such as While, Module, etc. in an abbreviated form which does not include the (typically large) body. Because Dbg is designed to be friendly to the beginner Mathematica programmer it has a default option that limits its output to 10 steps in the first instance to avoid overwhelming the naive user. This limit can be raised as required and it is also possible to specify that some number of steps should be executed silently before debugging begins.

Unlike the built-in commands, Dbg uses the facilities of the notebook frontend to help the user interpret the output from a Dbg run. In particular, the output contains embedded button objects which enable the user to navigate between a (sub) calculation and its ultimate result. Another embedded button navigates outward to the level that invoked the calculation.

Since debugging is often best performed in an interactive fashion, Dbg supplies a palette which enables single stepping and the setting of breakpoints as the calculation progresses.

Many (flawed) Mathematica programs generate one or more error messages as they execute. Dbg assumes that any unsuppressed error message is a mistake and terminates the program at once with a traceback. This traceback is often the only information a programmer needs to resolve his problem.

One problem with Dbg is the fact that because it must perform so much work in the background, programs run very much more slowly under Dbg. One section of this paper will suggest extensions to the Kernel which could reduce or eliminate this overhead.

A simple example

Here is a naive piece of Mathematica code. The flaw is obvious, but try to remember your first fumblings at Mathematica programming!

test[k_]:=Module[{},
	While[k>0,
        	Print[k];
        	k--;
    	]
	]

Here is the result of simply executing the above function. The program generates an error and enters an infinite loop:

test[5]

Here is the result of using TracePrint. Obviously, the output could have been restricted in various ways, but if the user does not understand what is wrong, this is hard to do effectively. Again the process looped and had to be aborted.

test[5]//TracePrint

<<Many cells omitted here>>

Here is the result of using Dbg with default options. Note that every step has been printed with buttons that help navigate the information. For example, pressing the '?' button on line 4 highlights lines 4 and 5, indicating that line 5 is the result of evaluating line 4 (obviously, an expression and its evaluation can be separated by many steps). Likewise, pressing the '↑' button will highlight the While loop that produced step 4. Note also, that the Module and While constructs are printed in abbreviated form and so do not clutter the output. Dbg output is printed in pink so as to distinguish it clearly from other output generated by the code. The basic fault – that we end up trying to change the constant 5 – is clearly visible, as is the fact that it is this line which generates the error message and terminates the program.

test[5] // Dbg[]

Examination of the above example shows that Dbg is actually a function which returns a debugging function! This may seem odd, but it means that Dbg can always be appended to the end of a line, even when it requires options, for example:

foo[k] // Dbg[MaxSteps->100]

This helps to separate users code from the details of debugger switches, and is obviously useful when debugging is complete and Dbg must be removed.

Deciding what not to print

A crucial aspect of Dbg is that it does not print out every step. Many of the steps which TracePrint exposes only serve to clutter the output and confuse the beginner. What, for example, does it mean to evaluate the While symbol! A naive user might well conclude that he had used While as a variable by mistake.

Dbg operates using TraceScan and thus obtains a HoldForm representation of every step before it is evaluated. The following evaluations are considered trivial and are not printed or counted in the step numbering:

• Evaluations of numbers or number-like objects such as DirectedInfinity[1].
• Evaluations of any atom which does not have a value. This means that for example While, Pi, and X would not be printed (assuming X was being used as an algebraic variable and did not have a value).
• Evaluations of lists. This does not preclude the printing of the evaluation of individual elements of a list if these are non-trivial, but it prevents the repeated printing of large vectors. For example:

{1,2+3,4} // Dbg[]

• Evaluations of expressions whose heads are explicitly excluded by the ExcludeHeads option, or which are not explicitly included when the ShowHeads option is used.

• By default, expressions whose heads belong to the private parts of other packages are not shown. This can greatly reduce the irrelevant Dbg output for code that contains calls to packages (including some kernel functions). Dbg printout from within packages can be enabled if required.

• For awkward cases the FilterHeads option allows the user to specify a predicate which is applied to each head to determine whether to print the corresponding expression. For example, in programs involving several packages the predicate might use the Context function to test each head to decide if it required printing.

Infinite loops

Many programming bugs manifest themselves as infinite loops. This is just as true of Mathematica code. Here is a trivial loop executed under Dbg. Since we use the default options, Dbg prints 10 steps and then silently attempts to complete the calculation. However, when the Dbg package is loaded it displays a small palette of options. This includes a button which can be used to abort the process with a traceback:

k=-1;
While[k != 0,
foo[k];
k--;
] //Dbg[]

When the 'Abort process' button is pressed, Dbg first allows a few steps to be printed – which often helps to identify what is going on – and then aborts with a traceback.

More control

Debugging real examples usually involves several experiments, gradually zooming in on the defective code. The following options let you control the behaviour of Dbg and the output that it generates:

({{MaxSteps, 10, \(Maximum\ number\ of\ intermediate\ steps\ to\ \(\(print\)\(.\)\)\)}, {ShowHeads, All, Head or list of heads .  
              Print only expressions 

          that match one of these.}, {ExcludeHeads, None, \(List\ of\ heads\ of\ expressions\ not\ to\ \(\(print\)\(.\)\)\)}, {ContinueExecution, True, \(Whether\ to\ continue\ execution\ after\ print\ 

          limit\ \(\(exceeded\)\(.\)\)\)}, {StartStep, 1, \(Number\ of\ step\ at\ which\ printing\ \(\(begins\)\(.\)\(\ \)\)\)}, {TriggerOn, None, \(Start\ printing\ after\ this\ pattern\ is\ \(\(reached\)\(.\)\)\)}, {TriggerOff, None, \(Stop\ printing\ after\ this\ pattern\ has\ 

          been\ \(\(reached\)\(.\)\)\)}, {ShowLevels, False, \(Show\ the\ level\ on\ the\ stack\ of\ each\ expression\ 

          \(\((advanced\ feature)\)\(.\)\)\)}, {SuppressPackages, True, \(Do\ not\ print\ steps\ inside\ \(\(packages\)\(.\)\(\ \)\)\)}, {HiddenFunctions, None, \(List\ of\ functions\ whose\ inner\ workings\ are\ 

          not\ of\ \(\(interest\)\(.\)\)\)}, {AbortStep, ∞, \(Abort\ with\ traceback\ after\ this\ step\ \(\(number\)\(.\)\)\)}, {Step, False, \(Suspend\ the\ program\ at\ the\ first\ step\ so\ as\ to

          use\ the\ breakpoint\ \(\(facilities\)\(.\)\)\)}, {BreakAt, Null, Pattern to use as a breakpoint .  
              This option is

          
          usually supplied using the  :>  
            operator to

          prevent the evaluation of the pattern itself.}, {BreakFail, False, \(Whether\ to\ use\ the\ breakpoint\ mechanism\ if\ the\ 

          
          program\ is\ aborted\ by\ button\ or\ generates\ an\ 

          error\ \(\(message\)\(.\)\)\)}, {AbortStepCount, 10, \(Number\ of\ steps\ to\ print\ after\ \ the\ Abort\ 

          button\ is\ pressed\ and\ before\ the\ process\ 

          goes\ into\ traceback\ and\ aborts\)}, {FilterHeads, Null, \(User - 
            supplied\ predicate\ which\ takes\ a\ head\ as

          
          argument\ and\ returns\ True\ if\ Dbg\ should\ display\ 

          expressions\ with\ that\ \(\(head\)\(.\)\)\)}})

Each of these options can be set as Dbg is used, or using SetOptions.

Using colour

The Dbg palette provides a button to colour a selected part of a program together with another button to remove the colour when not required. A coloured expression is represented in such a way that when the Dbg package is not loaded the colour has no effect.

If the Dbg package is loaded but not actually used to execute the expression, then coloured expressions are printed as they are encountered (but otherwise behave normally) – as illustrated below.

test[kk_]:=Module[{k = kk, ans = 1},
While[k > 0,
ans=ans (y + k);
k--;
];
ans
]

test[4]

When executed under Dbg, coloured expressions also print, as in the above example, but in addition they can form the basis for breakpoints as explained in the next section.

Stepping and breakpoint facilities

Debuggers for traditional programming languages such as C and Fortran are usually designed around the concept of setting breakpoints and/or single stepping the code. In my experience, this style of debugging becomes less and less useful as the code becomes more complicated and recursive. However, many Mathematica programmers have a Fortran background and like to step through code to discover how it works. The simplest way to proceed is to use the Step option on Dbg and then single-step the resultant code:

Map[Sqrt,{1,4,7+9}] // Dbg[Step->True]

In this example the output is generated one line at a time as the 'Step' palette is pressed. Eventually the 'Abort process' button is pressed to discard the rest of the run.

Using colour it is possible to set breakpoints. Consider the test program from the previous section and assume that it has been entered as defined above with the coloured expression. Now we can set up Dbg in single step mode and then press the 'Colour break' palette to run the program until a coloured expression is encountered:

test[4] // Dbg[Step -> True]

Whenever a program is suspended the user is free to execute any Mathematica command (except running a nested copy of Dbg) or to use any of the bottom set of palette buttons. These allow for stepping, resuming or setting break points. It is also possible to obtain a traceback (as if the program had aborted) without actually aborting the program. The 'Abort' button is used to abandon the program and return to the top level.

Improving the performance of Dbg

Perhaps the only fundamental drawback to using Dbg is that it inevitably slows the program down many-fold. This is because each step in the user's code requires the execution of many steps inside Dbg. Clearly, when Dbg is generating output this overhead is totally unimportant, however when the program must run silently for a while this can be a problem. Clearly, if some or all of this silent part of Dbg (or equivalent facilities) were added to the Mathematica kernel user programs could execute at almost full speed while being debugged.

One particular problem concerns the 'Abort process' button. Clearly the ability to abort a program that has gone into a loop – and get some useful information from it – is extremely valuable. Unfortunately, this task has to be done very indirectly. Normally, if a button in a palette is pressed while a kernel task is in progress, then the new action is queued until the kernel is idle. The code for the 'Abort process' button clearly cannot do this! The 'Abort process' button therefore changes the state of an innocuous Frontend option –  the StartupSound boolean, which fortunately it can do without using the kernel. Dbg queries the state of this to determine if the user has requested a program abort. Because communication with the Frontend is quite slow, this check is only made every 500 steps. Usually this works quite well, however if the program being debugged contains some exceptionally slow steps – e.g. arithmetic on very large integers – there could be a substantial wait before the 'Abort process' button works. Clearly Dbg could read the time each step, but this too would introduce extra overhead at each step of the program. Note that although Dbg sets the state of StartupSound back the way it found it, the above use of StartupSound is rather clumsy. It would be nice if the Frontend offered a few dummy variables that could be set in this way.

Much of the code in Dbg which must be executed for each program step is of a book-keeping nature – testing flags, incrementing counters, etc. – exactly the kind of code which is least efficient. Again, if some of this could be embedded inside the kernel a substantial speed improvement could be realised.

Summary

This paper has outlined just some of the features of Dbg. It is hoped that in addition to illustrating some of the features of the Dbg debugger, it will also stimulate a discussion about what sort of debug facilities would be useful inside Mathematica. It is felt that the debug facilities of Mathematica have not kept pace with the rest of the package and could offer the programmer much more help.