Debugging Tools

There are two debugging tools. The first is a
trace mechanism that can be used on the global functions in
the toplevel loop. The second tool is a debugger that is not
used in the normal toplevel loop. After a first program run it is
possible to go back to breakpoints, and to inspect values or to restart
certain functions with different arguments. This second tool only runs
under Unix, because it duplicates the running process via a
fork (see page ??).

Trace

The trace of a function is the list of the values of its
parameters together with its result in the course of a program run.

The trace commands are directives in the toplevel loop. They allow to
trace a function, stop its trace or to stop all active traces. These
three directives are shown in the table below.

#tracename

trace function name

#untracename

stop tracing function name

#untrace_all

stop all traces

Here is a first example of the definition of a function f:

# letfx=x+1;;val f : int -> int = <fun># f4;;- : int = 5

Now we will trace this function, so that its arguments and its return
value will be shown.

# #tracef;;f is now traced.# f4;;f <-- 4f --> 5- : int = 5

Passing of the argument 4 to f is shown, then the
function f calculates the desired value and the result is
returned and also shown. The arguments of a function call are
indicated by a left arrow and the return value by an arrow to the
right.

Functions of Several Arguments

Functions of several arguments (or functions returning a closure) are
also traceable. Each argument passed is shown. To distinguish the
different closures, the number of arguments already passed to the
closures is marked with a *. Let the function
verif_div take 4 numbers (a, b, q, r) corresponding to the
integer division: a = bq + r.

At each call of the function belongs_to the argument
4 and the list to search in are passed as arguments. When the
list becomes empty, the functions return false as a return
value which is passed along to each waiting recursive invocation.

The following example shows the section of the list when the element
searched for appears:

The Objective CAML toplevel loop can only show monomorphic types. Moreover,
it only keeps the inferred types of global declarations. Therefore,
after compilation of the expression belongs_to 3 [2;3;4], the
toplevel loop in fact no longer possesses any further type information about
the function belongs_to apart form the type
'a -> 'a list -> bool. The (monomorphic) types of 3
and [2;3;4] are lost, because the values do not keep any type
information: this is static typing. This is the reason why the trace
mechanism attributes the polymorphic types 'a and
'a list to the arguments of the function belongs_to
and does not show their values.

It is this absence of typing information in values that entails the
impossibility of constructing a generic print function of
type 'a -> unit.

Local Functions

Local functions cannot be traced for the same reasons as above, relating again
to static typing. Only global type declarations are kept in the
environment of the toplevel loop. Still the following programming
style is common:

The global function only calls on the local function, which does the
interesting part of the work.

Notes on Tracing

Tracing is actually the only multi-platform debugging tool. Its two
weaknesses are the absence of tracing information for local functions
and the inability to show the value of polymorphic parameters. This
strongly restricts its usage, mainly during the first steps with the language.

Debug

ocamldebug, is a debugger in the usual sense of the
word. It permits step-by-step execution, the insertion of breakpoints
and the inspection and modification of values in the environment.

Single-stepping a program presupposes the knowledge of what comprises
a program step. In imperative programming this is an easy
enough notion: a step corresponds (more or less) to a single
instruction of the language. But this definition does not make much
sense in functional programming; one instead speaks of program
events. These are applications, entries to functions, pattern
matching, a conditional, a loop, an element of a sequence, etc.

Warning

This tool only runs under Unix.

Compiling with Debugging Mode

The -g compiler option produces a .cmo file that allows
the generation of the necessary instructions for debugging. Only the
bytecode compiler knows about this option. It is necessary to set this
option during compilation of the files encompassing an
application. Once the executable is produced, execution in debug
mode can be accomplished with the following ocamldebug command:

ocamldebug [options] executable [arguments]

Take the following example file fact.ml which calculates the
factorial function:

Starting the Debugger

Once an executable is compiled with debug mode, it can be run in
this mode.

$ ocamldebug fact.exe
Objective Caml Debugger version 3.00
(ocd)

Execution Control

Execution control is done via program events. It is possible to go
forward and backwards by n program events, or to go forward or
backwards to the next breakpoint (or the nth breakpoint). A breakpoint
can be set on a function or a program event. The choice of language
element is shown by line and column number or the number of
characters. This locality may be relative to a module.

In the example below, a breakpoint is set at the fourth line of module
Main:

The initialisations of the module are done before the actual
program. This is the reason the breakpoint at line 4 occurs only after
5028 instructions.

We go forward or backwards in the execution either by program elements
or by breakpoints. run and reverse run the program just
to the next breakpoint. The first in the direction of program
execution, the second in the backwards direction. The step
command advanced by 1 or n program elements, entering into
functions, next steps over them. backstep and previous respectively do the same in the backwards direction. finish finally completes the current functions invocations, whereas
start returns to the program element before the function invocation.

To continue our example, we go forward to the breakpoint and then
execute three program instructions:

Access to the fields of a record or via the index of an array is
accepted by the printing commands.

(ocd) print x.contents
1 : int = -1

Execution Stack

The execution stack permits a visualization of the entanglement of
function invocations. The backtrace or bt command shows
the stack of calls. The up and down commands select the
next or preceding activation record. Finally, the frame command
gives a description of the current record.