[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
This chapter discusses how to debug Ada programs. An incorrect Ada program may be handled in three ways by the GNAT compiler:
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
GDB is a general purpose, platform-independent debugger that can be used to debug mixed-language programs compiled with GCC, and in particular is capable of debugging Ada programs compiled with GNAT. The latest versions of GDB are Ada-aware and can handle complex Ada data structures. The manual Debugging with GDB contains full details on the usage of GDB, including a section on its usage on programs. This manual should be consulted for full details. The section that follows is a brief introduction to the philosophy and use of GDB.
When GNAT programs are compiled, the compiler optionally writes debugging information into the generated object file, including information on line numbers, and on declared types and variables. This information is separate from the generated code. It makes the object files considerably larger, but it does not add to the size of the actual executable that will be loaded into memory, and has no impact on run-time performance. The generation of debug information is triggered by the use of the -g switch in the gnatgcc or gnatmake command used to carry out the compilations. It is important to emphasize that the use of these options does not change the generated code.
The debugging information is written in standard system formats that are used by many tools, including debuggers and profilers. The format of the information is typically designed to describe C types and semantics, but GNAT implements a translation scheme which allows full details about Ada types and variables to be encoded into these standard C formats. Details of this encoding scheme may be found in the file exp_dbug.ads in the GNAT source distribution. However, the details of this encoding are, in general, of no interest to a user, since GDB automatically performs the necessary decoding.
When a program is bound and linked, the debugging information is collected from the object files, and stored in the executable image of the program. Again, this process significantly increases the size of the generated executable file, but it does not increase the size of the executable program itself. Furthermore, if this program is run in the normal manner, it runs exactly as if the debug information were not present, and takes no more actual memory.
However, if the program is run under control of GDB, the debugger is activated. The image of the program is loaded, at which point it is ready to run. If a run command is given, then the program will run exactly as it would have if GDB were not present. This is a crucial part of the GDB design philosophy. GDB is entirely non-intrusive until a breakpoint is encountered. If no breakpoint is ever hit, the program will run exactly as it would if no debugger were present. When a breakpoint is hit, GDB accesses the debugging information and can respond to user commands to inspect variables, and more generally to report on the state of execution.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
The debugger can be launched directly and simply from emacs which allows
to browse and modify directly the source code during the debugging
session, See section 19.5 Ada Mode for emacs
. Here is described the basic use of
GDB is text mode.
The command to run GDB is
$ gnatgdb program |
where program
is the name of the executable file. This
activates the debugger and results in a prompt for debugger commands.
The simplest command is simply run
, which causes the program to run
exactly as if the debugger were not present. The following section
describes some of the additional commands that can be given to GDB.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
GDB contains a large repertoire of commands. The manual Debugging with GDB includes extensive documentation on the use of these commands, together with examples of their use. Furthermore, the command help invoked from within GDB activates a simple help facility which summarizes the available commands and their options. In this section we summarize a few of the most commonly used commands to give an idea of what GDB is about. You should create a simple program with debugging information and experiment with the use of these GDB commands on the program as you read through the following section.
set args arguments
set args
command is not needed if the program does not require arguments.
run
run
command causes execution of the program to start from the
beginning. If the program is already running, that is to say if you
are currently positioned at a breakpoint,
then a prompt will ask for confirmation that you want
to abandon the current execution and restart.
breakpoint location
file:linenumber
,
or it is the name of a subprogram. If you request that a breakpoint be set on
a subprogram that is overloaded, a prompt will ask you to specify on which of
those subprograms you want to breakpoint. You can also
specify that all of them should be breakpointed. If the program is run
and execution encounters the breakpoint, then the program
stops and GDB signals that the breakpoint was encountered by printing the
line of code before which the program is halted.
breakpoint exception name
print expression
continue
step
next
list
backtrace
up
up
can be used to
examine the contents of other active frames, by moving the focus up
the stack, that is to say from callee to caller, one frame at a time.
down
frame n
The above list is a very short introduction to the commands that GDB provides. Important additional capabilities, including conditional breakpoints, the ability to execute command sequences on a breakpoint, the ability to debug at the machine instruction level and many other features are described in detail in Debugging with GDB. Note that most commands can be abbreviated (for example, c for continue, bt for backtrace).
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
GDB supports a fairly large subset of Ada expression syntax, with some extensions. The philosophy behind the design of this subset is
Thus, for brevity, the debugger acts as if there were
implicit with
and use
clauses in effect for all user-written
packages, thus making it unnecessary to fully qualify most names with
their packages, regardless of context. Where this causes ambiguity,
GDB asks the user's intent.
For details on the supported Ada syntax Debugging with GDB.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
An important capability of GDB is the ability to call user-defined subprograms while debugging. This is achieved simply by entering a subprogram call statement in the form:
call subprogram-name (parameters) |
The keyword call
can be omitted in the normal case where the
subprogram-name
does not coincide with any of the predefined
GDB commands.
The effect is to invoke the given subprogram, passing it the list of parameters that is supplied. The parameters can be expressions and can include variables from the program being debugged. The subprogram must be defined at the library level within your program, and GDB will call the subprogram within the environment of your program execution (which means that the subprogram is free to access or even modify variables within your program).
The most important use of this facility is in allowing the inclusion of
debugging routines that are tailored to particular data structures
in your program. Such debugging routines can be written to provide a suitably
high-level description of an abstract type, rather than a low-level dump
of its physical layout. After all, the standard
GDB print
command only knows the physical layout of your
types, not their abstract meaning. Debugging routines can provide information
at the desired semantic level and are thus enormously useful.
For example, when debugging GNAT itself, it is crucial to have access to
the contents of the tree nodes used to represent the program internally.
But tree nodes are represented simply by an integer value (which in turn
is an index into a table of nodes).
Using the print
command on a tree node would simply print this integer
value, which is not very useful. But the PN routine (defined in file
treepr.adb in the GNAT sources) takes a tree node as input, and displays
a useful high level representation of the tree node, which includes the
syntactic category of the node, its position in the source, the integers
that denote descendant nodes and parent node, as well as varied
semantic information. To study this example in more detail, you might want to
look at the body of the PN procedure in the stated file.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
You can set breakpoints that trip when your program raises selected exceptions.
break exception
break exception name
break exception unhandled
info exceptions
info exceptions regexp
info exceptions
command permits the user to examine all defined
exceptions within Ada programs. With a regular expression, regexp, as
argument, prints out only those exceptions whose name matches regexp.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
GDB allows the following task-related commands:
info tasks
(gdb) info tasks ID TID P-ID Thread Pri State Name 1 8088000 0 807e000 15 Child Activation Wait main_task 2 80a4000 1 80ae000 15 Accept/Select Wait b 3 809a800 1 80a4800 15 Child Activation Wait a * 4 80ae800 3 80b8000 15 Running c |
In this listing, the asterisk before the first task indicates it to be the currently running task. The first column lists the task ID that is used to refer to tasks in the following commands.
break linespec task taskid
break linespec task taskid if ...
break ... thread ...
.
linespec specifies source lines.
Use the qualifier `task taskid' with a breakpoint command to specify that you only want GDB to stop the program when a particular Ada task reaches this breakpoint. taskid is one of the numeric task identifiers assigned by GDB, shown in the first column of the `info tasks' display.
If you do not specify `task taskid' when you set a breakpoint, the breakpoint applies to all tasks of your program.
You can use the task
qualifier on conditional breakpoints as
well; in this case, place `task taskid' before the
breakpoint condition (before the if
).
task taskno
This command allows to switch to the task referred by taskno. In particular, This allows to browse the backtrace of the specified task. It is advised to switch back to the original task before continuing execution otherwise the scheduling of the program may be perturbated.
For more detailed information on the tasking support Debugging with GDB.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
GNAT always uses code expansion for generic instantiation. This means that each time an instantiation occurs, a complete copy of the original code is made, with appropriate substitutions of formals by actuals.
It is not possible to refer to the original generic entities in GDB, but it is always possible to debug a particular instance of a generic, by using the appropriate expanded names. For example, if we have
procedure g is generic package k is procedure kp (v1 : in out integer); end k; package body k is procedure kp (v1 : in out integer) is begin v1 := v1 + 1; end kp; end k; package k1 is new k; package k2 is new k; var : integer := 1; begin k1.kp (var); k2.kp (var); k1.kp (var); k2.kp (var); end; |
Then to break on a call to procedure kp in the k2 instance, simply use the command:
(gdb) break g.k2.kp |
When the breakpoint occurs, you can step through the code of the instance in the normal manner and examine the values of local variables, as for other units.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
When presented with programs that contain serious errors in syntax or semantics, GNAT may on rare occasions experience problems in operation, such as aborting with a segmentation fault or illegal memory access, raising an internal exception, or terminating abnormally. In such cases, you can activate various features of GNAT that can help you pinpoint the construct in your program that is the likely source of the problem.
The following strategies are presented in increasing order of difficulty, corresponding to your programming skills and your familiarity with compiler internals.
gnatgcc
with the -gnatf
and -gnate
switches. The first
switch causes all errors on a given line to be reported. In its absence,
only the first error on a line is displayed.
The -gnate
switch causes errors to be displayed as soon as they
are encountered, rather than after compilation is terminated. If GNAT
terminates prematurely, the last error message displayed is likely to
pinpoint the culprit.
gnatgcc
with the -v (verbose)
switch. In this mode,
gnatgcc
produces ongoing information about the progress of the
compilation and provides the name of each procedure as code is
generated. This switch allows you to find which Ada procedure was being
compiled when it encountered a code generation problem.
gnatgcc
with the -gnatdc
switch. This is a GNAT specific
switch that does for the front-end what -v
does for the back end.
The system prints the name of each unit, either a compilation unit or
nested unit, as it is being analyzed.
gnatgdb
directly on the gnat1
executable. gnat1
is the
front-end of GNAT, and can be run independently (normally it is just
called from gnatgcc
). You can use gnatgdb
on gnat1
as you
would on a C program (but see section 20.1 The GNAT Debugger GDB for caveats). The
where
command is the first line of attack; the variable
lineno
(seen by print lineno
), used by the second phase of
gnat1
and by the gnatgcc
backend, indicates the source line at
which the execution stopped, and input_file name
indicates the name of
the source file.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
In order to examine the workings of the GNAT system, the following brief description of its organization may be helpful:
Ada
, as
defined in Annex A.
Interfaces
, as
defined in Annex B.
System
. This includes
both language-defined children and GNAT run-time routines.
GNAT
. These are useful
general-purpose packages, fully documented in their specifications. All
the other `.c' files are modifications of common gnatgcc
files.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Most compilers have internal debugging switches and modes. GNAT does also, except GNAT internal debugging switches and modes are not secret. A summary and full description of all the compiler and binder debug flags are in the file `debug.adb'. You must obtain the sources of the compiler to see the full detailed effects of these flags.
The switches that print the source of the program (reconstructed from the internal tree) are of general interest for user programs, as are the options to print the full internal tree, and the entity table (the symbol table information). The reconstructed source provides a readable version of the program after the front-end has completed analysis and expansion, and is useful when studying the performance of specific constructs. For example, constraint checks are indicated, complex aggregates are replaced with loops and assignments, and tasking primitives are replaced with run-time calls.
[ << ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |