Document revision date: 30 March 2001

## OpenVMS Debugger Manual

Previous | Contents | Index

## Chapter 14
Debugging Special Cases

This chapter presents debugging techniques for special cases that are not covered elsewhere in this manual:

- Optimized code
- Screen-oriented programs
- Multilanguage programs
- Stack corruption
- Exceptions and condition handlers
- Exit handlers
- AST-driven programs
- Translated images

## 14.1 Debugging Optimized Code

By default, many compilers optimize the code they produce so that the program executes faster. With optimization, invariant expressions are removed from DO loops so that they are evaluated only once at run time; some memory locations might be allocated to different variables at different points in the program, and some variables might be eliminated so that you no longer have access to them while debugging.

The net result is that the code that is executing as you debug might not match the source code displayed in a screen-mode source display (see Section 7.4.1) or in a source listing file.

To avoid the problems of debugging optimized code, many compilers allow you to specify the /NOOPTIMIZE (or equivalent) command qualifier at compile time. Specifying this qualifier inhibits most compiler optimization and thereby reduces discrepancies between the source code and executable code caused by optimization.

If this option is not available to you, or if you have a definite need to debug optimized code, read this section. It describes the techniques for debugging optimized code and gives some typical examples of optimized code to show the potential causes of confusion. It also describes some features you can use to reduce the confusion inherent in debugging optimized code.

In order to take advantage of the features that improve the ability to debug optimized code, you need an up-to-date version of your language compiler. For definitive information about the necessary version of your compiler, please see your compiler release notes or other compiler documentation.

Note that about one-third more disk space is needed for debugging optimized code, to accommodate the increased image size.

When debugging optimized code, use a screen-mode instruction display, such as the predefined display INST, to show the decoded instruction stream of your program (see Section 7.4.4). An instruction display shows the exact code that is executing.

In screen mode, pressing KP7 places the SRC and INST displays side by side for easy comparison. Alternatively, you can inspect a compiler-generated machine-code listing.

In addition, to execute the program at the instruction level and examine instructions, use the techniques described in Section 4.3.

Using these methods, you should be able to determine what is happening at the executable code level and be able to resolve the discrepancy between source display and program behavior.

### 14.1.1 Eliminated Variables

A compiler might optimize code by eliminating variables, either permanently or temporarily at various points during execution. For example, if you try to examine a variable X that no longer is accessible because of optimization, the debugger might display one of the following messages:

    %DEBUG-W-UNALLOCATED, entity X was not allocated in memory
                          (was optimized away)

    %DEBUG-W-NOVALATPC, entity X does not have a value at the
                        current PC

The following Pascal example shows how this could happen:

    PROGRAM DOC(OUTPUT);
       VAR
          X,Y: INTEGER;
       BEGIN
          X := 5;
          Y := 2;
          WRITELN(X*Y);
       END.

If you compile this program with the /NOOPTIMIZE (or equivalent) qualifier, you obtain the following (normal) behavior when debugging:

    $ PASCAL/DEBUG/NOOPTIMIZE DOC
    $ LINK/DEBUG DOC
    $ DEBUG/KEEP
       .
       .
       .
    DBG> RUN DOC
       .
       .
       .
    DBG> STEP
    stepped to DOC\%LINE 5
         5:         X := 5;
    DBG> STEP
    stepped to DOC\%LINE 6
         6:         Y := 2;
    DBG> STEP
    stepped to DOC\%LINE 7
         7:         WRITELN(X*Y);
    DBG> EXAMINE X,Y
    DOC\X:  5
    DOC\Y:  2
    DBG>

If you compile the program with the /OPTIMIZE (or equivalent) qualifier, because the values of X and Y are not changed after the initial assignment, the compiler calculates X*Y, stores that value (10), and does not allocate storage for X or Y. Therefore, after you start debugging, a STEP command takes you directly to line 7 rather than line 5. Moreover, you cannot examine X or Y:

    $ PASCAL/DEBUG/OPTIMIZE DOC
    $ LINK/DEBUG DOC
    $ DEBUG/KEEP
       .
       .
       .
    DBG> RUN DOC
       .
       .
       .
    DBG> EXAMINE X,Y
    %DEBUG-W-UNALLOCATED, entity X was not allocated in memory
                          (was optimized away)
    DBG> STEP
    stepped to DOC\%LINE 7
         7:         WRITELN(X*Y);
    DBG>

