Debugging Optimized Code (GNAT User’s Guide for Native Platforms)

#### 6.3.1.4 Debugging Optimized Code ¶

Although it is possible to do a reasonable amount of debugging at nonzero optimization levels, the higher the level the more likely that source-level constructs will have been eliminated by optimization. For example, if a loop is strength-reduced, the loop control variable may be completely eliminated and thus cannot be displayed in the debugger. This can only happen at `-O2` or `-O3`. Explicit temporary variables that you code might be eliminated at level `-O1` or higher.

The use of the `-g` switch, which is needed for source-level debugging, affects the size of the program executable on disk, and indeed the debugging information can be quite large. However, it has no effect on the generated code (and thus does not degrade performance)

Since the compiler generates debugging tables for a compilation unit before it performs optimizations, the optimizing transformations may invalidate some of the debugging data. You therefore need to anticipate certain anomalous situations that may arise while debugging optimized code. These are the most common cases:

- ‘The ‘hopping Program Counter’:’ Repeated `step` or `next` commands show the PC bouncing back and forth in the code. This may result from any of the following optimizations:
  - ‘Common subexpression elimination:’ using a single instance of code for a quantity that the source computes several times. As a result you may not be able to stop on what looks like a statement.
  - ‘Invariant code motion:’ moving an expression that does not change within a loop to the beginning of the loop.
  - ‘Instruction scheduling:’ moving instructions so as to overlap loads and stores (typically) with other code or in general to move computations of values closer to their uses. Often this causes you to pass an assignment statement without the assignment happening and then later bounce back to the statement when the value is actually needed. Placing a breakpoint on a line of code and then stepping over it may, therefore, not always cause all the expected side-effects.
- ‘The ‘big leap’:’ More commonly known as ‘cross-jumping’, in which two identical pieces of code are merged and the program counter suddenly jumps to a statement that is not supposed to be executed, simply because it (and the code following) translates to the same thing as the code that ‘was’ supposed to be executed. This effect is typically seen in sequences that end in a jump, such as a `goto`, a `return`, or a `break` in a C `switch` statement.
- ‘The ‘roving variable’:’ The symptom is an unexpected value in a variable. There are various reasons for this effect:
  - In a subprogram prologue, a parameter may not yet have been moved to its ‘home’.
  - A variable may be dead and its register re-used. This is probably the most common cause.
  - As mentioned above, the assignment of a value to a variable may have been moved.
  - A variable may be eliminated entirely by value propagation or other means. In this case, GCC may incorrectly generate debugging information for the variable

  In general, when an unexpected value appears for a local variable or parameter you should first ascertain if that value was actually computed by your program as opposed to being incorrectly reported by the debugger. Record fields or array elements in an object designated by an access value are generally less of a problem once you have verified that the access value is sensible. Typically, this means checking variables in the preceding code and in the calling subprogram to verify that the value observed is explainable from other values (you must apply the procedure recursively to those other values); or re-running the code and stopping a little earlier (perhaps before the call) and stepping to better see how the variable obtained the value in question; or continuing to step ‘from’ the point of the strange value to see if code motion had simply moved the variable’s assignments later.

In light of such anomalies, a recommended technique is to use `-O0` early in the software development cycle, when extensive debugging capabilities are most needed, and then move to `-O1` and later `-O2` as the debugger becomes less critical. Whether to use the `-g` switch in the release version is a release management issue. Note that if you use `-g` you can then use the `strip` program on the resulting executable, which removes both debugging information and global symbols.