-finstrument-functions范例

GCC Function instrumentation机制可以用来跟踪函数的调用关系,在gcc中对应的选项为“-finstrument-functions”。可查看gcc的man page来获取更详细信息。
编译时如果为gcc加上“-finstrument-functions”选项,那在每个函数的入口和出口处会各增加一个额外的hook函数的调用,增加的这两个函数分别为:
void __cyg_profile_func_enter (void *this_fn, void *call_site);
void __cyg_profile_func_exit  (void *this_fn, void *call_site);
其中第一个参数为当前函数的起始地址,第二个参数为返回地址,即caller函数中的地址。
这是什么意思呢?例如我们写了一个函数func_test(),定义如下:
static void func_test(v)
{
    /* your code... */
}
那通过-finstrument-functions选项编译后,这个函数的定义就变成了:
static void func_test(v)
{
    __cyg_profile_func_enter(this_fn, call_site);
    /* your code... */
    __cyg_profile_func_exit(this_fn, call_site);
}
我们可以按照自己的需要去实现这两个hook函数,这样我们就可以利用this_fn和call_site这两个参数大做文章。
例如下面这段代码:
instrfunc.c: 
#include <stdio.h>
 
 
#define DUMP(func, call) \
    printf("%s: func = %p, called by = %p\n", __FUNCTION__, func, call)
 
 
void __attribute__((no_instrument_function))
__cyg_profile_func_enter(void *this_func, void *call_site)
{
    DUMP(this_func, call_site);
}
 
 
void __attribute__((no_instrument_function))
__cyg_profile_func_exit(void *this_func, void *call_site)
{
    DUMP(this_func, call_site);
}
 
 
int do_multi(int a, int b)
{
    return a * b;
}
 
 
int do_calc(int a, int b)
{
    return do_multi(a, b);
}
 
 
int main()
{
    int a = 4, b = 5;
    printf("result: %d\n", do_calc(a, b));
    return 0;
}
这段代码中实现了两个hook函数,即打印出所在函数的函数地址以及返回地址。
编译代码:
[zhenfg@ubuntu]code:$ gcc -finstrument-functions instrfunc.c -o instrfunc
[zhenfg@ubuntu]code:$ ./instrfunc 
__cyg_profile_func_enter: func = 0x8048521, called by = 0xb75554e3
__cyg_profile_func_enter: func = 0x80484d8, called by = 0x8048562
__cyg_profile_func_enter: func = 0x804849a, called by = 0x8048504
__cyg_profile_func_exit: func = 0x804849a, called by = 0x8048504
__cyg_profile_func_exit: func = 0x80484d8, called by = 0x8048562
result: 20
__cyg_profile_func_exit: func = 0x8048521, called by = 0xb75554e3
通过反汇编的代码(objdump -D instrfunc)可以看到,这些地址和函数的对应关系为:
__cyg_profile_func_enter: func = 0x8048521(main), called by = 0xb75554e3
__cyg_profile_func_enter: func = 0x80484d8(do_calc), called by = 0x8048562(main)
__cyg_profile_func_enter: func = 0x804849a(do_multi), called by = 0x8048504(do_calc)
__cyg_profile_func_exit: func = 0x804849a(do_multi), called by = 0x8048504(do_calc)
__cyg_profile_func_exit: func = 0x80484d8(do_calc), called by = 0x8048562(main)
result: 20
__cyg_profile_func_exit: func = 0x8048521(main), called by = 0xb75554e3
实际上这就给出了函数的调用关系。

如果不想跟踪某个函数,可以给该函数指定“no_instrument_function”属性。需要注意的是,__cyg_profile_func_enter()和__cyg_profile_func_exit()这两个hook函数是一定要加上“no_instrument_function”属性的,不然,自己跟踪自己就会无限循环导致程序崩溃,当然,也不能在这两个hook函数中调用其他需要被跟踪的函数。

得到一系列的地址看起来不太直观,我们更希望看到函数名,幸运的是,addr2line工具为我们提供了这种可能。我们先看一下addr2line的使用方法:
[zhenfg@ubuntu]code:$ addr2line --help
Usage: addr2line [option(s)] [addr(s)]
 Convert addresses into line number/file name pairs.
 If no addresses are specified on the command line, they will be read from stdin
 The options are:
  @<file>                Read options from <file>
  -a --addresses         Show addresses
  -b --target=<bfdname>  Set the binary file format
  -e --exe=<executable>  Set the input file name (default is a.out)
  -i --inlines           Unwind inlined functions
  -j --section=<name>    Read section-relative offsets instead of addresses
  -p --pretty-print      Make the output easier to read for humans
  -s --basenames         Strip directory names
  -f --functions         Show function names
  -C --demangle[=style]  Demangle function names
  -h --help              Display this information
  -v --version           Display the program's version
