backtrace functions
RAHALI Rahma
The backtrace functions are used to obtain a list of function calls (the call stack) that led to a certain
point in a program. They are particularly useful for debugging situations such as segmentation faults,
where it’s important to identify which functions were called before the crash occurred in order to
locate the source of the problem.
The main functions used for this purpose are backtrace(), backtrace_symbols(), and
backtrace_symbols_fd().
int backtrace(void *buffer[size], int size);
The backtrace() function records the current call stack (the sequence of function calls that
led to the current point of execution) in the array pointed to by buffer. The parameter size
specifies the maximum number of stack frames to record. For example, if size is set to 5, the
function will store up to the last five return addresses from the call stack.
Each element in the buffer array is of type void *, which represents a generic pointer to a
memory address. Each of these addresses corresponds to a return address, i.e., the point in
the program where execution should continue after the current function returns.
The function returns the actual number of addresses stored, which may be less than size if
the stack depth is smaller.
char **backtrace_symbols(void *const buffer[], int size);
The backtrace_symbols() function converts the raw memory addresses obtained from
backtrace() into strings that describe each stack frame.
Each string in the returned array provides a symbolic representation of a frame in the call stack,
typically including:
The function name (if available)
The offset within that function
And the actual return address
The function returns an array of string pointers that is dynamically allocated (using malloc()
internally).
Therefore, it is the programmer’s responsibility to free this memory after use to avoid memory
leaks.
void backtrace_symbols_fd(void *const buffer[size], int size, int fd);
Instead of returning an array of strings this function writes the strings to the file descriptor fd
(for example, standard error STDERR_FILENO or a log file).
the static functions are not explored to the backtrace
All functions related to backtracing, such as backtrace() and backtrace_symbols(), are
provided by the <execinfo.h> library, which must be included in your program.
Examples
Example 1 :
This program demonstrates how to print the current call stack. The function func2() creates an array to store
up to 10 return addresses from the active stack frames, which are collected using backtrace(). These addresses
are then converted into symbols and written directly to the standard error output using
backtrace_symbols_fd(). The program execution flows from main() to func1() and then to func2(), allowing the
backtrace to display the sequence of function calls that led to the point where backtrace() is invoked
when executing this program we get this output
The output shows the current call stack of the program at the point where backtrace() was called. Each line
represents a stack frame and includes the binary name, an offset within the function, and the actual memory
address of the return point. For example, ./example(+0x11d5)[0x4011d5] indicates a return address at offset
0x11d5 in the example binary. While this information identifies the sequence of function calls, the raw addresses
and offsets are not immediately readable. This is where addr2line becomes necessary: it translates these
addresses into readable source code locations (file names and line numbers)
To ensure that the addresses in the backtrace correspond consistently to specific locations in the source
code, it is recommended to compile the program with the -no-pie option. here’s an explanation : By default,
Linux systems produce Position-Independent Executables (PIEs) to improve security. This causes the
memory addresses of functions to change at each execution, making it difficult to map backtrace addresses
reliably using tools like addr2line. Compiling with -no-pie disables this randomization, producing a fixed-
address executable, which allows the backtrace addresses to be accurately translated into file names and line
numbers for debugging purposes.
So if we want readable informations of backtrace, here are the main steps :
1. Compile with the options -g and -no-pie : -g to add debug info to the binary and -no-pie
to have static addresses of functions.
2. Execute your program, you will get something like :
3. Use addr2line to get the corresponding line in your code :
Example 2 :
In this example, we will explore the use of backtrace() in a situation involving a segmentation
[Link] main idea is to define a signal handler for the segmentation fault (SIGSEGV) signal, and
within this handler, call the backtrace() function to capture the call stack at the moment of the
crash.
To simulate the segmentation fault, we dereference a null pointer.
the output :
0x4011e5 corresponds to the most recent function call when backtrace was executed
So if we explore the adress just before which is 0x1401243 with addr2line
So as we can see this address corresponds to the line 27 in our code and it’s exactly
where the segmentation fault occured and the handler function was called