gcc/g++ -finstrument-functions 追踪函数调用,获取程序的执行流程

本文借鉴https://blog.csdn.net/u014608280/article/details/82669330,在他的基础上进行了完善和介绍在实际工程中的使用方法。

在阅读不熟悉大型工程特别是c++工程时,程序的回调,类的继承关系复杂会增加阅读难度。

在使用gcc/g++编译工程时使用-finstrument-functions标志会函数进入和退出时调用钩子函数。

void __cyg_profile_func_enter (void *this_fn, void *call_site);
void __cyg_profile_func_exit  (void *this_fn, void *call_site);

我们通过实现这两个钩子函数在编译时加入编译标志可以在打印出当前函数的内存地址,从而通过addr2line工具找到具体的调用位置,将所有的地址串起来就可以形成完整的执行流程。

两个钩子函数的入口参数是函数指针第一个参数指向将要执行的位置,第二个参数指向调用的位置。但是两个位置指针都是内存地址,而不是相对程序起始的偏移地址,这里使用backtrace_symbols将地址进行转换输出。

我们首先重写钩子函数,一个两个程序文件我们可以直接将钩子写到程序中,但如果是一个大型工程且这必然是不方便的而且需要处理依赖关系。我们就想到了可以使用头文件。

在重写钩子函数时使用的printf直接打印到终端,也可以直接将这部分写入文件,但是我个人建议不要把钩子函数和程序输出分离开,这样不便于调试。日志文件的保存可以看我另一篇文章https://blog.csdn.net/cooper1024/article/details/119873836

finstrument.h

#ifndef __FINSTRUMENT_H_
#define __FINSTRUMENT_H_

#ifdef __cplusplus
extern "C"{
#endif

#include <stdio.h>
#include <stdlib.h>
#include <execinfo.h>
#include <unistd.h>
#include <stdlib.h>

static void  __attribute__((no_instrument_function))
__cyg_profile_func_enter(void *this_func, void *call_func)
{
  void* buffer[2]={ this_func, call_func };
  char **point = backtrace_symbols(buffer, 2);
  char call_excl[256]={0};
  char call_adder[64]={0};
  char this_excl[256]={0};
  char this_adder[64]={0};
  sscanf(point[1]  ,"%[^(]%*[^+]+%[^)]", call_excl, call_adder);
  sscanf(point[0]  ,"%[^(]%*[^+]+%[^)]", this_excl, this_adder);

	printf("From %s %s entry function %s %s\n", call_excl, call_adder, this_excl, this_adder);
  free(point);
}

static void  __attribute__((no_instrument_function))
__cyg_profile_func_exit(void *this_func, void *call_func)
{
  void* buffer[2]={ this_func, call_func };
  char **point = backtrace_symbols(buffer, 2);
  char call_excl[256]={0};
  char call_adder[64]={0};
  char this_excl[256]={0};
  char this_adder[64]={0};
  sscanf(point[1]  ,"%[^(]%*[^+]+%[^)]", call_excl, call_adder);
  sscanf(point[0]  ,"%[^(]%*[^+]+%[^)]", this_excl, this_adder);
	printf("Exit function %s %s to %s %s\n", this_excl, this_adder, call_excl, call_adder);
  free(point);
}

#ifdef __cplusplus
};
#endif
#endif

将两个钩子函数通过静态声明在头文件中的方式定义,在需要使用的文件中引入头文件即可。这种方式有一个优点就是我只要在需要跟踪的文件中引用头文件可以输出在改文件中的执行流程,在没有引用的文件中由于找不到这两个钩子函数的定义而不会执行钩子函数;但是在方便的同时有引入了一个问题,在一个大型工程中一个文件一个文件的去加会很麻烦单独加又不知道加在什么位置。这时我们就引入一个新的编译特性-include,使用-include会在每个文件中自动引入设置的头文件。

当程序执行后输出了大量的地址我们使用shll脚本直接将地址进行转换。

下面是实例
main.c

#include <stdio.h>

extern int test();

#ifdef __cplusplus
class Box
{
public:
  void print(void );
};
// 成员函数定义
void Box::print(void)
{
  printf("Hello C++\n");
}
#endif

int func1()
{
  printf("this func1\n");
  return 0;
}

int func(int a, int b)
{
  func1();
  test();
  return a + b;
}

static inline void print(int n)
{
  printf("%d\n", n);
}

int main()
{
#ifdef __cplusplus
  Box Box1;
#endif
  func(3, 4);
  print(func(3,4));
#ifdef __cplusplus
  Box1.print();
#endif
  return 0;
}

test.c

#include <stdio.h>

int test()
{
    printf("Hello world\n");
    return 0;
}

