12 Debugging DEC Ada Programs

12.1 Significant Supported Features

You can use Ladebug to debug Ada programs on the Digital UNIX operating system. Supported features include the following:

Some of these features are further discussed in this chapter. The chapter also explains Ada compiler options, debugging limitations, and specific debugging tasks.

12.2 Compiling and Linking for Debugging

To compile units for debugging, specify the -g compiler option on the ada command, as follows:

% ada -g reservations_.ada reservations.ada hotel.ada

The -g option automatically suppresses code optimization. Nonoptimized code is easier to debug (although it is possible to debug optimized code). Nonoptimized code more closely resembles your program as you wrote it.

Alternatively, if your program has been compiled once and then modified, you can recompile the program by entering the amake command, as follows:

% amake -C' -g' hotel

To link units for debugging, enter the ald command, as follows:

% ald -o hotel hotel

The ald command automatically copies debugging information into the executable file.

For information the amake and ald commands, see manpages amake(1) and ald(1) .

12.3 Debugging Multilanguage Programs

The debugger allows you to debug mixed-language programs. Program flow of control across functions written in different languages is transparent.

The debugger automatically identifies the language of the current function or code segment based on information embedded in the executable file. If program execution is suspended in an Ada function, the current language is Ada. If the program executes a C function, the current language becomes C.

The current language determines the valid expression syntax for the debugger. It also affects the semantics used to evaluate an expression.

The debugger sets the $lang variable to the language of the current function or code segment. By manually setting the debugger variable $lang, you can force the debugger to interpret expresssions used in commands by the rules and semantics of a particular language.


Note
C language syntax is used when $lang is set to Ada; and no thread-related commands are available.

12.4 Using Case-Insensitive Commands and Variable Names

When the $lang debugger variable is set to Ada, command names and program identifiers are case-insensitive.

The $lang variable is set to "Ada" at Ladebug startup if your program has been linked using the ald linker. In this case, the main routine appears in a pseudo-file with a file name of the following form:

ald_mmmmmmmm_nnnnnnnnnn.ada

Otherwise, $lang reflects the the language of the main routine. When the $lang debugger variable is set to a language other than Ada (which may occur if most, but not all, of your routines are written in languages other than Ada), command names and program identifiers follow the case conventions of the $lang setting.

12.5 Printing ISO Latin-1 Characters

The $ascii debugger variable allows you to print ASCII characters in the 7-bit ASCII character set or in the 8-bit Latin-1 character set (a superset of the 7-bit ASCII character set). The ISO Latin-1 standard calls for character representation in 8 bits (256 values), rather than 7 bits, so the extended character set can include additional characters, such as those commonly found in Western European languages. (This character set is not coextensive with the DEC Multinational Character Set, but is very similar.)

To choose a character set, define $ascii as follows:

The Ada CHARACTER data type can contain any of the Latin-1 characters.

12.6 Displaying the Source Code of Generic Units

The instantiation of a generic unit is the code corresponding to the generic unit itself. Ladebug displays the correct source code for the instantiation of a generic unit by recognizing the unique, argumented file names that the DEC Ada compiler records for generic units. For example:

generic                          -- declaration of generic unit
   type T is range <>;           --
procedure init (X: out T);       -- formal parameter within declaration

procedure init (X: out T) is
begin
   X := 0;
end;

with init;
procedure test is
      procedure int_init is new init(integer);
                                 -- Ada instantiation of generic unit
      I: integer;
begin
      int_init(I);
end;

In this example, when you step into int_init, you will see the source code that corresponds to the generic procedure init:

X := 0;

You will not see source code corresponding to the procedure call:

int_init(I);

12.7 Debugging Multiple Units in One Source File

In Ada, unlike some other languages, the basic compilation unit need not correspond to a source file. For example:

generic                          -- first compilation unit in file
   type T is range <>;           --
procedure init (X: out T);       --

procedure init (X: out T) is     -- second compilation unit in file
begin                            --
   X := 0;                       --
