欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-07-13 23:17:16
...

    我们在阅读源码的时候经常会碰到很多回调函数,而单步调试又比较麻烦,所以我们可以用gcc 的-finstrument-functions 选项打印函数调用栈。

   如果我们在编译、链接的时候在gcc加上-finstrument-functions,gcc会自动在函数的入口调用 __cyg_profile_func_enter函数,在函数出口调用__cyg_profile_func_exit函数。参数this_fn 为当前函数的起始地址,call_site为返回地址,即caller函数中的地址。注意inline函数也会调用这两个函数,如果不想调用这两个函数,在声明函数时增加no_instrument_function属性。

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

举例说明,假如我们有函数
 

void func ()
{
    printf("Hello world!\n");
}

在编译时gcc增加了-finstrument-functions选项,函数就会变成

void func()
{
    __cyg_profile_func_enter(this_fn, call_site);
    printf("Hello world!\n");
    __cyg_profile_func_exit(this_fn, call_site);

}

如果不想让这个函数调用,声明时我们需要这样

void func() __attribute__((no_instrument_function));

顺便说一下__attribute__关键字

作用:__attribute__可以设置函数属性,变量属性和类型属性。

格式:放在声明的尾部;之前,attribute之前和后面各有两个下划线,后面是一对括弧,括弧里是相应的属性。

举例:(packed属性)如下结构体

typedef struct STUDENT
 {
   int age;
   char c;
 }student;

我们sizeof这个结构体会发现结果是8,这是因为编译器会对结构体调整,内存对齐的结果。如果我们不需要内存对齐,可以这样声明

typedef struct STUDENT
{
    int age;
    char c;
}__attribute__((packed)) student;

我们再sizeof结果就会为5.

言归正传

我们有程序finstrument.c

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

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

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

int main()
{
	func(3, 4);
	print(func(3,4));
	return 0;
}

实现__cyg_profile_func_enter和__cyg_profile_func_exit函数在my_debug.c中

#ifndef __MY_DEBUG_H__
#define __MY_DEBUG_H__

#include <stdio.h>

void __attribute__((no_instrument_function)) debuf_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
#include <stdarg.h>
#include <stdlib.h>
#include "my_debug.h"

#define DEBUG_FILE_PATH  "./mydebug.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, "a");
	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", call, this);
}

void  __attribute__((no_instrument_function))
__cyg_profile_func_exit(void *this, void *call)
{
	debug_log("Exit\n%p\n%p\n", call, this);
}

编译命令

 gcc finstrument.c my_debug.c -g  -finstrument-functions -o finstrument

执行可执行文件生成mydebug.log文件

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

看不懂没关系,我们addr2line工具

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

一条条转换太麻烦,可以用如下脚本批量转换

#!/bin/sh

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

cat $2 | while read line
      do
          if [ "$line" = 'Enter' ]; then
              read line1
              read line2
              addr2line -e $1 -f $line1 -s >> $3
              echo "-----> call" >> $3
              addr2line -e $1 -f $line2 -s | sed 's/^/    /' >> $3
              echo >> $3
          elif [ "$line" = 'Exit' ]; then
              read line1
              read line2
              addr2line -e $1 -f $line2 -s | sed 's/^/    /' >> $3
              echo "<----- return" >> $3
              addr2line -e $1 -f $line1 -s >> $3
              echo >> $3
          fi;
      done

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

相关标签: gcc C语言