20.3.3 Debugging Strategies for Optimized Code

The optimizer can introduce challenges for debugging code which increase with higher levels of optimization. For the best debugging experience, make sure that the ELF/DWARF object file format is selected (as opposed to COFF) whenever possible. The output file format is selected in MPLAB X IDE under Project Properties>XC16 (Global Options) (see figure below).

The DWARF symbol language has advanced features that allow the compiler to provide more information when optimized. The compiler will be able to describe how object values flow in and out of registers, even if the register changes. For this reason, ELF/DWARF at -O1 will provide a reasonably smooth debugging experience with some optimizations.

Figure 20-2. Project Properties XC16 Global Options

Earlier (Using Optimizations) we mentioned some of the effects of optimizing code. Some of these effects will prevent the debugger from displaying a value (the variable is not needed and has been optimized away) or placing a breakpoint (the line of code does not exist).

Sometimes it is more effective to debug in a mixed C-assembly display, or to follow the C code along with the Program Memory view.

Additionally, MPLAB XC C compilers provide a couple of tools that can be helpful.

  • A variant of the standard C assertion mechanism can be used to return to the debugger at certain execution points. The macro __conditional_software_breakpoint(X) is available in assert.h and can be used to halt the debugger.
  • The optimization level can be set on a function-by-function basis. For example, to make debugging of a particular function easier while still optimizing the rest of the application, define the function like this:


    Tau __attribute__((optimize(1))) fn(...){}

    A declaration of this form will override the current global optimization setting on a function-by-function basis.

  • The MPLAB X IDE defines the pre-processor symbol __DEBUG when a debug build is being produced. This can be useful for enabling code changes to support debugging only when actually debugging. For example, conditionally changing the optimization level for a given function can be implemented with a simple macro:
    #ifdef __DEBUG
    #define DBG_OPTIMIZE(X)  __attribute__((optimize(X)))
    #else
    #define DBG_OPTIMIZE(X) /* not debugging */
    #endif
    
    Tau DBG_OPTIMIZE(1) fn(...) {
    }

    Multiple attributes can be combined. This is valid:

    void __attribute__((interrupt)) DBG_OPTIMIZE(1) _T1Interrupt(void) {
    }