Dynamic Stack Trace Printing in C/C++ on Linux Systems

Nov 22, 2025 · Programming · 9 views · 7.8

Keywords: C Programming | Stack Trace | Linux Debugging | glibc | backtrace Function

Abstract: This technical paper provides an in-depth analysis of dynamic stack trace acquisition and printing techniques in C/C++ on Linux environments. Focusing on the glibc library's backtrace and backtrace_symbols functions, it examines their working principles, implementation methods, compilation options, and performance characteristics. Through comparative analysis of different approaches, it offers practical technical references and best practice recommendations for developers.

Overview of Stack Trace Technology

In software development, debugging and analyzing program execution flow are crucial processes. Stack trace technology helps developers understand function call relationships and identify problem sources. Particularly when dealing with complex logic or random behaviors, such as the pseudo-random number generator call difference analysis mentioned in the problem description, stack trace information provides essential debugging clues.

glibc backtrace Function Family

The glibc library on Linux systems provides a comprehensive stack trace solution. The backtrace() function serves as the core component, capable of retrieving the current thread's call stack information. The function prototype is as follows:

#include <execinfo.h>

int backtrace(void **buffer, int size);

This function populates the specified buffer with return addresses from the current call stack, where each return address corresponds to a stack frame. The buffer parameter is an array for storing addresses, and size specifies the maximum capacity of the array. The function returns the actual number of stack frames obtained.

Symbol Information Resolution

After obtaining address information, it needs to be converted into readable function names and location information. Glibc provides backtrace_symbols() and backtrace_symbols_fd() functions to accomplish this task.

char **backtrace_symbols(void *const *buffer, int size);
void backtrace_symbols_fd(void *const *buffer, int size, int fd);

Both functions can convert address arrays into readable string formats, differing only in their output methods. backtrace_symbols() returns a string array, while backtrace_symbols_fd() directly writes results to a file descriptor.

Complete Implementation Example

Below is a complete implementation of a stack trace printing function:

#include <execinfo.h>
#include <stdio.h>
#include <stdlib.h>

void print_stack_trace(void) {
    void *array[64];
    size_t size;
    char **strings;
    size_t i;

    size = backtrace(array, 64);
    strings = backtrace_symbols(array, size);

    if (strings == NULL) {
        perror("backtrace_symbols");
        return;
    }

    printf("Stack trace (%d frames):\n", size);
    for (i = 0; i < size; i++) {
        printf("%s\n", strings[i]);
    }

    free(strings);
}

Compilation and Linking Options

To obtain complete symbol information, special attention must be paid to linker options during compilation. As mentioned in the best answer, the -rdynamic option must be used:

gcc -rdynamic -o program source.c

This option instructs the linker to add all symbols to the dynamic symbol table, enabling backtrace_symbols() to resolve function names. Without this option, the output will only display address offsets without specific function names.

Practical Application Scenarios

In the specific scenario described in the problem, the developer needs to track calls to the pseudo-random number generator. Stack trace printing code can be inserted at the beginning of the target function:

void random_number_generator(void) {
    print_stack_trace();
    // Random number generation logic
}

By comparing stack trace outputs under different command-line parameters, it can be determined whether the random number generator is triggered by different call paths, thereby resolving behavioral differences.

Performance Considerations

According to benchmark data, the glibc backtrace_symbols method takes approximately 21 microseconds per call. While this is acceptable in most debugging scenarios, it should be used cautiously in performance-sensitive production environments. Conditional compilation or runtime switches can be considered to control stack trace enabling.

Limitation Analysis

This method has several limitations: static function names cannot be displayed, optimized compilation may affect stack frame integrity, particularly tail call optimization can disrupt normal call stack structure. Additionally, this method is not signal-safe and cannot be used directly in signal handlers.

Comparison with Other Methods

Compared to other stack trace methods, the glibc solution offers better portability as it is a standard component of Linux systems. It performs better than GDB scripting methods (600 microseconds/call); compared to C++23's <stacktrace> (7 microseconds/call), it is slightly slower but offers better compatibility.

Best Practice Recommendations

In actual projects, it is recommended to encapsulate stack trace functionality as a configurable debugging module. Control its enabled state through preprocessor directives or runtime flags to avoid unnecessary impact on production environment performance. Additionally, consider adding log level control to output detailed stack trace information only when needed.

Copyright Notice: All rights in this article are reserved by the operators of DevGex. Reasonable sharing and citation are welcome; any reproduction, excerpting, or re-publication without prior permission is prohibited.