On VAX processors, to see what values are being used in your optimized program, use the EXAMINE/OPERAND .%PC command to display the machine code at the current PC value, including the values and symbolization of all of the operands. For example, the following lines show the optimized code when the PC value is at the WRITELN statement:

    DBG> STEP
    stepped to DOC\%LINE 7
         7:         WRITELN(X*Y);
    DBG> EXAMINE/OPERAND .%PC
    DOC\%LINE 7:    PUSHL   S^#10
    DBG>

In contrast, the following lines show the unoptimized code at the WRITELN statement:

    DBG> STEP
    stepped to DOC\%LINE 7
         7:         WRITELN(X*Y);
    DBG> EXAMINE/OPERAND .%PC
    DOC\%LINE 7:    MOVL    S^#10,B^-4(FP)
         B^-4(FP)   2146279292 contains 62914576
    DBG>

### 14.1.2 Changes in Coding Order

Several methods of optimizing consist of performing operations in a sequence different from the sequence specified in the source code. Sometimes code is eliminated altogether.

As a result, the source code displayed by the debugger does not correspond exactly to the actual code being executed.

The following example depicts a segment of source code from a Fortran program as it might appear on a compiler listing or in a screen-mode source display. This code segment sets the first ten elements of array A to the value 1/X.

    Line          Source Code
    ----          -----------
      5            DO 100 I=1,10
      6            A(I) = 1/X
      7        100 CONTINUE

Optimization may produce the following scenario: As the compiler processes the source program, it determines that the reciprocal of X need only be computed once, not 10 times as the source code specifies, because the value of X never changes in the DO loop. The compiler thus may generate optimized code equivalent to the following code segment:

    Line          Optimized Code Equivalent
    ----          -------------------------
      5            TEMP = 1/X
                   DO 100 I=1,10
      6            A(I) = TEMP
      7        100 CONTINUE

Depending on the compiler implementation, the moved code may be associated with the first line of the loop (common on VAX systems) or may retain its original line number (common on Alpha systems).

If a discrepancy occurs, it is not obvious from looking at the displayed source line. Furthermore, if the computation of 1/X were to fail because X is 0, it would appear from inspecting the source display that a division by 0 had occurred on a source line that contains no division at all.

This kind of apparent mismatch between source code and executable code should be expected from time to time when you debug optimized programs. It can be caused not only by code motions out of loops, as in the previous example, but by a number of other optimization methods as well.

### 14.1.3 Semantic Stepping (Alpha Only)

Semantic stepping (available only on Alpha systems) makes stepping through optimized code less confusing. The semantic-stepping mode complements the traditional step-by-line and step-by-instruction modes. There are two commands for semantic stepping: SET STEP SEMANTIC_EVENT and STEP/SEMANTIC_EVENT.

Semantic Events

One problem of stepping through optimized code is that the apparent source program location "bounces" back and forth with the same line often appearing again and again. Indeed, sometimes the forward progress in STEP LINE mode averages barely more than one instruction per STEP command.

This problem is addressed through annotating instructions that are semantic events. Semantic events are important for two reasons:

- They represent the points in the program where the effects of the program actually happen.
- These effects tend to happen in an order that remains close to the source order of events in the program.

A semantic event is one of the following:

- Data event --- An assignment to a user variable
- Control event --- A control flow decision, with a conditional or unconditional transfer of control, other than a call
- Call event --- A call (to a routine that is not stepped over) or a return from a call

It is important to understand that not every assignment, transfer of control, or call is necessarily a semantic event. The major exceptions are as follows:

- When two instructions are required to assign to a complex or X_floating value, only the first instruction is treated as a semantic event.
- When there are multiple branches that are part of a single higher-level construct, such as a decision tree of branches that implement a case or select construct, then only the first is treated as a semantic event.
- When a call is made to a routine that is a compiler-specific helper routine, such as a call to OTS$MOVE, which handles certain kinds of string or storage copy operations, the call is not considered a semantic event. This means that control will not stop at the call.
  To step into such a routine, you must do either of the following:
  - Set a breakpoint at the routine entry point
  - Use a series of STEP/INSTRUCTION commands to reach the call of interest and then use STEP/INSTRUCTION/INTO to enter the called routine.
- When there is more than one semantic event in a row with the same line number, then only the first is used.