end;                             --

with init;
procedure test is                -- third compilation unit in file
   procedure int_init is new init(integer);
   I: integer;                   --
begin                            --
   int_init(I);                  --
end;                             --

In earlier versions of Ladebug, each compilation unit yielded a separate object file and debug symbol table, while referring to the same source file. However, new naming conventions now provide for the renaming of the source files for better correlation with object files and debug symbol tables, as follows:

You can expect to see these augmented file names when you enter commands that result in file-name output (for example, file, whereis, where ). This naming convention is a temporary solution and may change in future releases.

12.8 Debugging Ada Elaboration Code

In Ada, as in other languages, the initial entry point is the procedure main, but unlike other languages, main in Ada is not the top-level application procedure. Instead, the elaboration code, which contains initialization routines for Ada-specific constructs such as packages, is called from a fabricated main routine before the top-level application is called.

If you wish to debug elaboration code, you can set a breakpoint on main, step to elaboration code initialization routine calls, and step into these routines. To ignore elaboration code, you can set a breakpoint in the Ada subprogram.

12.9 Accessing Unconstrained Array Types

Accesses to unconstrained arrays are implemented as pointers to structures known as descriptors or dope vectors. For example:

procedure Dbg_30 is
   type A1 is access String;
   X1 : A1 := new String'("123");
begin
   null;
end;

When you enter the print command, the debugger displays the pointer address (the address of the first (lo1) component) and the values of the first and last components. For example:

(ladebug) p *X1
struct {
    pointer = 0x14000c620;
    lo1 = 1;
    hi1 = 3;
}

To examine individual components, use the dereferencing operator (->) as follows:

(ladebug) p X1->pointer[0]; p X1->pointer[2]
struct {
   value = '1';
}
struct {
   value = '3';
}

12.10 Accessing Incomplete Types Completed in Another Compilation Unit

The full type often appears in a different compilation unit than the access type, which makes values of these types difficult to examine. Except in cases where the full type is an array type, you can examine values by carefully setting scopes and explicit type conversion. For example:

package Tmp_Pkg is
   type A_T is private;
   X : A_T;
private
   type T;
   type A_T is access T;
end Tmp_Pkg;

package body Tmp_Pkg is
   type T is record C1, C2 : Integer; end record;
begin
   X := new T'(71,72);
end Tmp_Pkg;

with Tmp_Pkg;
procedure Tmp is
begin
   null;
   --
   -- (ladebug) whereis X
   -- "tmp_pkg_.ada"`X
   --
   -- (ladebug) p "/c/project/aosf_ft2/tmp_pkg_.ada"`X
   -- 0x14000c600
   --
   -- (ladebug) file tmp_pkg.ada
   --
   -- (ladebug) p * ( (T*) (0x14000c600) )
   -- struct {
   --         C1 = 71;
   --         C2 = 72;
   --     }
   --
end;

12.11 Limitations on Ladebug Support for DEC Ada

Ladebug and the Digital UNIX operating system support the DEC Ada language with certain limitations, which are described in the following sections.

12.11.1 Limitations for Expressions in Ladebug Commands

Expressions in Ladebug commands use C source language syntax for operators and expressions. Data is printed as the equivalent C data type.

Table 12-1 shows Ada expressions and the debugger equivalents.

Table 12-1 Ada Expressions and Debugger Equivalents

Ada Expression  Debugger Equivalent 
Name  See Section 12.4
Binary operations and unary operations  Only integer, floating, and Boolean expressions are easily expressed. 
a+b,-,*  a+b,-,* 
a/b  a/b 
a = b /= < <= > >=  a == b != < <= > >= 
a and b  a&&b 
a or b  a||b 
a rem b  a%b 
not (a=b)  !(a==b) 
- a  -a 
Qualified expressions  None. There is no easy way of evaluating subtype bounds. 
Type conversions  Only simple numeric conversions are supported, and the bounds checking cannot be done. Furthermore, float -> integer truncates rather than rounds. integer -> float
(ladebug) print (float) (2147483647)
2147483648.0
(ladebug) print (double) (2147483647)
2147483647.0
 