Makefile

all:
	gcc -o test_c main.c test.c -g -finstrument-functions -include finstrument.h
	g++ -o test_cxx main.c test.c -g -finstrument-functions -include finstrument.h

test:
	./test_c > log_c.txt
	./test_cxx > log_cxx.txt
	./add2line.sh log_c.txt backtrace_c.txt
	./add2line.sh log_cxx.txt backtrace_cxx.txt

addr2line.sh

#!/bin/sh

if [ $# != 2 ]; then
  echo 'Usage: addr2line.sh addressfile functionfile'
  exit
fi;

echo > $2
while read line
do
  TYPE=`echo $line | awk '{print $1}'`;
  case $TYPE in
    From)
      EXCL=`echo $line | awk '{print $2}'`
      ADDER=`echo $line | awk '{print $3}'`
      addr2line -e $EXCL -fp $ADDER -s | sed 's/$/;/' >> $2
      echo "-----> call{" >> $2
      EXCL=`echo $line | awk '{print $6}'`
      ADDER=`echo $line | awk '{print $7}'`
      addr2line -e $EXCL -fp $ADDER -s | sed 's/$/;\n/'  >> $2
      ;;
    Exit)
      EXCL=`echo $line | awk '{print $3}'`
      ADDER=`echo $line | awk '{print $4}'`
      addr2line -e $EXCL -fp $ADDER -s | sed 's/$/;/' >> $2
      echo "}<----- return" >> $2
      EXCL=`echo $line | awk '{print $6}'`
      ADDER=`echo $line | awk '{print $7}'`
      addr2line -e $EXCL -fp $ADDER -s | sed 's/$/;\n/' >> $2
      ;;
    *)
      echo $line | sed 's/$/;\n/' >> $2
  esac;
done  < $1

使用make编译生成可执行文件,使用make test将进行测试。log_c.txtlog_cxx.txt存放程序输出,backtrace_c.txtbacktrace_cxx.txt分别存放对应转换处理后的文件。backtrace_c.txtbacktrace_cxx.txt通过格式化工具格式化之后看会更加明了。

log_c.txt

From /lib/x86_64-linux-gnu/libc.so.6 0xe7 entry function ./test_c 0xcc0
From ./test_c 0xceb entry function ./test_c 0xc1b
From ./test_c 0xc47 entry function ./test_c 0xbde
Exit function ./test_c 0xbde to ./test_c 0xc47
From ./test_c 0xc51 entry function ./test_c 0x1106
Hello world
Exit function ./test_c 0x1106 to ./test_c 0xc51
Exit function ./test_c 0xc1b to ./test_c 0xceb
From ./test_c 0xcfa entry function ./test_c 0xc1b
From ./test_c 0xc47 entry function ./test_c 0xbde
Exit function ./test_c 0xbde to ./test_c 0xc47
From ./test_c 0xc51 entry function ./test_c 0x1106
Hello world
Exit function ./test_c 0x1106 to ./test_c 0xc51
Exit function ./test_c 0xc1b to ./test_c 0xcfa
From ./test_c 0xd01 entry function ./test_c 0xc76
7
Exit function ./test_c 0xc76 to ./test_c 0xd01
Exit function ./test_c 0xcc0 to /lib/x86_64-linux-gnu/libc.so.6 0xe7

格式化后的backtrace_c.txt


?? ??:0;
-----> call{
        main 于 main.c:36;

        main 于 main.c:41;
        -----> call{
                func 于 main.c:24;

                func 于 main.c:26;
                -----> call{
                        func1 于 main.c:19;

                        func1 于 main.c:19;
                }<----- return
                func 于 main.c:26;

                func 于 main.c:27;
                -----> call{
                        test 于 test.c:4;

                        Hello world;

                        test 于 test.c:4;
                }<----- return
                func 于 main.c:27;

                func 于 main.c:24;
        }<----- return
        main 于 main.c:41;

        main 于 main.c:41;
        -----> call{
                func 于 main.c:24;

                func 于 main.c:26;
                -----> call{
                        func1 于 main.c:19;

                        func1 于 main.c:19;
                }<----- return
                func 于 main.c:26;

                func 于 main.c:27;
                -----> call{
                        test 于 test.c:4;

                        Hello world;

                        test 于 test.c:4;
                }<----- return
                func 于 main.c:27;

                func 于 main.c:24;
        }<----- return
        main 于 main.c:41;

        main 于 main.c:45;
        -----> call{
                print 于 main.c:31;

                7;

                print 于 main.c:31;
        }<----- return
        main 于 main.c:45;

        main 于 main.c:36;
}<----- return
?? ??:0;


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值