Adding debug symbols and instrumentation code to binaries

Updated: April 19, 2023

The active build configuration determines which make commands are used to compile and link binaries in the current project. To debug and analyze your program with certain tools, you must define the right options in those commands.

When you use the IDE to create a project based on standard makefiles, the IDE generates a default makefile that defines the compiler and linker flags required to build different binary versions. These flags are stored in variables with descriptive names (e.g., CCFLAGS_debug, LDFLAGS_profile).

If you create a project outside of the IDE or inherit a project from a third party, your makefiles might not be set up in this way. In this case, we recommend reorganizing them to use such variables because it makes it easy to support various analysis activities. You should define variables for storing compiler options, linker options, and libraries to link, for each of four binary versions:
  • debug
  • release
  • coverage
  • profile

No tool-enabling options should be used with the release version because it's intended for use in post-deployment (i.e., customer) environments. Also, for some activities, such as profiling, you may have to change the flags and rebuild if you want to measure different metrics.

For QNX Legacy Recursive Make projects, which use managed recursive makefiles, you must adjust the project properties to build the binary for debugging or for generating analysis data. You can't edit the makefiles directly.

Enabling debug symbols

The QNX build tools (qcc and q++) use the same flag for adding debug symbols as the GDB tools (gcc and g++), namely, the -g option. For standard makefiles, you simply add this option to the debug compiler flags. For example, if you're using the C compiler:
CCFLAGS_debug += -g -O0 -fno-builtin
Or if you're using the C++ compiler:
CXXFLAGS_debug += -g -O0 -fno-builtin
For QNX Legacy Recursive Make projects, you must ensure that the debug binary versions are selected for building:
  1. Choose Project > Properties > QNX C/C++ Project.
  2. In the Build Variants tab, expand the listings for all architectural variants that you want to build.
  3. Check the debug boxes for all variants that you want to debug.
  4. Click OK to confirm the settings and exit the properties window.

    The IDE updates the makefile and rebuilds the project.

Enabling Code Coverage instrumentation

If your project uses standard makefiles, you must define -f options to add instrumentation code for measuring which lines of code are executed. You should verify that the following options (shown in bold) are defined in the coverage variables:
CCFLAGS_coverage += -g -O0 -ftest-coverage -fprofile-arcs
                    -nopipe -Wc,-auxbase-strip,$@
LDFLAGS_coverage += -ftest-coverage -fprofile-arcs
The above example uses CCFLAGS_coverage, which is meant for the C compiler, qcc. If you're using the C++ compiler, q++,you must use CXXFLAGS_coverage (and LDFLAGS_coverage).
The relevant options are:
  • -O0 (“big-oh zero”) — turns off compiler optimization; optimization eliminates some lines of code, making it impossible to maintain separate execution counts for each line
  • -fprofile-arcs — adds code to the object files to record program arcs (flow)
  • -ftest-coverage — generates notes files (.gcno), which are needed to report program coverage
For QNX Legacy Recursive Make projects, you must modify the QNX C/C++ Project properties as follows:
  1. Choose Project > Properties > QNX C/C++ Project.
  2. In the Options tab, under Build Options, check Build with Code Coverage.

    You must have only one architecture and variant selected in the Build Variants tab. Otherwise, the IDE displays an error and won't build the project until you fix the configuration.

  3. In the Compiler tab, under Code Generation Options, set Optimization Level to “No optimize”.

    Code optimization should be turned off because it can produce inaccurate coverage results. Also, in this release, you don't need to explicitly specify the -f options for the compiler flags (CCFLAGS or CXXFLAGS) or linker flags (LDFLAGS).

  4. Click OK to confirm the settings and exit the properties window.

    The IDE updates the makefile and rebuilds the project.

Full details about all UI fields that control the building of these types of projects are given in the QNX C/C++ Project properties reference.

Enabling call count instrumentation

The qconn agent periodically samples the execution position of a running process, by recording the current address every millisecond. This means that you don't need to instrument the binary to see where in the code your application spends most of its time. However, runtimes estimates based on statistical sampling don't tell the full story because you still need to know how often a function gets called when determining what areas to optimize.

In standard makefiles, to insert code that counts function calls, you must add the -p option to the debug compiler and linker flags:
CCFLAGS_debug += -p -g -O0
LDFLAGS_debug += -p -g -nopie
The above example uses CCFLAGS_debug, which is meant for the C compiler, qcc. If you're using the C++ compiler, q++, you must use CXXFLAGS_debug (and LDFLAGS_debug).
Note: If a user wants to use call count and Sampling profiling in postmortem mode (see postmortem analysis) while using gmon.out, the binary should not be Position Independent Executable (PIE). When using qcc, it means passing -nopie. To insert code that counts function calls, you must add the -p option to the debug compiler and linker flags in standard makefiles.
Note: The debug variables, not the profile variables, are used because this type of profiling is done with the debug variant of the binary. With function instrumentation (explained below), the profiling variant of the binary is needed, so different variables must be set in that case.

For the compiler, the -p option makes it insert code at every function entry to gather call information. This information allows the IDE to display who called that function and its place in the program's call graph, through the right-click menu of the Execution Time view. For the linker, the option makes it link in the profiling version of libc. Note that with the default makefile content, you may have to create the LDFLAGS_debug variable.

When the Sampling profiling method is selected in the Application Profiler UI fields, the IDE always shows runtime estimates in the results, even if -p isn't specified. But in this case, neither the call counts nor the call information previously mentioned are shown. Also, if you enable Use Call Count Instrumentation and then try to run an application binary built without the -p option, the IDE doesn't launch the application and instead displays an error.

Note: If your application uses libraries that weren't compiled with -p, the profiling results don't include call counts for their functions. If a lot of execution time is spent inside these functions, this profiling method may not be of much value and you may instead want to use function instrumentation. With this other method, the time spent in a non-instrumented library function is charged to the caller.
For QNX Legacy Recursive Make projects, you must adjust the QNX C/C++ Project properties as follows:
  1. Choose Project > Properties > QNX C/C++ Project.
  2. In the Options tab, under Build Options, check Build for Profiling (Call Count Instrumentation).
  3. Click OK to confirm the settings and exit the properties window.

    The IDE updates the makefile and rebuilds the project.

Enabling function instrumentation

In standard makefiles, you must define options to add instrumentation code for measuring function runtimes. You should verify that the following options (shown in bold) are defined in the profile variables:
CCFLAGS_profile += -g -O0 -finstrument-functions
LIBS_profile += -lprofilingS
The above example uses CCFLAGS_profile, which is meant for the C compiler, qcc. If you're using the C++ compiler, q++, you must use CXXFLAGS_profile (and LDFLAGS_profile).
Note: The profile variables are used because the profiling variant of the binary is used with this profiling method.

For the compiler, the -finstrument-functions option makes it insert code at every function entry and exit. This code calls a profiling library function that records the current time. When displaying the results, the IDE can then calculate the total time spent inside a given function, based on its entry and exit timestamps. For the linker, the -lprofilingS option makes it link in the profiling library (libprofilingS.a), which implements the time-recording methods.

For QNX Legacy Recursive Make projects, you must adjust the QNX C/C++ Project properties as follows:
  1. Choose Project > Properties > QNX C/C++ Project.
  2. In the Options tab, under Build Options, check Build for Profiling (Function Instrumentation).
  3. Click OK to confirm the settings and exit the properties window.

    The IDE updates the makefile and rebuilds the project.