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:
$ 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>
$ 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.
13.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.
13.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.
13.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 13.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.
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
The debugger uses the terminal screen for input and output (I/O) during a debugging session. If you use a single terminal to debug a screen-oriented program that uses most or all of the screen, debugger I/O can overwrite, or can be overwritten by, program I/O.
Using one terminal for both program I/O and debugger I/O is even more complicated if you are debugging in screen mode and your screen-oriented program calls any Run-Time Library (RTL) Screen Management (SMG$) routines. This is because the debugger's screen mode also calls SMG routines. In such cases, the debugger and your program share the same SMG pasteboard, which causes further interference.
To avoid these problems when debugging a screen-oriented program, use one of the following techniques to separate debugger I/O from program I/O:
Assume that TTD1: is your current terminal from which you plan to start the debugger. You want to display debugger I/O on terminal TTD2: so that TTD1: is devoted exclusively to program I/O.
Follow these steps:
$ ALLOCATE TTD2:
$ DEFINE DBG$INPUT TTD2: $ DEFINE DBG$OUTPUT TTD2:
$ SHOW DEVICE/FULL TTD2:
$ SET TERMINAL/PERMANENT/DEVICE=VT200 TTD2:
$ DEBUG/KEEP . . . DBG> RUN prog-name
$ DEALLOCATE TTD2:
On a properly secured system, terminals are protected so that you cannot allocate a terminal from another terminal.
To set the necessary protection, your system manager (or a user with the privileges indicated) should follow the steps shown in the following example.
In the example, TTD1: is your current terminal (from which you plan to start the debugger), and TTD2: is the terminal to be allocated so that it can display debugger I/O.
$ SET PROCESS/PRIV=LOG_IO $ SET TERMINAL/NOHANG/PERMANENT $ LOGOUT/NOHANG
$ SET ACL/OBJECT_TYPE=DEVICE/ACL=(IDENT=[PROJ,JONES],ACCESS=READ+WRITE) TTD2: $ SET PROTECTION=WORLD:RW/DEVICE TTD2:
The debugger enables you to debug modules whose source code is written in different languages within the same debugging session. This section highlights some language-specific behavior that you should be aware of to minimize possible confusion.
When debugging in any language, be sure to consult:
When you bring a program under debugger control, the debugger sets the current language to that in which the module containing the main program (usually the routine containing the image transfer address) is written. The current language is identified at that point. For example:
$ DEBUG/KEEP Debugger Banner and Version Number DBG> RUN prog-name Language: PASCAL, Module: FORMS DBG>
The current language setting determines how the debugger parses and interprets the names, operators, and expressions you specify in debugger commands, including things like the typing of variables, array and record syntax, the default radix for integer data, case sensitivity, and so on. The language setting also determines how the debugger displays data associated with your program.
Many programs include modules that are written in languages other than that of the main program. To minimize confusion, by default the debugger language remains set to the language of the main program throughout a debugging session, even if execution is paused within a module written in another language.
To take full advantage of symbolic debugging with such modules, use the SET LANGUAGE command to set the debugging context to that of another language. For example, the following command causes the debugger to interpret any symbols, expressions, and so on according to the rules of the COBOL language:
DBG> SET LANGUAGE COBOL
On VAX processors, the SET LANGUAGE command takes the following keywords:
ADA | BASIC | BLISS | C |
C_PLUS_PLUS | COBOL | DIBOL | FORTRAN |
MACRO | PASCAL | PLI | RPG |
SCAN | UNKNOWN |
On Alpha processors, the SET LANGUAGE command takes the following keywords:
ADA | AMACRO | BASIC | BLISS |
C | C_PLUS_PLUS | COBOL | FORTRAN |
MACRO | MACRO64 | PASCAL | PLI |
UNKNOWN |
In addition, when debugging a program that is written in an unsupported
language, you can specify the SET LANGUAGE UNKNOWN command. To maximize
the usability of the debugger with unsupported languages, the SET
LANGUAGE UNKNOWN command causes the debugger to accept a large set of
data formats and operators, including some that might be specific to
only a few supported languages. The operators and constructs that are
recognized when the language is set to UNKNOWN are identified in the
debugger's online help (type HELP Language).
13.3.2 Specific Differences Among Languages
This section lists some of the differences you should keep in mind when debugging in various languages. Included are differences that are affected by the SET LANGUAGE command and other differences (for example, language-specific initialization code and predefined breakpoints).
This section is not intended to be complete. See the debugger's online
help (type HELP Language) and your language documentation for complete
details.
13.3.2.1 Default Radix
The default radix for entering and displaying integer data is decimal for most languages.
On VAX processors, the exceptions are BLISS and MACRO--32, which have a hexadecimal default radix.
On Alpha processors, the exceptions are BLISS, MACRO--32, and MACRO--64, which have a hexadecimal default radix.
Use the SET RADIX command to establish a new default radix.
13.3.2.2 Evaluating Language Expressions
Several debugger commands and constructs evaluate language expressions:
When processing these commands, the debugger evaluates language expressions in the syntax of the current language and in the current radix as discussed in Section 4.1.6. At each execution (not when you enter the command), the debugger checks the syntax of any expressions in WHEN or DO clauses, and then evaluates them.
Note that operators vary widely among different languages. For example, the following two commands evaluate equivalent expressions in Pascal and Fortran, respectively:
DBG> SET WATCH X WHEN (Y < 5) ! Pascal DBG> SET WATCH X WHEN (Y .LT. 5) ! FORTRAN
Assume that the language is set to Pascal and you have entered the first (Pascal) command. You now step into a Fortran routine, set the language to Fortran, and resume execution. While the language is set to Fortran, the debugger is not able to evaluate the expression (Y < 5). As a result, it sets an unconditional watchpoint and, when the watchpoint is triggered, returns a syntax error for the < operator.
This type of discrepancy can also occur if you use commands that evaluate language expressions in debugger command procedures and initialization files.
When the language is set to BLISS, the debugger processes language
expressions that contain variable names (or other address expressions)
differently than when it is set to another language. See Section 4.1.6
for details.
13.3.2.3 Arrays and Records
The syntax for denoting array elements and record components (if applicable) varies among languages.
For example, some languages use brackets ([]), and others use parentheses (( )), to delimit array elements.
Some languages have zero-based arrays. Some languages have one-based arrays, as in the following example:
DBG> EXAMINE INTEGER_ARRAY PROG2\INTEGER_ARRAY (1,1): 27 (1,2): 31 (1,3): 12 (2,1): 15 (2,2): 22 (2,3): 18 DBG>
For some languages (like Pascal and Ada) the specific array declaration
determines how the array is based.
13.3.2.4 Case Sensitivity
Names and language expressions are case sensitive in C. You must specify them exactly as they appear in the source code. For example, the following two commands are not equivalent when the language is set to C:
DBG> SET BREAK SCREEN_IO\%LINE 10 DBG> SET BREAK screen_io\%LINE 10
Many programs issue a NOTATMAIN message when a program is brought under debugger control. For example:
$ DEBUG/KEEP Debugger Banner and Version Number DBG> RUN prog-name Language: ADA, Module: MONITOR Type GO to reach main program DBG>
The NOTATMAIN message indicates that execution is paused before the beginning of the main program. This enables you to execute and check some initialization code under debugger control.
The initialization code is created by the compiler and is placed in a special PSECT named LIB$INITIALIZE. For example, in the case of an Ada package, the initialization code belongs to the package body (which might contain statements to initialize variables, and so on). In the case of a Fortran program, the initialization code declares the handler that is needed if you specify the /CHECK=UNDERFLOW or /CHECK=ALL qualifier.
The NOTATMAIN message indicates that, if you do not want to debug the
initialization code, you can execute immediately to the beginning of
the main program by entering a GO command. You are then at the same
point as when you start debugging any other program. Entering the GO
command again starts program execution.
13.3.2.6 Predefined Breakpoints
If your program is a tasking program, two breakpoints that are associated with tasking exception events are automatically established when the program is brought under debugger control. These breakpoints are not affected by a SET LANGUAGE command. They are established automatically during debugger initialization when appropriate run-time libraries are present.
To identify these predefined breakpoints, enter the SHOW BREAK command. For example:
DBG> SHOW BREAK Predefined breakpoint on ADA event "EXCEPTION_TERMINATED" for any value Predefined breakpoint on ADA event "DEPENDENTS_EXCEPTION" for any value DBG>
On VAX systems, the STEP/OVER command may result in stepping into, not over, Fortran Run-Time Library (RTL) routines. If you are debugging a Digital Fortran program, and the debugger encounters a Fortran I/O operation such as READ, WRITE, or PRINT, the Fortran compiler calls a Fortran RTL routine to complete the I/O operation. When you issue a STEP command at the call to the RTL routine, the debugger steps into, rather than over, the routine and issues the following error message:
%DEBUG-W-NOSCRLIN, no source line for address nnnnnnnn
4538P021.HTM OSSG Documentation 22-NOV-1996 13:02:02.77
Copyright © Digital Equipment Corporation 1996. All Rights Reserved.