Memory Analysis

QNX Tool SuiteIntegrated Development Environment User's GuideDeveloperSetup

Memory Analysis is a QNX tool that uses the debug version of the memory allocation library (librcheck) to track heap memory, validate pointer arguments to C library functions, and detect memory corruption. This tool displays data received from the librcheck library about memory events and problems that occur in an active application.

How to configure Memory Analysis

You can enable the Memory Analysis tool by selecting Memory as the launch mode. Although this mode supports other integrated tools, Memory Analysis is selected by default. Aside from Memory mode, you can select any other mode except Coverage and enable this tool by opening the launch configuration and clicking the Memory Analysis radio button in the rightmost tab.

The tool settings are made visible in four dropdown panels:
  • Memory Errors
  • Memory Tracing
  • Memory Snapshots
  • Advanced Settings

Memory Errors

Enable error detection
Enables or disables memory error-checking; you can disable this feature but retain its settings
Verify parameters in string and memory functions
When enabled (as by default), makes Memory Analysis verify pointers passed to functions such as memcmp() and strcpy() and report bad values (including NULL) as errors
Enable bounds checking (where possible)
Whether to check for data being written before the beginning or after the end of allocated blocks (default: enabled). This checking is done only for dynamically allocated blocks.
Enable check on realloc()/free() argument
Whether to check the pointer argument passed to realloc() or free() (default: enabled)
When an error is detected
The action to take when a memory error is detected. The default setting is report the error and continue but you can also choose launch the debugger or terminate the process.
Limit back-trace depth to
Specifies how many frames up the stack the tool should look when reporting memory corruption errors. If you set this field to 1, Memory Analysis lists only the line where the error occured. A value of 2 makes the tool list the error line and the line in the calling function. A greater depth creates greater overhead, so there's a trade-off between performance and the level of stack data reported. A value of 0 disables memory error stack tracing.
Note: For librcheck to produce stack trace data, your target system must include libunwind.so.8 in a location accessible to dlopen(). For more information, see the dlopen() entry in the C Library Reference. The librcheck library still functions without libunwind, but prints a warning that it can't produce backtraces when tracing function calls.
Perform leak check every
The time interval, in milliseconds, to check for memory leaks. Leak checks add overhead, so you must pick an interval value based on how quickly you want to detect leaks versus how much overhead you can tolerate. You can set a value of 0 to disable leak checking.
For this option to work, the control thread must be enabled in the Advanced Settings.
Perform leak check when process exits
When enabled (as by default), makes Memory Analysis check for memory leaks when the process exits. Some leaks can be caught only if this option is enabled. For this option to work, the application must exit cleanly by calling exit() or in the main() function, return.

Memory Tracing

Record memory allocation/deallocation events
Enables or disables memory tracing; you can disable this feature but retain its settings
Limit back-trace depth to
Specifies how many frames up the stack the tool should look when reporting memory allocation traces. If you set this field to 1, only the exact line where memory was allocated is shown. A value of 2 shows the allocation line and the line in the calling function. A greater depth creates greater overhead, so there's a trade-off between performance and the amount of stack data reported. A value of 0 disables memory allocation tracing.
Note: For librcheck to produce stack trace data, your target system must include libunwind.so.8 in a location accessible to dlopen(). For more information, see the dlopen() entry in the C Library Reference. The librcheck library still functions without libunwind, but prints a warning that it can't produce backtraces when tracing function calls.
Minimum allocation to trace
The minimum size, in bytes, for a block of memory to be included in the allocation stack trace results. A value of 0 (the default) means there's no filtering of small blocks.
Maximum allocation to trace
The maximum size, in bytes, for a block of memory to be included in the allocation stack trace results. A value of 0 (the default) means there's no filtering of large blocks.

Memory Snapshots

Memory Snapshots
Enables or disables memory snapshots; you can disable this feature but retain its settings
Perform snapshot every
The periodic time interval (in milliseconds) for taking heap snapshots. Each snapshot captures details about the full block list of the heap. Performing regular snapshots lets you see how the heap contents change over time. The default value is 50 000 milliseconds.
Bins counters
A comma-separated list specifying the sizes, in bytes, for defining the bins (groups) when reporting the allocation counts in the Bins tab. Suppose you fill in this field with 32, 256. The heap snapshot results will then contain separate counts for memory blocks up to 32 bytes in size, between 32 and 256 bytes, and more than 256 bytes.
If you leave this field blank, the default block size boundaries are powers of 2, from 2 B up to 4 KB, with an additional upper boundary of 2 GB to cover large blocks.

