gcc选项跟踪函数调用

本文最后更新于:2026年5月14日 下午

初体验

关键字:trace

有时需要追踪函数究竟挂在了哪儿,gdb是一个不错的调试工具,但遇到栈被破坏的情况,并不能打印出调用栈,这时下面的方法就排上用处了。

增加gcc编译选项 -finstrument-functions
gcc会自动在函数的入口和出口增加一个函数调用,函数原型为:

1
2
void __cyg_profile_func_exit(void* callee, void* callsite) __attribute__((no_instrument_function));
void __cyg_profile_func_enter(void* callee, void* callsite) __attribute__((no_instrument_function));

以下为自定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <execinfo.h>

void __cyg_profile_func_exit(void* callee, void* callsite) __attribute__((no_instrument_function));
void __cyg_profile_func_enter(void* callee, void* callsite) __attribute__((no_instrument_function));

void __cyg_profile_func_enter(void* callee, void* callsite) {
void *funptr = callee;
char **p = backtrace_symbols(&funptr, 1);
printf("\033[;31m Entering: %s \033[0m\n", *p);
free(p);
}

void __cyg_profile_func_exit(void* callee, void* callsite) {
void *funptr = callee;
char **p = backtrace_symbols(&funptr, 1);
printf("\033[;31m Exiting: %s \033[0m\n", *p);
free(p);
}

运行时就会打印出来函数调用来了,如:

1
2
3
4
5
Entering: ./blink() [0x1bb2c] 
Entering: ./blink() [0x2028c]
Entering: ./blink() [0x1ffe4]
Entering: ./blink() [0x1f400]
Exiting: ./blink() [0x1f400]

再用addr2line(进程加了-g编译)来解析地址:

1
/opt/toolchains/crosstools-arm-gcc-9.2-linux-4.19-glibc-2.30-binutils-2.32/usr/bin/arm-buildroot-linux-gnueabi-addr2line -e tmp/blink -f 0x1f400

输出如下:

1
2
hgdpi_is_debug
/home/leon/code/bcm-ax3000-cmcc/userspace/private/apps/blink-sdk-publish/adapt/public_adapt/blink_msg.cpp:57

工程中好用的一个实现

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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
#define _GNU_SOURCE
#include <stdio.h>
#include <stdint.h>
#include <dlfcn.h>
#include "hi_util_log.h"

static __thread int depth;

void __attribute__((no_instrument_function))
print_indent()
{
for (int i = 0; i < depth; i++)
printf(" ");
}

void __attribute__((no_instrument_function))
print_name(void *func)
{
Dl_info info;

// 完全无法解析符号,那么打印地址
if (!dladdr(func, &info)) {
printf("%p", func);
return;
}

// 如果可以解析函数,打印函数名字
if (info.dli_sname) {
printf("%s", info.dli_sname);
return;
}

// 如果可以解析到是哪个库和相对地址,打印库+相对地址(可以通过addr2line完成解析)
if (info.dli_fname && info.dli_fbase) {
printf("%s+0x%lx", info.dli_fname, (unsigned long)((char *)func - (char *)info.dli_fbase));
return;
}

printf("%p", func);
}

void __attribute__((no_instrument_function))
__cyg_profile_func_enter(void *this_fn, void *call_site)
{
if (hi_log_call_trace_get()) {
print_indent();
printf("-> ");
print_name(this_fn);
printf("\n");
}

depth++;
}

void __attribute__((no_instrument_function))
__cyg_profile_func_exit(void *this_fn, void *call_site)
{
if (depth > 0)
depth--;

if (hi_log_call_trace_get()) {
print_indent();
printf("<- ");
print_name(this_fn);
printf("\n");
}
}

打印示例:

1
2
3
4
5
6
7
8
9
10
11
<- /lib/libhi_ipc.so+0x6b1b
-> /lib/libmib.so+0x9d29
<- /lib/libmib.so+0x9d29
-> /lib/libmib.so+0xbbe0
-> mib_conf_global_configs
<- mib_conf_global_configs
-> mib_table_config_get_json_config
<- mib_table_config_get_json_config
-> mib_json_dump_file
-> mib_conf_global_configs

注意

(1)static内敛的函数不会打印函数名,能打印-> /lib/libmib.so+0xbb5a,通过addr2line解析为:

1
2
3
$ addr2line -e build-x86/rootfs/lib/libmib.so -f 0xbb5a    
mib_table_handler_configfile_backup
/home/leon/code2/starnet_plt/gateway/cml/mib_v2/source/mib_table_handler.c:506

(2)跟踪函数下面的所有调用都要添加__attribute__((no_instrument_function))属性,避免产生循环
(3)编译选项添加-finstrument-functions,链接选项添加-rdynamic,链接库添加dl


gcc选项跟踪函数调用
https://leon0625.github.io/2023/08/28/b38c13ae5dd0/
作者
leon.liu
发布于
2023年8月28日
许可协议