gcc 选项-finstrument-functions实现函数调用栈追踪

        当我们需要明确函数的调用关系时,可以在编译的时候使用gcc的编译选项-finstrument-functions来打印函数调用栈。

        在编译时加上-finstrument-functions后,gcc会在函数内部增加两个hook函数的调用,具体就是在每个函数的入口处调用__cyg_profile_func_enter函数,在函数的出口处调用__cyg_profile_func_exit函数。两个函数的声明如下:

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

        其中,参数this_fn为当前函数的起始地址,call_site为当前函数的返回地址,即caller函数中执行完当前函数的下一条指令的地址。这两个函数的具体实现,可以根据你的需求自行实现。

        举个例子,我们有整体的测试源代码test.c如下:

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

int add(int a, int b){
    return a + b;
}

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

int main(){
    print(add(1, 2));
    return 0;
}

        同时,为了自定义hook函数,我们还需要新增ftrace.c和ftrace.h,编译测试源文件test.c时需要将ftrace.c一起编译并链接成一个可执行文件。

#include <stdarg.h>
#include <stdlib.h>
#include "ftrace.h"
 
#define DEBUG_FILE_PATH  "./ftrace.log"
 
void  __attribute__((no_instrument_function)) 
debug_log(const char *format, ...)
{
	FILE *fp;
	va_list ap;
	va_start(ap, format);
	
	fp = fopen(DEBUG_FILE_PATH, "w");
	if(NULL == fp)
	{
		printf("Can not open debug file.\n");
		return;
	}
	vfprintf(fp, format, ap);
	va_end(ap);
	fflush(fp);
	fclose(fp);
}
 
void  __attribute__((no_instrument_function))
__cyg_profile_func_enter(void *this, void *call)
{
	debug_log("Enter\n%p\n%p\n", this, call);
}
 
void  __attribute__((no_instrument_function))
__cyg_profile_func_exit(void *this, void *call)
{
	debug_log("Exit\n%p\n%p\n", this, call);
}
#ifndef __FTRACE_H__
#define __FTRACE_H__

void __attribute__((no_instrument_function)) debug_log(const char *format, ...);
void __attribute__((no_instrument_function)) __cyg_profile_func_enter(void*, void*);
void __attribute__((no_instrument_function)) __cyg_profile_func_enter(void*, void*);

#endif

注意,ftrace.h中声明的三个函数需要加上no_instrument_function的属性,不然自己追踪自己就会使程序陷入死循环,追踪函数中调用的函数也需要加上该属性。

1.  执行编译命令:

gcc test.c ftrace.c -g -finstrument-functions -o test

 2. 运行可执行文件,生成trace的log用于函数调用栈的分析:

./test

补充ftrace.log的图片

3. 使用工具addr2line来转化log中的地址为具体的函数名和源文件行位置:

addr2line -e test -a 0x4007b8 -fp -s

编译一个shell脚本addr2line.sh批量转化ftrace.log中的地址:

#!/bin/sh
 
if [ $# != 3 ]; then
    echo 'Usage: addr2line.sh executefile addressfile outputfile'
    exit
fi;

rm -rf trans_ftrace.log

space_num=0

print_spaces() {
    printf "%${1}s" ""
}
 
cat $2 | while read line
      do
          if [ "$line" = 'Enter' ]; then
              read line1
              read line2
              print_spaces count
              echo "-----> call" >> $3
              print_spaces count
              addr2line -e $1 -pf $line1 -s >> $3
              ((count++))
          elif [ "$line" = 'Exit' ]; then
              ((count--))
              read line1
              read line2
              print_spaces count
              echo "<----- return" >> $3
              print_spaces count
              addr2line -e $1 -fp $line2 -s >> $3
          fi;
      done

./addr2line.sh test ftrace.log trans_ftrace.log

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值