Advanced Settings

Runtime library
The filename of the allocation library to use to collect memory data. You would change this field only when working with an older SDK version and using a nondefault library.
Use regular file / Use streaming device
Whether to output the data to a regular file or a streaming device. By default, Use regular file is selected. In this case, the qconn service can't become blocked when writing data, but there's a file size limit of 2G. This setting is handy for postmortem analysis.
When Use streaming device is selected, the data is streamed directly to the IDE using a device path created by qconn. There's no size limit on the data that can be logged, but qconn becomes blocked if it sends data faster than the IDE can read it.
Target output file or device
Path of the file or device on the target for outputting the memory data. When Use regular file is selected, this field defaults to /tmp/traces.rmat.
When Use streaming device is selected, this field is set to /dev/rcheck/traces.rmat.
Create control thread
Create a thread to handle data requests at runtime. This setting is enabled by default and is required for doing regular leak checks.
Use dladdr to find dll names
This field isn't used when you're working with the SDK from QNX SDP 7.0 or later, or the librcheck.so runtime library, so it's unavailable by default. If you switch to an older SDK version and enter libmalloc_g.so in the Runtime library field, this checkbox becomes available. Then, you must check the Use dladdr field if you want to see backtrace information from shared objects (dynamically linked libraries) built with debugging information.
Show debug output on console
Show the librcheck output in the Console view (default: disabled)
Note: Below the four dropdown panels, the Configure Shared Libraries Paths link takes you to the Libraries tab. In this tab, you must define the path of any shared library for which you want to see symbol information in the Memory Analysis results.

How the tool works

When Memory Analysis has been enabled in the launch configuration, the application uses the debug version of the allocation library, librcheck. This library version tracks the history of all dynamic memory blocks and generates data about where in the program each block was allocated or freed. It also contains its own implementations of memory- and string-related functions, such as memcmp() and strcpy(), so it can validate pointer arguments passed to them and report any invalid pointers.

The librcheck library sends data about the memory activity of application processes to the qconn agent, which then writes the data to a log file or a streaming device. Within the IDE, the Memory Analysis tool reads and displays the analysis data:
Figure 1. Memory Analysis: data flow
Component diagram showing data flow between librcheck, qconn, and the Memory Analysis tool within the IDE
The data flow for Memory Analysis works as follows:
  1. Reacting to process memory activity

    Whenever an application process allocates or frees memory, or calls any function implemented in librcheck, the library generates data describing the memory activity.

  2. Sending data to the transport agent

    The librcheck library doesn't interact directly with the IDE on the host. Like other runtime analysis components, librcheck must send its data to a transport agent. In this example, we show the qconn service, but you could use any service that talks to the IDE over an IP connection.

  3. Logging data to an output file or device

    The qconn agent outputs the memory data to either a local file or a streaming device, depending on the advanced settings.

  4. Reading and presenting the data

    Within the IDE, the Memory Analysis tool reads the memory data from the log file or streaming device, then visually presents the data in a new analysis session.

How Memory Analysis results are presented

When you launch an application with Memory Analysis enabled, the IDE switches to the QNX Analysis perspective and opens the Memory Analysis editor, which displays charts illustrating the application's heap usage. In the Analysis Sessions view, the IDE creates a new session for storing the analysis results. Each open Memory Analysis session has a header containing the tool's open session icon (Icon: Open Memory Analysis session), the binary name, the session number, and the launch time.

Note: In this IDE version, the latest session is displayed at the top of the view, not at the bottom like in previous versions.

Under the header, all processes in the program being analyzed are listed. You can expand each process entry to see the threads, the executable binary, any shared libraries, and all source files that run within that process. For source files compiled into the executable binary, they're shown only if the binary was built with debug information. For source files compiled into shared libraries, you must have a debug version of these libraries within the shared libraries path on the host to see them listed.

When you click a program component (e.g., process, thread) within a session, the Memory Events and Memory Problems views display details about the memory management operations and any errors that occurred within that component. You can then click a line in these other two views to see the allocation or deallocation call chain in the Memory Backtrace view. Double-clicking a program component opens another editor window that shows the heap usage for that component. Double-clicking a call chain entry opens the source file to the corresponding line of code (when source file information is available):
Screenshot of the QNX Analysis perspective, with a process selected in Analysis Sessions, an event selected in Memory Events, a call chain entry selected in Memory Backtrace, and the source code opened to the line that makes an unmatched allocation

