拿到一份全新代码,最耗时间的往往不是读语法,而是 理清「谁调了谁、先走哪条路」。逐行读效率低,gdb 单步又太慢。
-finstrument-functions 是 GCC/Clang 编译器的函数插桩选项,用于在编译时自动在每个函数的入口和出口插入调用钩子函数,常用于函数调用栈追踪、性能分析或调试。
一、它解决什么问题?
| 传统方式 | 痛点 |
|---|---|
|
肉眼看代码 |
调用链深、间接调用多,容易迷路 |
|
grep 函数名 |
只能看「定义/引用」,看不到运行时顺序 |
|
gdb 单步 |
准确但极慢,不适合第一次摸底 |
|
画调用图工具 |
静态分析对函数指针、条件分支无能为力 |
插桩追踪的思路是:编译器在每个函数 进入/退出 时自动调用钩子,运行一次程序(或加载一次模块),就能得到 真实执行路径。
二、核心机制
-finstrument-functions 是 GCC/Clang 的编译选项(默认关闭,对应 -fno-instrument-functions)。
2.1 编译器做了什么?
在每个函数:
- 入口 → 插入
__cyg_profile_func_enter(this_fn, call_site) - 出口 → 插入
__cyg_profile_func_exit(this_fn, call_site)
| 参数 | 含义 |
|---|---|
|
|
当前函数起始地址 |
|
|
返回地址(调用点下一条指令,可定位「谁调用的」) |
2.2 使用要求(必记三条)
自己实现两个钩子,否则链接报错
钩子必须加 __attribute__((__no_instrument_function__)),否则递归插桩 → 栈溢出
建议加 -g,配合 addr2line 把地址转成「文件名:行号」
2.3 副作用
开启后编译器倾向于 减少内联,否则插桩会失效——对调试是好事,对发布版性能需留意(仅用于分析构建,不要进生产)。
三、完整代码示例
#include <stdio.h>
#include <execinfo.h>
#include <stdlib.h>
#define print_trace()\
do{\
void *array[10];\
size_t size;\
char **strings;\
size_t i;\
size = backtrace (array, 2);\
strings = backtrace_symbols (array, size);\
if(size == 2) {\
fprintf(stderr,"%s\n", strings[1]);\
}\
free (strings);\
}while(0)
static __thread int stacks=0;
void __attribute__((__no_instrument_function__))__cyg_profile_func_enter(void *this_func, void *call_site)
{
int i =0 ;
for(i = 0 ; i < stacks ;i++){
fprintf(stderr,"\t");
}
print_trace();
stacks++;
}
void __attribute__((__no_instrument_function__))__cyg_profile_func_exit(void *this_func, void *call_site)
{
if(stacks > 0)
stacks--;
}
void func_1_1_1(){
;
}
void func_1_1(){
func_1_1_1();
}
void func_1(){
func_1_1();
}
void func_2(){
;
}
void func_3(){
func_2();
}
int main()
{
func_1();
func_2();
func_3();
return 0;
}
编译:gcc test.c -finstrument-functions -rdynamic -o test
运行查看结果:

我们可以看到运行之后每个函数调用关系,代码执行流程,一眼看出:main → func_1 → func_1_1 → func_1_1_1,比翻四个函数定义快得多。
同样,使用内核代码可以替换使用
void __attribute__((__no_instrument_function__))
__cyg_profile_func_enter(void *this_func, void *call_site)
{
char buff[100]="";
int i =0;
for(i=0;i<stack;i++){
strcat(buff,"\t");
}
printk("---%s %pS ---start\n",buff,this_func);
stack++;
}
void __attribute__((__no_instrument_function__))
__cyg_profile_func_exit(void *this_func, void *call_site)
{
stack--;
}
1万+

被折叠的 条评论
为什么被折叠?