首先要注意,使用addr2line工具时,需要用gcc的“-g”选项编译程序增加调试信息。
同样是上面的程序,我们加上-g选项再编译一次:
[zhenfg@ubuntu]code:$ gcc -g -finstrument-functions instrfunc.c -o instrfunc
[zhenfg@ubuntu]code:$ ./instrfunc 
__cyg_profile_func_enter: func = 0x8048521, called by = 0xb757d4e3
__cyg_profile_func_enter: func = 0x80484d8, called by = 0x8048562
__cyg_profile_func_enter: func = 0x804849a, called by = 0x8048504
__cyg_profile_func_exit: func = 0x804849a, called by = 0x8048504
__cyg_profile_func_exit: func = 0x80484d8, called by = 0x8048562
result: 20
__cyg_profile_func_exit: func = 0x8048521, called by = 0xb757d4e3
使用addr2line尝试查找0x8048504地址所在的函数:
[zhenfg@ubuntu]code:$ addr2line -e instrfunc -a 0x8048504 -fp -s
0x08048504: do_calc at instrfunc.c:25
这样一来,就可以通过gcc的“-finstrument-functions”选项结合addr2line工具,方便的对一个程序中的函数进行跟踪。并且既然我们可以自己实现hook函数,那不仅仅可以用来跟踪函数调用关系,你可以在hook函数中添加自己想做的事情,例如添加一些统计信息。
另外,我们知道__builtin_return_address(level)宏可以获得不同层级的函数返回地址,但是在某些体系架构(如mips)中,__builtin_return_address(level)只能获得当前函数的直接调用者的地址,即level只能是0,那这时,就可使用上述方法来跟踪函数调用关系(mips中竟然能用,确实有些小吃惊)。

接下来可以看一下gcc是如何将hook函数嵌入各个函数中的,以反汇编代码中的do_multi()函数为例(这是mips的汇编代码),在mips中,ra寄存器用来存储返回地址,a0-a3用来做函数参数。
004006c8 <do_multi>:
  4006c8:    27bdffd8     addiu    sp,sp,-40
  4006cc:    afbf0024     sw    ra,36(sp)    ;;存储ra寄存器(返回地址)的值
  4006d0:    afbe0020     sw    s8,32(sp)
  4006d4:    afb1001c     sw    s1,28(sp)
  4006d8:    afb00018     sw    s0,24(sp)
  4006dc:    03a0f021     move    s8,sp
  4006e0:    03e08021     move    s0,ra    ;;s0 = ra
  4006e4:    afc40028     sw    a0,40(s8)
  4006e8:    afc5002c     sw    a1,44(s8)
  4006ec:    02001021     move    v0,s0    ;;v0 = s0
  4006f0:    3c030040     lui    v1,0x40
  4006f4:    246406c8     addiu    a0,v1,1736    ;;将本函数的地址赋值给a0寄存器
  4006f8:    00402821     move    a1,v0        ;;将返回地址ra的值赋值给a1寄存器
  4006fc:    0c100188     jal    400620 <__cyg_profile_func_enter> ;;调用hook函数
  400700:    00000000     nop
  400704:    8fc30028     lw    v1,40(s8)
  400708:    8fc2002c     lw    v0,44(s8)
  40070c:    00000000     nop
  400710:    00620018     mult    v1,v0
  400714:    00008812     mflo    s1
  400718:    02001021     move    v0,s0
  40071c:    3c030040     lui    v1,0x40
  400720:    246406c8     addiu    a0,v1,1736    ;;将本函数的地址赋值给a0寄存器
  400724:    00402821     move    a1,v0        ;;将返回地址ra的值赋值给a1寄存器
  400728:    0c10019d     jal    400674 <__cyg_profile_func_exit> ;;调用hook函数
  40072c:    00000000     nop
  400730:    02201021     move    v0,s1
  400734:    03c0e821     move    sp,s8
  400738:    8fbf0024     lw    ra,36(sp)    ;;恢复ra寄存器(返回地址)的值
  40073c:    8fbe0020     lw    s8,32(sp)
  400740:    8fb1001c     lw    s1,28(sp)
  400744:    8fb00018     lw    s0,24(sp)
  400748:    27bd0028     addiu    sp,sp,40
  40074c:    03e00008     jr    ra
  400750:    00000000     nop
上述反汇编的代码中,使用“-finstrument-functions”选项编译程序所增加的指令都已注释出来,实现没什么复杂的,在函数中获得自己的地址和上一级caller的地址并不是什么难事,然后将这两个地址传给__cyg_profile_func_enter和__cyg_profile_func_exit就好了。

参考:调试技巧之 gcc/g++ -instrument-functions 参数 - wangkangluo1 - 博客园 (cnblogs.com)

参考官方关于GCC编译器选项说明:Instrumentation Options (Using the GNU Compiler Collection (GCC))

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值