SET STEP SEMANTIC_EVENT Command

The SET STEP SEMANTIC_EVENT command establishes the default stepping mode as semantic.

STEP/SEMANTIC_EVENT Command

STEP/SEMANTIC_EVENT, or simply STEP when semantic mode is in effect, causes a breakpoint to be set at the next semantic event, whether an assignment, a transfer of control, or a call. Execution proceeds to that next event. Parts of any number of different lines/statements may be executed along the way without interfering with progress. When the semantic event is reached (that is, when the instruction associated with that event is reached but not yet executed), execution is suspended (similar to reaching the next line when STEP/LINE is used).

Example of Semantic Stepping

The comments in the following C program, doct2, point out some considerations for optimization:

    #include <stdio.h>
    #include <stdlib.h>

    int main(unsigned argc, char **argv) {
        int w, x, y, z=0;

        x = atoi(argv[1]);
        printf("%d\n", x);

        x = 5;
        y = x;

        if (y > 2) {               /* always true */
            printf("y > 2");
            }
        else {
            printf("y <= 2");
            }

        if (z) {                   /* always false */
            printf("z");
            }
        else {
            printf("not z");
            }

        printf("\n");
        }

Contrast the following two examples, which show stepping by line and stepping by semantic event through the optimized doct2 program:

- Stepping by line:

      $ doct2:=$sys$disk:[]doct2
      $ doct2 6

               Debugger Banner and Version Number

      Language:: Module: Doct2: GO to reach DBG> go
      break at routine DOCT2\main
         654:     x = atoi(argv[1]);
      DBG> step
      stepped to DOCT2\main\%LINE 651
         651: int main(unsigned argc, char **argv) {
      DBG> step
      stepped to DOCT2\main\%LINE 654
         654:     x = atoi(argv[1]);
      DBG> step
      stepped to DOCT2\main\%LINE 651
         651: int main(unsigned argc, char **argv) {
      DBG> step
      stepped to DOCT2\main\%LINE 654
         654:     x = atoi(argv[1]);
      DBG> step
      stepped to DOCT2\main\%LINE 655
         655:     printf("%d\n", x);
      DBG> step
      stepped to DOCT2\main\%LINE 654
         654:     x = atoi(argv[1]);
      DBG> step
      stepped to DOCT2\main\%LINE 655
         655:     printf("%d\n", x);
      DBG> step
      6
      stepped to DOCT2\main\%LINE 661
         661:         printf("y > 2");
      DBG> step
      y > 2
      stepped to DOCT2\main\%LINE 671
         671:         printf("not z");
      DBG> step
      not z
      stepped to DOCT2\main\%LINE 674
         674:     printf("\n");
      DBG> step
      stepped to DOCT2\main\%LINE 675
         675:     }
      DBG> step
      'Normal successful completion'
      DBG>

- Stepping by semantic event:

      $ doct2:=$sys$disk:[]doct2
      $ doct2 6

               Debugger Banner and Version Number

      Language:: Module: Doct2: GO to reach DBG> set step semantic_event
      DBG> go
      break at routine DOCT2\main
         654:     x = atoi(argv[1]);
      DBG> step
      stepped to DOCT2\main\%LINE 654+8
         654:     x = atoi(argv[1]);
      DBG> step
      stepped to DOCT2\main\%LINE 655+12
         655:     printf("%d\n", x);
      DBG> step
      6
      stepped to DOCT2\main\%LINE 661+16
         661:         printf("y > 2");
      DBG> step
      y > 2
      stepped to DOCT2\main\%LINE 671+16
         671:         printf("not z");
      DBG> step
      not z
      stepped to DOCT2\main\%LINE 674+16
         674:     printf("\n");
      DBG> step
      stepped to DOCT2\main\%LINE 675+24
         675:     }
      DBG> step
      stepped to DOCT2\__main+104
      DBG> step
      'Normal successful completion'
      DBG>

Notice that the semantic stepping behavior is much smoother and more straightforward than the stepping-by-line example. Further, semantic stepping results in stopping at significant points of the program. In general, semantic stepping significantly reduces or eliminates the confusion of "bouncing" around the code nonsequentially, which characteristically happens with stepping by line through optimized code. Although some reordering of the source program may be done to take advantage of better execution characteristics, generally the flow is from top to bottom.

