[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

20. Running and Debugging Ada Programs

This chapter discusses how to debug Ada programs. An incorrect Ada program may be handled in three ways by the GNAT compiler:

  1. The illegality may be a violation of the static semantics of Ada. In that case GNAT diagnoses the constructs in the program that are illegal. It is then a straightforward matter for the user to modify those parts of the program.

  2. The illegality may be a violation of the dynamic semantics of Ada. In that case the program compiles and executes, but may generate incorrect results, or may terminate abnormally with some exception.

  3. When presented with a program that contains convoluted errors, GNAT itself may terminate abnormally without providing full diagnostics on the incorrect user program.

20.1 The GNAT Debugger GDB  
20.2 Running GDB  
20.3 Introduction to GDB Commands  
20.4 Using Ada Expressions  
20.5 Calling User-Defined Subprograms  
20.6 Breaking on Ada Exceptions  
20.7 Ada Tasks  
20.8 Debugging Generic Units  
20.9 GNAT Abnormal Termination  
20.10 Naming Conventions for GNAT Source Files  
20.11 Getting Internal Debugging Information  

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

20.1 The GNAT Debugger GDB

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] [ ? ]

20.2 Running GDB

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] [ ? ]

20.3 Introduction to GDB Commands

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
The arguments list above is a list of arguments to be passed to the program on a subsequent run command, just as though the arguments had been entered on a normal invocation of the program. The set args command is not needed if the program does not require arguments.

The 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
The breakpoint command sets a breakpoint, that is to say a point at which execution will halt and 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
A special form of the breakpoint command which breakpoints whenever exception name is raised. If name is omitted, then a breakpoint will occur when any exception is raised.

print expression
This will print the value of the given expression. Most simple Ada expression formats are properly handled by GDB, so the expression can contain function calls, variables, operators, and attribute references.

Continues execution following a breakpoint, until the next breakpoint or the termination of the program.

Executes a single line after a breakpoint. If the next statement is a subprogram call, execution continues into (the first statement of) the called subprogram.

Executes a single line. If this line is a subprogram call, executes and returns from the call.

Lists a few lines around the current source location. In practice, it is usually more convenient to have a separate edit window open with the relevant source file displayed. Successive applications of this command print subsequent lines. The command can be given an argument which is a line number, in which case it displays a few lines around the specified one.

Displays a backtrace of the call chain. This command is typically used after a breakpoint has occurred, to examine the sequence of calls that leads to the current breakpoint. The display includes one line for each activation record (frame) corresponding to an active subprogram.

At a breakpoint, 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.

Moves the focus of GDB down from the frame currently being examined to the frame of its callee (the reverse of the previous command),

frame n
Inspect the frame with the given number. The value 0 denotes the frame of the current breakpoint, that is to say the top of the call stack.

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] [ ? ]

20.4 Using Ada Expressions

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] [ ? ]

20.5 Calling User-Defined Subprograms

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] [ ? ]

20.6 Breaking on Ada Exceptions

You can set breakpoints that trip when your program raises selected exceptions.

break exception
Set a breakpoint that trips whenever (any task in the) program raises any exception.

break exception name
Set a breakpoint that trips whenever (any task in the) program raises the exception name.

break exception unhandled
Set a breakpoint that trips whenever (any task in the) program raises an exception for which there is no handler.

info exceptions
info exceptions regexp
The 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] [ ? ]

20.7 Ada Tasks

GDB allows the following task-related commands:

info tasks
This command shows a list of current Ada tasks, as in the following example:

   (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 ...
These commands are like the 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] [ ? ]

20.8 Debugging Generic Units

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
            v1 := v1 + 1;
         end kp;
      end k;

      package k1 is new k;
      package k2 is new k;

      var : integer := 1;

      k1.kp (var);
      k2.kp (var);
      k1.kp (var);
      k2.kp (var);

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] [ ? ]

20.9 GNAT Abnormal Termination

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.

  1. Run 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.

  2. Run 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.

  3. Run 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.
  4. Finally, you can start 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] [ ? ]

20.10 Naming Conventions for GNAT Source Files

In order to examine the workings of the GNAT system, the following brief description of its organization may be helpful:

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

20.11 Getting Internal Debugging Information

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] [ ? ]

This document was generated by Tom Bennet on August, 25 2000 using texi2html