Attributes  None, but if E is an enumeration type with default representations for the values then E'PRED(X) is the same as x-1. E'SUCC(X) is the same as x+1 
p.all  *p (pointer reference) 
p.m  p -> m (member of an "access record" type) 

12.11.2 Limitations in Data Types

This section lists the limitation notes by data type. For more information on these types, with examples, see the Developing Ada Programs on Digital UNIX Systems manual. Also see the the DEC Ada release notes for detailed information on debugging.

All Types

The debugger, unlike the Ada language, allows out-of-bounds assignments to be performed.

Integer Types

If integer types of different sizes are mixed (for example, byte-integer and word-integer), the one with the smaller size is converted to the larger size.

Floating-Point Types

If integer and floating-point types are mixed in an expression, the debugger converts the integer type to a floating-point type.

The debugger displays floating-point values that are exact integers in integer literal format.

Fixed-Point Types

The debugger displays fixed-point values as real-type literals or as structures. The structure contains values for the sign and the mantissa. To display the structure's value, multiply the sign and mantissa values. For example:

procedure Tmp3 is
    type F is delta 0.1 range -3.0 .. 9.0;
    X : F := 1.0;
begin
    X := X+1.0;
end;

(ladebug) s
stopped at [Tmp3:5 0x1200023dc]
    5       X := X+1.0;

(ladebug) print X
struct {
    fixed_point, small = 0.625E-1 * mantissa = 32;
}

Enumeration Types

The debugger displays enumeration values as the actual enumeral or its position.

Enumeration values must be manually converted to 'pos values before you can use them as array indices.

Array Types

The debugger displays string array values in horizontal ASCII format, enclosed in quotation ("x") marks. A single component (character) is displayed within single quotation ('x') marks.

The debugger allows you to assign a component value to a single component; you cannot assign using an entire array or array aggregate.

Arrays whose components are neither a single bit nor a multiple of bytes are described to the debugger as structures; a print command displays only the first component of such arrays.

Records

The debugger cannot display record components whose offsets from the start of the record are not known at compile time.

For variant records, however, the debugger can display the entire record object that has been declared with the default variant value. The debugger allows you to print or assign a value to a component of a record variant that is not active.

Access Types

The debugger does not support allocators, so you cannot create new access objects with the debugger. When you specify the name of an access object, the debugger displays the memory location of the object it designates. You can examine the memory location value.

12.11.3 Limitations for Tasking Programs

When you debug Ada tasking programs, you use the debugger and the DEC Ada ada_debug routine.

12.12 Debugging Programs That Generates an Exception

Ada exceptions can be raised in the following cases:

When an exception occurs, you need to determine the following:

  1. Where the exception is being raised

  2. How the exception is being raised (by the raise statement or by language check)

  3. Why the exception occurred, by either:

The following sections give more detail.

Determining Where the Exception Is Being Raised

There are two ways to determine where an exception is being raised:

Determining How the Exception Is Being Raised

Having determined where the exception is occurring, examine your program code to see whether an explicit raise statement has caused the exception. If a raise statement does not appear, then the exception is the result of an Ada language check.

Determining Why the Exception Is Being Raised

If the exception is raised by an explicit raise statement, examine your code to determine why the raise statement was executed.

If the exception is raised by a language check, see Developing Ada Programs on Digital UNIX Systems for tips on pinpointing the error.

12.13 Debugging Optimized Programs

The DEC Ada compiler performs code optimizations by default, unless you specify the -g flag. Debugging optimized code is recommended only if unoptimized code is unavailable. It is extremely difficult to understand your program by examining the workings of its optimized form.

If you must debug optimized code, then note the following changes that optimization may make in your source code:

You may wish to make use of the following aids when debugging optimized code: