function call trace in c program
Created:
Updated:
backtrace functions in c source code
When we debugging, we sometimes need to figure out how the function was called. Here, I will describe the c functions that can trace back the function call. In a function that requires traceback, it finds the return addresses of the functions by calling backtrace() and backtrace_symbols(), and after that, print them out with a printf statement.
source code
backtrace.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <stdio.h>
#include <execinfo.h>
#include <stdlib.h>
#define BUF_SIZE (100)
void func3()
{
int i, num_of_addrs;
void *buff[BUF_SIZE];
char **symbol_str;
num_of_addrs = backtrace(buff, BUF_SIZE);
printf("backtrace() returned : %d \n", num_of_addrs);
symbol_str = backtrace_symbols(buff, num_of_addrs);
if (symbol_str == NULL) {
printf("backtrace_symbols returned NULL");
return;
}
for (i = 0; i < num_of_addrs; i++)
printf("%s\n", symbol_str[i]);
free(symbol_str);
}
void func2()
{
func3();
}
void func1()
{
func2();
}
int main(void)
{
printf("main enter\n");
func1();
printf("main exit\n");
}
Functions are called as following order.
- main() -> func1() -> func2() -> func3() -> backtrace(), backtrace_symbols()
backtrace() returns the number of addresses returned in buff. Each return address from the corresponding stack frame is saved in buff array. backtrace_symbols() translates the addresses into an array of strings that describe the addresses symbolically.
build
1
$ gcc -o backtrace backtrace.c -rdynamic -g
- -rdynamic : Pass the flag “-export-dynamic” to the ELF linker. This is needed to allow obtaining backtraces from within a program.
- -g : Produce debugging information in the operating system’s native format. This is needed when using “addr2line” to convert addresses into correcponding source code line number.
output
1
2
3
4
5
6
7
8
9
10
11
$ ./backtrace
main enter
backtrace() returned : 6
./backtrace(func3+0x2e) [0x5655555541b3]
./backtrace(func2+0xe) [0x565555554286]
./backtrace(func1+0xe) [0x565555554297]
./backtrace(main+0x1a) [0x5655555542b4]
/lib64/libc.so.6(__libc_start_main+0xeb) [0x7efbff4ffe0b]
./backtrace(_start+0x2a) [0x5655555540ca]
main exit
$
In this example, There are 6 elements in buff array. The result shows return address of each stack frame.
ex) func2+0xe == 0x1286 == return address of func3() stack frame.
1
$ objdump -D backtrace > backtrace.lst
And open backtrace.lst file in text editor.
1
2
3
4
5
6
7
8
0000000000001278 <func2>:
1278: 55 push %rbp
1279: 48 89 e5 mov %rsp,%rbp
127c: b8 00 00 00 00 mov $0x0,%eax
1281: e8 ff fe ff ff callq 1185 <func3>
1286: 90 nop
1287: 5d pop %rbp
1288: c3 retq
addr2line
convert addresses into file names and line numbers. Here is an example to convert symbol addres((func2+0xe) and (func3+0x2e)) into source code line number. Check if the result matches the above source code.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ nm backtrace | grep -ni func2
14:0000000000001278 T func2
$ addr2line -a -f -e backtrace 0x1286 //0x1286 == (func2+0xe)
0x0000000000001286
func2
/home/chuljeon39a/src/backtrace/backtrace.c:31
$ nm backtrace | grep -ni func3
15:0000000000001185 T func3
$ addr2line -a -f -e backtrace 0x11a3 //0x11a3 == (func3+0x2e)
0x00000000000011a3
func3
/home/chuljeon39a/src/backtrace/backtrace.c:13
cflow
It generates a C-language flowgraph. Refer to cflow man page.
1
2
3
4
5
6
7
8
9
10
11
$ cflow backtrace.c
main() <int main (void) at backtrace.c:38>:
printf()
func1() <void func1 () at backtrace.c:33>:
func2() <void func2 () at backtrace.c:28>:
func3() <void func3 () at backtrace.c:7>:
backtrace()
printf()
backtrace_symbols()
free()
$
Leave a comment