[ < ] | [ > ] | [ << ] | [ 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 gcc 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 glide
or
through its graphical interface: gvd
. It can also be used
directly in text mode. Here is described the basic use of GDB
in text mode. All the commands described below can be used in the
gvd
console window even though there is usually other more
graphical ways to achieve the same goals.
The command to run the graphical interface of the debugger is
$ gvd program |
The command to run GDB
in text mode is
$ gdb 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
GDB
will await further
commands. location is
either a line number within a file, given in the format 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
GDB
, so the expression
can contain function calls, variables, operators, and attribute references.
continue
step
next
list
backtrace
up
GDB
can display the values of variables local
to the current frame. The command 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
GDB
down from the frame currently being
examined to the frame of its callee (the reverse of the previous command),
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
GDB
should provide basic literals and access to operations for
arithmetic, dereferencing, field selection, indexing, and subprogram calls,
leaving more sophisticated computations to subprograms written into the
program (which therefore may be called from GDB
).
GDB
user.
GDB
user.
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, see 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] | [ ? ] |
When you use the next
command in a function, the current source
location will advance to the next statement as usual. A special case
arises in the case of a return
statement.
Part of the code for a return statement is the "epilog" of the function. This is the code that returns to the caller. There is only one copy of this epilog code, and it is typically associated with the last return statement in the function if there is more than one return. In some implementations, this epilog is associated with the first statement of the function.
The result is that if you use the next
command from a return
statement that is not the last return statement of the function you
may see a strange apparent jump to the last return statement or to
the start of the function. You should simply ignore this odd jump.
The value returned is always that from the first return statement
that was stepped through.
[ < ] | [ > ] | [ << ] | [ 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, see 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, terminating abnormally, or failing to terminate at all. 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 experience in using GNAT and your familiarity with compiler internals.
gcc
with the `-gnatf'. This 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 `-gnatdO' switch causes errors to be displayed as soon as they are encountered, rather than after compilation is terminated. If GNAT terminates prematurely or goes into an infinite loop, the last error message displayed may help to pinpoint the culprit.
gcc
with the `-v (verbose)' switch. In this
mode, gcc
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.
gcc
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.
gdb
directly on the gnat1
executable. gnat1
is the
front-end of GNAT, and can be run independently (normally it is just
called from gcc
). You can use gdb
on gnat1
as you
would on a C program (but see section 24.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 gcc
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 gcc
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.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Traceback is a mechanism to display the sequence of subprogram calls that leads to a specified execution point in a program. Often (but not always) the execution point is an instruction at which an exception has been raised. This mechanism is also known as stack unwinding because it obtains its information by scanning the run-time stack and recovering the activation records of all active subprograms. Stack unwinding is one of the most important tools for program debugging.
The first entry stored in traceback corresponds to the deepest calling level, that is to say the subprogram currently executing the instruction from which we want to obtain the traceback.
Note that there is no runtime performance penalty when stack traceback is enabled, and no exception is raised during program execution.
24.13.1 Non-Symbolic Traceback 24.13.2 Symbolic Traceback
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Note: this feature is not supported on all platforms. See `GNAT.Traceback spec in g-traceb.ads' for a complete list of supported platforms.
24.13.1.1 Tracebacks From an Unhandled Exception 24.13.1.2 Tracebacks From Exception Occurrences 24.13.1.3 Tracebacks From Anywhere in a Program
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
A runtime non-symbolic traceback is a list of addresses of call instructions.
To enable this feature you must use the `-E'
gnatbind
's option. With this option a stack traceback is stored as part
of exception information. You can retrieve this information using the
addr2line
tool.
Here is a simple example:
procedure STB is procedure P1 is begin raise Constraint_Error; end P1; procedure P2 is begin P1; end P2; begin P2; end STB; |
$ gnatmake stb -bargs -E $ stb Execution terminated by unhandled exception Exception name: CONSTRAINT_ERROR Message: stb.adb:5 Call stack traceback locations: 0x401373 0x40138b 0x40139c 0x401335 0x4011c4 0x4011f1 0x77e892a4 |
As we see the traceback lists a sequence of addresses for the unhandled
exception CONSTRAINT_ERROR
raised in procedure P1. It is easy to
guess that this exception come from procedure P1. To translate these
addresses into the source lines where the calls appear, the
addr2line
tool, described below, is invaluable. The use of this tool
requires the program to be compiled with debug information.
$ gnatmake -g stb -bargs -E $ stb Execution terminated by unhandled exception Exception name: CONSTRAINT_ERROR Message: stb.adb:5 Call stack traceback locations: 0x401373 0x40138b 0x40139c 0x401335 0x4011c4 0x4011f1 0x77e892a4 $ addr2line --exe=stb 0x401373 0x40138b 0x40139c 0x401335 0x4011c4 0x4011f1 0x77e892a4 00401373 at d:/stb/stb.adb:5 0040138B at d:/stb/stb.adb:10 0040139C at d:/stb/stb.adb:14 00401335 at d:/stb/b~stb.adb:104 004011C4 at /build/.../crt1.c:200 004011F1 at /build/.../crt1.c:222 77E892A4 in ?? at ??:0 |
The addr2line
tool has several other useful options:
--functions
--demangle=gnat
$ addr2line --exe=stb --functions --demangle=gnat 0x401373 0x40138b 0x40139c 0x401335 0x4011c4 0x4011f1 00401373 in stb.p1 at d:/stb/stb.adb:5 0040138B in stb.p2 at d:/stb/stb.adb:10 0040139C in stb at d:/stb/stb.adb:14 00401335 in main at d:/stb/b~stb.adb:104 004011C4 in <__mingw_CRTStartup> at /build/.../crt1.c:200 004011F1 in <mainCRTStartup> at /build/.../crt1.c:222 |
From this traceback we can see that the exception was raised in
`stb.adb' at line 5, which was reached from a procedure call in
`stb.adb' at line 10, and so on. The `b~std.adb' is the binder file,
which contains the call to the main program.
See section 4.1 Running gnatbind
. The remaining entries are assorted runtime routines,
and the output will vary from platform to platform.
It is also possible to use GDB
with these traceback addresses to debug
the program. For example, we can break at a given code location, as reported
in the stack traceback:
$ gdb -nw stb Furthermore, this feature is not implemented inside Windows DLL. Only the non-symbolic traceback is reported in this case. (gdb) break *0x401373 Breakpoint 1 at 0x401373: file stb.adb, line 5. |
It is important to note that the stack traceback addresses do not change when debug information is included. This is particularly useful because it makes it possible to release software without debug information (to minimize object size), get a field report that includes a stack traceback whenever an internal bug occurs, and then be able to retrieve the sequence of calls with the same program compiled with debug information.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Non-symbolic tracebacks are obtained by using the `-E' binder argument.
The stack traceback is attached to the exception information string, and can
be retrieved in an exception handler within the Ada program, by means of the
Ada95 facilities defined in Ada.Exceptions
. Here is a simple example:
with Ada.Text_IO; with Ada.Exceptions; procedure STB is use Ada; use Ada.Exceptions; procedure P1 is K : Positive := 1; begin K := K - 1; exception when E : others => Text_IO.Put_Line (Exception_Information (E)); end P1; procedure P2 is begin P1; end P2; begin P2; end STB; |
This program will output:
$ stb Exception name: CONSTRAINT_ERROR Message: stb.adb:12 Call stack traceback locations: 0x4015e4 0x401633 0x401644 0x401461 0x4011c4 0x4011f1 0x77e892a4 |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
It is also possible to retrieve a stack traceback from anywhere in a
program. For this you need to
use the GNAT.Traceback
API. This package includes a procedure called
Call_Chain
that computes a complete stack traceback, as well as useful
display procedures described below. It is not necessary to use the
`-E gnatbind' option in this case, because the stack traceback mechanism
is invoked explicitly.
In the following example we compute a traceback at a specific location in
the program, and we display it using GNAT.Debug_Utilities.Image
to
convert addresses to strings:
with Ada.Text_IO; with GNAT.Traceback; with GNAT.Debug_Utilities; procedure STB is use Ada; use GNAT; use GNAT.Traceback; procedure P1 is TB : Tracebacks_Array (1 .. 10); -- We are asking for a maximum of 10 stack frames. Len : Natural; -- Len will receive the actual number of stack frames returned. begin Call_Chain (TB, Len); Text_IO.Put ("In STB.P1 : "); for K in 1 .. Len loop Text_IO.Put (Debug_Utilities.Image (TB (K))); Text_IO.Put (' '); end loop; Text_IO.New_Line; end P1; procedure P2 is begin P1; end P2; begin P2; end STB; |
$ gnatmake -g stb $ stb In STB.P1 : 16#0040_F1E4# 16#0040_14F2# 16#0040_170B# 16#0040_171C# 16#0040_1461# 16#0040_11C4# 16#0040_11F1# 16#77E8_92A4# |
You can then get further information by invoking the addr2line
tool as described earlier (note that the hexadecimal addresses
need to be specified in C format, with a leading "0x").
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
A symbolic traceback is a stack traceback in which procedure names are associated with each code location.
Note that this feature is not supported on all platforms. See `GNAT.Traceback.Symbolic spec in g-trasym.ads' for a complete list of currently supported platforms.
Note that the symbolic traceback requires that the program be compiled with debug information. If it is not compiled with debug information only the non-symbolic information will be valid.
24.13.2.1 Tracebacks From Exception Occurrences 24.13.2.2 Tracebacks From Anywhere in a Program
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
with Ada.Text_IO; with GNAT.Traceback.Symbolic; procedure STB is procedure P1 is begin raise Constraint_Error; end P1; procedure P2 is begin P1; end P2; procedure P3 is begin P2; end P3; begin P3; exception when E : others => Ada.Text_IO.Put_Line (GNAT.Traceback.Symbolic.Symbolic_Traceback (E)); end STB; |
$ gnatmake -g .\stb -bargs -E -largs -lgnat -laddr2line -lintl $ stb 0040149F in stb.p1 at stb.adb:8 004014B7 in stb.p2 at stb.adb:13 004014CF in stb.p3 at stb.adb:18 004015DD in ada.stb at stb.adb:22 00401461 in main at b~stb.adb:168 004011C4 in __mingw_CRTStartup at crt1.c:200 004011F1 in mainCRTStartup at crt1.c:222 77E892A4 in ?? at ??:0 |
In the above example the ".\" syntax in the gnatmake
command
is currently required by addr2line
for files that are in
the current working directory.
Moreover, the exact sequence of linker options may vary from platform
to platform.
The above `-largs' section is for Windows platforms. By contrast,
under Unix there is no need for the `-largs' section.
Differences across platforms are due to details of linker implementation.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
It is possible to get a symbolic stack traceback
from anywhere in a program, just as for non-symbolic tracebacks.
The first step is to obtain a non-symbolic
traceback, and then call Symbolic_Traceback
to compute the symbolic
information. Here is an example:
with Ada.Text_IO; with GNAT.Traceback; with GNAT.Traceback.Symbolic; procedure STB is use Ada; use GNAT.Traceback; use GNAT.Traceback.Symbolic; procedure P1 is TB : Tracebacks_Array (1 .. 10); -- We are asking for a maximum of 10 stack frames. Len : Natural; -- Len will receive the actual number of stack frames returned. begin Call_Chain (TB, Len); Text_IO.Put_Line (Symbolic_Traceback (TB (1 .. Len))); end P1; procedure P2 is begin P1; end P2; begin P2; end STB; |
[ << ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |