梳理陌生代码函数调用链,finstrument-functions助你快速破局

    拿到一份全新代码,最耗时间的往往不是读语法,而是 理清「谁调了谁、先走哪条路」。逐行读效率低,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)
参数含义

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--;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值