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.
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)
.
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.
$lang
is set to Ada; and no thread-related commands
are available.
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.
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:
$ascii
is set to 1, Ladebug prints the
7-bit ASCII character set characters. All other alphanumerics
appear in octal notation. (This is the default.)
$ascii
is set to 0, Ladebug prints the
Latin-1 character set characters. All other alphanumerics appear
in octal notation.
The Ada CHARACTER data type can contain any of the Latin-1 characters.
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);
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:
program-name.ada
program-name.ada~nnn~decada_
XXXXXXXX
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.
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.
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'; }
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;
Ladebug and the Digital UNIX operating system support the DEC Ada language with certain limitations, which are described in the following sections.
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.
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) |
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.
The debugger, unlike the Ada language, allows out-of-bounds assignments to be performed.
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.
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.
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; }
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.
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.
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.
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.
When you debug Ada tasking programs, you use the debugger and the
DEC Ada ada_debug
routine.
Ada exceptions can be raised in the following cases:
exception-name;
statements
in program code
When an exception occurs, you need to determine the following:
raise
statement or by language check)
raise
statement.
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:
This method is not always helpful, especially if the code has been optimized. The raising of an exception terminates the current sequence of instructions, and the raising of some exceptions (for example, arithmetic traps) is delayed, so that the PC rests on an instruction just before or just after the instruction causing the exception.
You can intercept all exceptions being raised by your program by
using the Ladebug command stop in exc_dispatch_exception.
This sets a breakpoint in the fundamental run-time system
routine that raises all exceptions.
When Ladebug stops in this routine, a where
command
will show you the place where the exception was raised.
(ladebug) where >0 0x120007f0c in Dbg_24a$ELAB(=0x14000e400, =0x14000e3c8) dbg_24a.ada:13
You can examine more of the adjacent instructions by using the <expression>/i command.
(ladebug) 0x120007f0c-4 /2i [Dbg_24a$ELAB:13, 0x120007f08] ornot zero, zero, t1 *[Dbg_24a$ELAB:13, 0x120007f0c] srl t1, 0x21, t1
If you intercept an exception other than the one you intended to catch, you can continue the program's execution and catch the next one. If there are many exceptions before the one you wish to catch, you need to set a breakpoint closer to the raising of the exception, then get to it before starting to intercept exceptions.
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.
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:
volatile
pragma. This pragma suppresses
some of the optimization associated with a particular variable,
forcing its location or its associated fetch and store
instructions to be more predictable.