The granularity of stepping is different between stepping by line and stepping semantically. Sometimes it is greater, sometimes smaller. For example, a statement that would by its semantic nature constitute a semantic event will not show up with semantic stepping if it has been optimized away. Thus, the semantic region will span across several lines, skipping the line that has been optimized away.

### 14.1.4 Use of Registers

A compiler might determine that the value of an expression does not change between two given occurrences and might save the value in a register. In such cases, the compiler does not recompute the value for the next occurrence, but assumes the value saved in the register is valid.

If, while debugging a program, you use the DEPOSIT command to change the value of the variable in the expression, the corresponding value stored in the register might not be changed. Thus, when execution continues, the value in the register might be used instead of the changed value in the expression, which will cause unexpected results.

In addition, when the value of a nonstatic variable (see Section 3.4.3) is held in a register, its value in memory is generally invalid; therefore, a spurious value might be displayed if you enter the EXAMINE command for a variable under these circumstances.

### 14.1.5 Use of Condition Codes (VAX Only)

On VAX processors, one optimization technique takes advantage of the way in which the VAX processor condition codes are set. For example, consider the following Pascal source code:

    X := X + 2.5;
    IF X < 0
    THEN
        ...

Rather than test the new value of X to determine whether to branch, the optimized code bases its decision on the condition code setting after 2.5 is added to X. Thus, if you attempt to set a breakpoint on the IF statement and deposit a different value into X, you do not achieve the intended result because the condition codes no longer reflect the value of X. In other words, the decision to branch is being made without regard to the deposited value of the variable.

Again, you can use the EXAMINE/OPERAND .%PC command to determine the correct location for depositing so as to achieve the results you expect.

### 14.1.6 Split-Lifetime Variables

In compiling with optimization, the compiler sometimes performs split-lifetime analysis on a variable, "splitting" it into several independent subvariables that can be independently allocated. The effect is that the original variable can be thought to reside in different locations at different points in time --- sometimes in a register, sometimes in memory, and sometimes nowhere. It is even possible for the different subvariables to be simultaneously active.

On Alpha processors, in response to the EXAMINE command, the debugger tells you at which locations in the program the variable was defined. When the variable has an inappropriate value, this location information can help you determine where the value of the variable was assigned. (The /DEFINITIONS qualifier enables you to specify more or fewer than the default five locations.)

Split-lifetime analysis applies only to scalar variables and parameters. It does not apply to arrays, records, structures, or other aggregates.

Examples of Split-Lifetime Processing

The following examples illustrate the use of split-lifetime processing. For the first example, a small C program, the numbers in the left column are listing line numbers.

    385 doct8 () {
    386
    387     int i, j, k;
    388
    389     i = 1;
    390     j = 2;
    391     k = 3;
    392
    393     if (foo(i)) {
    394         j = 17;
    395         }
    396     else {
    397         k = 18;
    398         }
    399
    400     printf("%d, %d, %d\n", i, j, k);
    401
    402     }

When compiled, linked, and executed for debugging, the optimized program results in this dialogue:

    $ run doct8

       .
       .
       .
         DBG> step/into
         stepped to DOCT8\doct8\%LINE 391
            391:     k = 3;
         DBG> examine i
         %W, entity 'i' was not allocated in memory (was optimized away)
         DBG> examine j
         %W, entity 'j' does not have a value at the current PC
         DBG> examine k
         %W, entity 'k' does not have a value at the current PC

Note the difference in the message for the variable i compared to j or k. The variable i was not allocated in memory (registers, core, or otherwise) at all, so there is no point in ever trying to examine its value again. By contrast, j and k do not have a value "at the current PC" here; somewhere later in the program they will.

