gcc -finstrument-functions 追踪函数调用,获取程序的执行流程
我们在阅读源码的时候经常会碰到很多回调函数,而单步调试又比较麻烦,所以我们可以用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文件
看不懂没关系,我们addr2line工具
一条条转换太麻烦,可以用如下脚本批量转换
#!/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