Each session also has a Logs entry that lists the filename (without the path) specified in the Target output file or device field. If you double-click this item, the IDE opens the log containing the analysis data gathered so far. This action is handy when you're working with QNX customer support and you need to look up something specific. When the session ends, you'll see the trace (.rmat) file stored in workspace_dir/.metadata/.plugins/com.qnx.tools.ide.common.sessions.core/sessions/session_number/. You can then import these analysis results to view them later.

Finally, you can reopen a closed session (which is indicated with a different icon, Icon: Closed Memory Analysis session) by double-clicking its Analysis Sessions entry. The IDE then redisplays the program components for this session in that same view and the heap usage charts in the editor pane.

Error details and messages

Each table row in Memory Problems describes one error. The default columns, from left to right, are:
  • Severity — Either Error, for memory read and write errors, or Leak, for leaks
  • Description — Informative message summarizing the error
  • Pointer — Address of the memory block involved
  • Trap Function — Function that trapped the error
  • Alloc Kind — Type of allocation used for the associated block
  • Binary — Executable or library file where the error occurred
  • Location — Source file and line of code where the error occurred. For this field to be meaningful, debug symbols must be available for the file.

You can change which details get displayed by clicking the dropdown button (Icon: Dropdown button) in the upper right corner of the view and choosing Preferences. This action opens a popup window that lets you choose the columns to display and the order to list them, from left to right.