Stepping one more line results in this:

         DBG> step
         stepped to DOCT8\doct8\%LINE 385
            385: doct8 () {

This looks like a step backward --- a common phenomenon in optimized (scheduled) code. (This problem is dealt with by "semantic stepping mode," discussed in Section 14.1.2.) Continuing to step results in this:

         DBG> step 5
         stepped to DOCT8\doct8\%LINE 391
            391:     k = 3;
         DBG> examine k
         %W, entity 'k' does not have a value at the current PC
         DBG> step
         stepped to DOCT8\doct8\%LINE 393
            393:     if (foo(i)) {
         DBG> examine j
         %W, entity 'j' does not have a value at the current PC
         DBG> examine k
         DOCT8\doct8\k:  3
             value defined at DOCT8\doct8\%LINE 391

Here j is still undefined, but k now has a value, namely 3. That value was assigned at line 391.

Recall from the source that j was assigned a value before k (at line 390), but that has yet to show up. Again, this is common with optimized (scheduled) code.

         DBG> step
         stepped to DOCT8\doct8\%LINE 390
            390:     j = 2;

Here the value of j appears. Thus:

         DBG> examine j
         %W, entity 'j' does not have a value at the current PC
         DBG> step
         stepped to DOCT8\doct8\%LINE 393
            393:     if (foo(i)) {
         DBG> examine j
         DOCT8\doct8\j:  2
             value defined at DOCT8\doct8\%LINE 390

Skipping ahead to the print statement at line 400, examine j again.

         DBG> set break %line 400
         DBG> g
         break at DOCT8\doct8\%LINE 400
            400:     printf("%d, %d, %d\n", i, j, k);
         DBG> examine j
         DOCT8\doct8\j:  2
             value defined at DOCT8\doct8\%LINE 390
             value defined at DOCT8\doct8\%LINE 394

Here there is more than one definition location given for j. Which applies depends on which path was taken in the IF clause. If a variable has an apparently inappropriate value, this mechanism provides a means to take a closer look at those places, and only those, where that value might have come from.

You can use the SHOW SYMBOL/ADDRESS command to display the split-lifetime information for a symbol, as in the following example:

    DBG> show symbol/address j
         data DOCT8\doct8\j
           between PC 131128 and 131140
             PC definition locations are at: 131124
             address: %R3
           between PC 131144 and 131148
             PC definition locations are at: 131140
             address: %R3
           between PC 131152 and 131156
             PC definition locations are at: 131124
             address: %R3
           between PC 131160 and 131208
             PC definition locations are at: 131124, 131140
             address: %R3

The variable j has four lifetime segments. The PC addresses are the result of linking the image, and the comments relate them to line numbers in the source program.

- The first segment starts at the assignment of 2 to j and extends through the test to just before the assignment of 17 to j.
- The second segment starts at the assignment of 17 to j and extends up to the ELSE part of the IF statement.
- The third segment corresponds to the ELSE clause. There is no assignment to j in this range of PCs. Note that the definition of j that applies here is from the first segment.
- The fourth segment starts at the join point following the IF clause and extends to the end of the program. The definition of j comes from either line 390 or line 394 depending on which path was taken through the IF statement.

There is one major conceptual difference between the split-lifetime support on OpenVMS Alpha systems and what is available on OpenVMS VAX systems. On Alpha systems, the debugger tracks and reports which assignments and definitions might have provided the displayed value of a variable. This additional information can help you cope with some of the effects of code motion and other optimizations --- effects that cause a variable to have a value coming from an unexpected place in the program.

EXAMINE/DEFINITIONS Command (Alpha Only)

For a split-lifetime variable, the EXAMINE command not only displays the value of the active lifetime, it also displays the lifetime's definition points. The definition points are places where the lifetime could have received an initial value (if there is only one definition point, then that is the only place.)

There is more than one definition point if a lifetime's initial value can come from more than one place. In the previous example when the program is suspended at the printf, examining j results in the following:

    DBG> examine j
    DOCT8\doct8\j:  2
        value defined at DOCT8\doct8\%LINE 390
        value defined at DOCT8\doct8\%LINE 394

Here, the lifetime of j has two definition points, because the value could have come from either line 390 or line 394, depending on whether or not the expression at line 393 was TRUE.

By default, up to five definition locations are displayed when the contents of a variable are examined. You can specify the number of definition locations to display with the /DEFINITIONS=n qualifier, as in the following example:

    DBG> EXAMINE/DEFINITIONS=74 FOO

Note that the minimum abbreviation is /DEFI.

If you want a default number of definitions other than five, you can use a command definition like the following:

    DBG> DEFINE/COMMAND E = "EXAMINE/DEFINITIONS=100"

If the /DEFINITIONS qualifier is set to 100, and the split-lifetime variable examined has 120 definition points, the debugger displays the 100 as specified, and then reports:

    there are 20 more definition points

Previous | Next | Contents | Index

| privacy and legal statement |
4538PRO_027.HTML