Caution: This version of this document is no longer maintained. For the latest documentation, see http://www.qnx.com/developers/docs.

Backtraces

Overview

The libbacktrace library gives you a way to programmatically backtrace a running process from within itself. You can use backtracing for debugging, as well as for diagnostics or logging. Most of the time, you should use gdb for debugging.

The backtrace library lets you:

The backtrace library is compatible with QNX Neutrino 6.3.0 SP2 or later.

Note the following:

The following notes apply only to PowerPC targets:


Note: The backtrace library is an unsupported feature, due to its fragility. The functionality it offers is available via gdb in a safer and more stable way; gdb uses debugging information to unwind, while backtrace uses guessing — it decodes instructions/stack/registers to figure out the return address, and this is prone to errors. GDB uses such guessing only as a fallback when symbols aren't available.

Due to multiple gcc versions that can be used, and in each of them probable different optimizations, backtrace accuracy is directly affected; it is almost certain the backtrace won't work in many cases.


API

The libbacktrace library defines the following data types and variables:

bt_accessor_t
An opaque structure that holds the identity of the thread to backtrace.
bt_memmap_t
An opaque structure that holds the memory map of a given process. A memory map is made of a list of all of the object files (executable and shared libraries) in process memory, and its text segments' location and size.
bt_accessor_t bt_acc_self
A preinitialized accessor used to backtrace BT_SELF.

Note: Don't call bt_init_accessor() or bt_release_accessor() for this global variable.

The library also defines the following functions:

bt_get_backtrace()
Collect a backtrace
bt_init_accessor()
Initialize a backtrace accessor
bt_load_memmap()
Load a memory map associated with a backtrace
bt_release_accessor()
Release an accessor for a backtrace
bt_set_flags()
Set or clear the flags for backtracing
bt_sprn_memmap()
Format the memory map information for a backtrace
bt_sprnf_addrs()
Format the addresses from a backtrace
bt_translate_addrs()
Translate the addresses from a backtrace
bt_unload_memmap()
Unload a memory map associated with a backtrace

In general, here's how you use these functions:

  1. Call bt_init_accessor() to set up the backtrace.
  2. Optionally call bt_set_flags() if you want to do a live backtrace. By default, bt_get_backtrace() freezes a thread before gathering the backtrace.
  3. Call bt_get_backtrace() to collect the backtrace addresses.
  4. Optionally load the memory map for the process by calling bt_load_memmap(). You need to do this if you want to format the information in certain ways.
  5. Optionally call bt_sprn_memmap() to produce a string of the memory map's contents.
  6. Call bt_sprnf_addrs() or bt_translate_addrs() to format the backtrace addresses.
  7. Call bt_unload_memmap() to unload the memory map.
  8. Call bt_release_accessor() to release the accessor.

Examples


Note: To keep the samples short, these examples don't handle any errors.

Obtaining and printing a memory map

Use the following sample code segment to obtain, and then print the contents of a memory map:

char out[1024];

bt_accessor_t acc;
bt_memmap_t memmap;
bt_init_accessor(&acc, BT_SELF);
bt_load_memmap(&acc, &memmap);
bt_sprn_memmap(&memmap, out, sizeof(out));
puts(out);
bt_release_accessor(&acc);

Additional notes about memory:


Note: There are no explicit links between the memory map and a backtrace. Consequently, you're responsible for ensuring that the memory map is reread to account for the proper handling of the removal of the dlopen() and dlclose() processes, as well as the recycling of process IDs.

Backtracing a thread in another process

Use the following sample code segment to repeatedly backtrace a thread in another process by using the remote process's pid and thread tid:

char out[1024];
bt_addr_t pc[16];
bt_accessor_t acc;

bt_init_accessor(&acc, BT_PROCESS, remotepid, remotetid);
bt_load_memmap(&acc, &memmap);
bt_sprn_memmap(&acc, out, sizeof(out));

for (i=0; i<10; i++) {
   cnt=bt_get_backtrace(&acc, pc, sizeof(pc)/sizeof(bt_addr_t));
   bt_sprnf_addrs(&memmap, pc, cnt, "%a\n", out, sizeof(out), 0);
   puts(out);
}
bt_unload_memmap(&memmap);
bt_release_accessor(&acc);

Backtracing another thread within the same process

Backtracing a different thread in the current process is similar to backtracing a thread in a different process (see above). The only difference is how you initialize the accessor structure. The flags passed to bt_init_accessor() define which process to backtrace. If you specify BT_THREAD, a different thread within the same process is set up for backtracing:

bt_init_accessor(&acc, BT_THREAD, tid);

To debug a thread within a different process, set the BT_PROCESS flag and supply the pid and tid to be backtraced:

bt_init_accessor(&acc, BT_PROCESS, getpid(), tid);

Backtracing the current thread

To backtrace the currently running process and thread, you can do one of the following:

You can also use a preinitialized accessor called bt_acc_self.

Doing a BT_SELF backtrace in a signal handler

Providing backtracing that's signal-handler-safe is a special case. From within a signal handler, you can use only bt_get_backtrace(), and you must ensure access to memory is without conflict. For example:

bt_accessor_t acc_sighandler1;
bt_addr_t pc_sighandler1[10];
int cnt_sighandler1;
  
void sighandler1(int sig)
{
    cnt_sighandler1 =
        bt_get_backtrace(&acc_sighandler1, pc_sighandler1,
           sizeof(pc_sighandler1)/sizeof(bt_addr_t));
}

thread_func()
{
    char out[512];
    bt_init_accessor(&acc_sighandler1, BT_SELF);

    signal(SIGUSR2, sighandler1);
    ...
    bt_sprnf_addrs(&memmap, pc_sighandler1, cnt_sighandler1, "%a",
                   out, sizeof(out), 0);
    ...
    bt_release_accessor(&acc);
    ...
}

Backtracing a collection of threads

Occasionally, it may be useful to backtrace a collection of threads in a coherent manner. You can accomplish this by freezing all the threads, backtracing the threads, and then unfreezing them. For example:

bt_accessor_t acc;
hold_all_threads();

for (i=0; i < max_thread; i++) {
    bt_init_accessor(&acc, BT_PROCESS, pid, i);
    bt_set_flags(acc, BT_LIVE_BACKTRACE, 1);
    bt_get_backtrace(&acc, addrs, len);
    bt_release_accessor(&acc);
    save_addrs(addrs);
}

cont_all_threads();