In the Description column, the possible error messages and their meanings are:
Error MessageMeaning
pointer does not point to heap area The program attempted to free non-heap memory.
data has been written outside allocated memory block The program attempted to write data to a region beyond the allocated memory.
data area is not in use (can't be freed or realloced) A buffer overflow occurred in the heap and it's now corrupted.
unable to get additional memory from the system There is no more heap memory that can be allocated.
pointer points to the heap but not to a user writable area A buffer overflow occurred in the heap and it's now corrupted.
free'd pointer isn't at start of allocated memory block The program attempted to deallocate a pointer that shifted from its original value returned by the allocator.
memory leak of size n A heap block of size n has been lost.

Event details

Each table row in Memory Events describes one event. The default columns, from left to right, are:
  • Kind — Type of memory operation (malloc, calloc, new, free, etc.). The arrow icon points right for allocations and left for deallocations. When the icon has a checkmark, there's a matching request. For example, an allocation with a checkmark has a corresponding free.
  • Requested — Number of bytes requested
  • Actual — Number of bytes actually allocated or deallocated
  • Pointer — Address of the memory block involved
  • Location — Source file and line of code where the event occurred. For this field to be meaningful, debug symbols must be available for the file.
  • Count — Number of individual allocations (this is more than 1 only when they're grouped)

You can change which details get displayed by clicking the dropdown button (Icon: Dropdown button) in the upper right corner of the view and choosing Preferences. This action opens a popup window that lets you choose the columns to display and the order to list them, from left to right.

How to use the Memory Analysis editor

When you launch a Memory Analysis session, the IDE automatically opens an editor window that shows the analysis results for that session. These results are refreshed regularly as the program runs. If you double-click any component within a session, the IDE opens another window to display only the results for that component. You can open such windows for both active and finished sessions.

The editor window contains two tabs:
  • Allocations
  • Settings

Windows showing program-level data display both tabs. Windows displaying data for a program component display only the Allocations tab.

Allocations tab

This tab is shown when the editor is first opened, and it provides an interactive display of events related to dynamic memory and lets you view details about a subset of events. Two charts are displayed:
  • Details — In the top part of the editor, this chart illustrates the byte sizes of the memory blocks related to the events selected in the bottom part.
  • Memory Allocation and Deallocation Events — In the bottom part of the editor, this chart provides an overview of memory events.
To select a subset of events, left-click and drag the mouse within the bottom chart. When you release the mouse button, the top chart is redrawn to graph the details for the newly selected events. The byte size range is indicated along the vertical axis and the event counts (i.e., their ordering within the selected event subset) is indicated along the horizontal axis. In the bottom chart, the startpoint and endpoint of the selection are indicated:
Screenshot of both charts in the Allocations tab, with an event subset selected in the bottom chart and the details of the corresponding events shown in the top chart
Note: The screenshot above represents a system targeting SDP 7.1. In an SDP 8.0 target, you won't see data for the Bins, Bands, or Usage tabs.

The Details chart uses a 2D bar chart format by default. You can change the format by right-clicking anywhere in the top part of the editor, choosing Chart types, then choosing one of these four options: BarChart (the default), BarChart_3D, Differentiator, and Differentiator_3D.

The radio buttons just above the Details chart determine which types of events are graphed. By default, requested allocations and deallocations are displayed, but you can also display actual allocations and actual deallocations to see how much the heap memory footprint grows or shrinks.

The Memory Allocation and Deallocation Events chart illustrates the byte sizes for memory events too, but for a larger section of the session data set. In this bottom chart, the byte size range is shown along the vertical axis and by default, the event ID range is shown along the horizontal axis.

You can right-click anywhere in the bottom part of the editor and select By Timestamp to display the time range (in microseconds) instead of the event ID range (and you can return to the event ID labelling by selecting By Event ID from this same menu). This context menu also has other options:
Filters
Open the Memory Events Filter window so you can set fields for filtering the events shown in the two charts
Zoom In
After you've selected a subset of events, click this option to zoom in on those events
Zoom Out
After zooming in on a subset of events, click this option to zoom out again
Show Events Table
Update the Memory Events view to display information about the selected subset of events

Initially, the data set from the entire session is graphed. Because the bottom chart can get quite wide if there's a lot of data, you can divide the chart into different pages to display only part of the data set at a time. To do this, in the Page Size text field just below the chart, enter the number of events that you want to see at a given time. On the left of this text field is a spinner that lets you manually enter a page to display, or navigate between pages with the arrow buttons.

The Allocations tab provides a toolbar (in the top left corner) that supports the following actions:
  • Reload (Icon: Manual refresh) — Load the latest analysis results and redraw the charts to show them; this action is meaningful only for active sessions
  • Prevent Auto-refresh (Icon: Prevent auto-refresh) — For active sessions, disable the automatic updating of the chart
  • Toggle Overview Chart (Icon: Overview chart) — Toggle the visibility of the bottom chart
  • Show Events Table (Icon: Show events table) — Update the Memory Events view to display information about the selected subset of events (this is the same as the last option in the bottom chart's context menu)

Memory Events Filter

This window provides event filtering controls, which let you reduce the number of events displayed in the two charts so you can spot problems more easily. The window contains these dropdown panels:
Memory Events Filter
Defines memory block properties required for a memory event to appear in the charts:
Hide matching allocation/deallocation pair
Whether the block must be associated with an unmatched allocation or deallocation operation. Unmatched operations often indicate leaks or errors.
Show only events for retained objects
Whether the block must have been kept in memory instead of being deallocated. Blocks like this are common sources of leaks or errors.
Requested size range
The amount of bytes requested. For this field and other range-based fields, you can enter two values separated by a dash (-) to indicate the inclusive minimum and maximum range values, or enter one value for an exact match.
Band size
The band of memory that the associated block is allocated from or freed to. Bands are groups of small, preallocated memory blocks of the same size. In this field, you must enter the exact band size (not a range).
Pointer
The pointer values (addresses). You can enter one address (0x80832c8), or a range of addresses (0x80832c8-0x80832f0).
Memory Events Kind
Specifies which types of memory events will appear in the charts. Here, event type means which C or C++ memory-management function or operator was called (e.g., malloc(), realloc(), new, delete).
Range
Defines the range of events to display, based on either timestamps or event IDs
Files
Lists the files in which a memory event must have occurred for it to appear in the charts
Binaries and Libraries
Lists any of the executable binaries and shared library files in which a memory event must have occurred for it to appear in the charts
Threads
Lists the threads in which a memory event must have occurred for it to appear in the charts

Settings tab

This last tab lets you adjust Memory Analysis settings only for active sessions. You can't change the Advanced Settings but you can change any field in the Memory Errors, Memory Tracing, and Memory Snapshots dropdowns. Any new settings take effect when you click the Apply button in the toolbar.

The toolbar also has Reload and Prevent Auto-refresh buttons, similar to the toolbars in other tabs, and three buttons for performing specific operations on demand:
  • Collect Memory Leaks (Icon: Collect Memory Leaks)
  • Get Memory Snapshot (Icon: Get Memory Snapshot)
  • Gather Allocation Traces (Icon: Gather Allocation Traces)

Running Memory Analysis and the GDB Debugger concurrently

If you want to debug a project while running Memory Analysis to see debugging information and memory events at the same time, you must manually configure GDB to support this workflow. To run Memory Analysis with a debugging session:
  1. After selecting the project, target, and Debug mode in the launch bar, click the Edit button (Icon: Edit button) on the right of the Launch Configuration dropdown.
  2. Ensure that the Stop on startup at box is checked in the Debug tab, or that you have an active breakpoint at the first line in your program.
  3. In the Tools tab, ensure that the Memory Analysis radio button is selected and the Create control thread box is unchecked (disabled) under Advanced Settings.

    Running a separate data-collection thread can cause the application to deadlock when the debugger is also running. If you disable this thread, your program must call the librcheck API to perform leak checks and memory tracing at runtime. Note that you can't send signals to a target process to control librcheck when no control thread is running.

  4. Optional: You can specify nondefault Memory Analysis settings. To do so:
    1. Click the Memory tab on the right.
    2. In the Memory Analysis controls, change any settings to customize what gets reported.

      You can adjust how Memory Analysis measures heap usage, enable or disable specific memory checks, and turn tracing on or off and specify block size tracing limits.

    3. Click OK to save the configuration changes and close the window.
  5. When you're ready to debug and analyze your application, click the Debug button (Icon: Debug button).

    The IDE launches the application and attaches the debugger, which stops execution at the startup location specified in the Debug tab. A new Memory Analysis session is shown in Analysis Sessions.

  6. If you don't see the gdb output in the Console view, click the Display Selected Console button (Icon: Display Selected Console button) in the upper right area and select the gdb entry.
  7. Tell the debugger to ignore the SIGSEGV signal, with this command: handle SIGSEGV nopass

    Sometimes, the debugger tooling does unsafe operations and causes the OS to emit this particular signal. This directive tells gdb to not pass this signal onto the program.

  8. Click Resume in the Debug view toolbar, or type continue and press Enter in the Console view.

The program continues execution until the next breakpoint. When execution stops again, you can view the stack trace and other useful information in the Debug perspective. You can also switch to the QNX Analysis perspective and view the Memory Analysis data gathered up till that point, in the editor pane.

CSV file contents for exported event data

If you export Memory Analysis results to a CSV file, the resulting data in the file depends on the event type selected in the Export Memory Analysis Data window.

The tables shown below list the fields exported to the CSV file, in the order in which they're written, for each event type reported by Memory Analysis. If you want to see field names in the column headers when viewing the data in a spreadsheet application (e.g., Excel), be sure to check Generate header row in the export controls window.

Table 1. Fields for all event types
Name Description
Session Name Abbreviated session name, which is just the binary name without the session number or timestamp shown in the Analysis Sessions entry.
Session Time When the session was created. For an imported session, it's the time of the import, not the time of creation.
Event ID Unique ID for the memory event.
Time Stamp Timestamp of when the event occurred on the target machine.
Process ID PID of the process.
Table 2. Additional fields for memory events
Name Description
Thread ID TID of the thread.
CPU CPU number (for multicore machines).
Alloc Kind Memory operation type (e.g., malloc, free).
Actual Size Number of bytes in the allocated block.
Requested Size Number of bytes requested by the program.
Deallocation Whether the memory block was freed.
Pointer Pointer value associated with the event.
Source Location Location in the source code where the memory was allocated.
Root Location Location in the source code for the root of the stack trace; typically, this is main() or a thread entry function.
Full Trace Full stack trace for the allocation.
Table 3. Additional fields for runtime errors
Name Description
Thread ID TID of the thread.
CPU CPU number (for multicore machines).
Message Error message returned.
Pointer Pointer value associated with the event.
Trap Function Function where the error was caught.
Alloc Kind Type of allocation for the argument (pointer) being validated.
Severity Error severity.
Memory State Whether the pointer memory is used or free.
Source Location Location in the source code where the memory was allocated.
Root Location Location in the source code for the root of the stack trace; typically, this is main() or a thread entry function.
Full Trace Full stack trace for the error.
Full Alloc Trace Full allocation stack trace for the pointer.
Table 4. Additional fields for bin events
Name Description
Size Maximum size, in bytes, for the bin to which the memory belongs. The bin size ranges are based on the Bins counters field under Memory Snapshots. The default size ranges are based on powers of two, with a special bin for large allocations between 4 KB and 2 GB.
Allocations Number of allocated blocks in this bin.
Deallocations Number of freed blocks in this bin.
Table 5. Additional fields for band events
Name Description
Size Size, in bytes, of the preallocated band of memory used.
Total Blocks Number of total blocks in this band.
Free Blocks Number of free blocks in this band.
Page updated: