天天看点

手动跟踪函数的调用过程

           今天是10月13号,不知道为什么日子过的如此的快,大概是假期的原因吧。在十一国庆以后,上了3天课又放假了...感觉研究生的生活越来越没有学生样子啦...老师在很久以前就安排了一个任务给我,叫我完成在arm板子上的视频显示,做过了前期的JPEG的显示,觉得这个问题本身不是很大。大概是自己对这种事情了解的太少,当真正的去接触的时候就觉得难度很大。

            视频本身是有一帧一帧的数据组成,一帧其实可以看成是一张图片,所以说直白点就是让很多张图片,在一秒内一张一张的显示,因为人类眼睛的视觉暂留的特性,在一个频率的情况下就会形成动态效果。可以如果真的这样保存数据的话那样一个很多的视频的数据量也是很大很大的,所以就要对视频进行压缩。老师给与的任务竟然是视频的播放,难道是要做播放器,视频的压缩,我的天啊,我突然觉得自己压力很大...那个时候才真正的感觉到team work的重要性,就像很多程序员一样,都是比较相信自己的能力,也比较独立的,很多事情不喜欢让别人去做,或者对别人做的事情不相信之类的,什么事情都是亲历亲为的,当然我也是一样,很多的东西我喜欢自己完成,自己做的东西才能让自己感觉是对的。可是当项目很大的事情,你会觉得自己很无力,觉得自己看着后面还有那么多的东西要写的时候,你会觉得自己真的不想做。这样就超级容易想放弃的冲动!很多大牛写的东西都很大很大,觉得真的很佩服他们,或者他们真的找到了自己喜欢的东西。关于视频的播放在拖了一段时间以后,老师来看我的进展的时候我和他说了自己的想法,他给予的意见是他不需要我做什么播放器或者是解码器,只要我能会用现成的东西,于是就很偷懒的去网上下了一个库,这是一个相当有名的库哦(FFMPEG),花了不少时间在做移植上,感觉这个东西其实也很难的,很多东西不对,很多东西和别人说的有差别,感觉这个事情必须在自己很冷静的情况下去做,在我伟大的EQ支持下,终于搞定了移植,程序是用他本身的那个例子,我唯一做的就是把我以前关于framebuffer的显示功能给链接起来...这一切花了我不少时间,老师今天下午看了一下成果,好像还挺满意的。说要把一个本科身的人脸识别给移植过来,不过我不急,因为那个本科身本身也还没写好...可以空闲不少时间了,今天下午就打了一下午的酱油。

           说了那么多最近的事情,都好像和题目没有什么关系,呵呵,关于这个题目,是在看<C专家编程>的一个编程挑战题中看到的。其实一开始看见这个题的时候我不会,连一点想法都没有,感觉自己还算太嫩了点.很多关于C的东西我都不是清楚的,状况是我知道他们也知道,他们知道我并不一定知道的状况,惭愧啊.....所以就很伤心了...

先说一下最近网易的一个笔试题目吧,我一同学和我说的,话说网易这个公司是我比较喜欢的,根据我的了解还有朋友的介绍,相对其他同等级别的公司,网易这个公司的企业文化还不错,当然我还没去过,也不清楚...而且它只对浙大校招,让我很向往这个公司,哈哈....又跑题了,这个问题好像是这样的,让你写一个程序,你怎么输出他的行号,函数名,文件名等,其实这个问题你知道的人其实很简单,不知道的人打死你也想不出来的。这就是几个宏而已,__LINE__,__FUNCTION__,__FILE__这些宏。所以我觉得在编程的道路上一定要有一种好奇心,那样才能让你学的越多,知识面越广...正如某某人说的“细节决定一切”。

关于上面的这个题目的答案其实我在上个月我也碰见过,不过好像这回事情没有激起我的兴趣,所以他就被我忽略了...真不好意思啊...哈哈哈...以后不会这样了!话说网易竟然考了这题,让我很想知道编译器还定义了其他什么宏,google了一下,搜索到了两个函数,这个让我很惊讶,也让我有冲动去完成书中的题目。

这个两个函数就是:

void __cyg_profile_func_enter(void *funaddr, void *callsite)__attribute__((no_instrument_function));

void __cyg_profile_func_exit(void *funaddr, void *callsite)__attribute__((no_instrument_function));

          这两个函数的作用是,当你调用一个函数的时候它会自动的调用 __cyg_profile_func_enter,而当程序离开函数的时候他会调用__cyg_profile_func_exit这个函数。其实当你碰到书中的题目的时候,唯一的难度就是在于函数的调用的时候怎么去识别,如果你对每一个你写过的函数你都写入输入输出,那么就是一个改动相当巨大的事情,而且这使得你必须去改变你的源代码了,所以难题就是怎么样去识别函数的调用过程,而这两个函数不是正好就体现了这个功能吗???所以当我看见这两个函数的时候我很兴奋!!先让我介绍这两个函数吧。

           这种现象是属于GCC function instrumentation机制,这种机制出现在gcc2.x中,是一个有cygnus提出。所以要使用这种这些功能,必须在编译的时候加上-finstrument-functions这个选项。都知道C语言实现调用是通过桟来实现的,那么我们将函数调用过程中的过程记录叫做call frame,而上述的两个函数就是在进入函数时和退出函数时被调用。关于这个__attribute__这个东西我也不是很清楚,明天研究一下再说,不过这里的no_instrument_function的作用为了防止进入__cyg_profile_func_enter函数时再次调用这个函数,然后就进入了无限递归的状态。

            问题解决了一半,所以现在可以自动去识别函数的调用,但是要输出函数名字,好象还有点问题,本以为可以使用上面几个介绍的宏,可是现在主要在__cyg_profile_func_enter的函数中,所以这些宏都失效了。不过到现在你还不知道函数参数的具体含义吧,funaddr是函数的开始地址,而callsite则是返回地址,不知道有没有什么东西能将这些东西转变为具体的行号。哈哈...我觉得我是幸运的,在前几天看见了一个GCC的工具,是addr2line这个工具,这个工具能将地址转变为对应的行号之类的,不过一些特别的地址好像是不能进行转化的。可是这是一个具体的命令,不知道怎么用到程序中来,还是一个问题,本来先进行输出,然后再对输出文件进行shell

脚本处理。不过好像又很幸运的在网上碰到了popen这个函数。 

FILE *popen(const char *command,const char *type);

           这个函数的好处是会创建一个管道,并且调用shell,type有两种类型一种是“r” 另一种是“w”,这两种的不同之处就再说r是读管道,w是写入管道,不过这两个只能选择其中一个,关键是这个是调用shell的,并且让shell执行你的命令,那不是很符合我要的功能吗。。。哈哈哈

忘记讲一个东西了,那就是一个dladdr这个函数了,这个函数其实我也不怎么清楚,所以让我多行了解以后再说,不过这个函数的用处是可以通过一个函数地址,知道一个Dl_info 的结构体,

typedef struct {

const char *dli_fname;  /* Pathname of shared object that contains address */               
void       *dli_fbase;  /* Address at which shared object is loaded */               
const char *dli_sname;  /* Name of nearest symbol with address  lower than addr */               
void       *dli_saddr;  /* Exact address of symbol named  in dli_sname */           
} Dl_info;
           

关于dladdr这个函数我在我的下一篇文章已经翻译了这函数参数的具体意思。

           昨天说了,下面这个函数好像对动态库和静态库不能支持,不过经过刚才的研究发现,当你在编译库文件的时候也显示的使用-finstrument-funtions这个去编译的话,最后是可以显示出来的,不过问题是我们不能让本身系统的库在编译的过程中加这个选项,所以不能访问。其实这不是dladdr的错,错应该是上面那个红色的函数没有被调用的错,所以还要继续研究看看能不能继续改进。不过时候dladdr可以显示出动态库中的消息,相当强大的一个函数。

下面是code,好像是可以手动的追踪函数的调用

#define _GNU_SOURCE
#include <stdio.h>
#include <dlfcn.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>



//this func is called when your program has func is called
//thiz is enter func address
//callsite is func back address 
void __cyg_profile_func_enter(void *thiz, void *callsite)__attribute__((no_instrument_function));

void __cyg_profile_func_exit(void *thiz, void *callsite)__attribute__((no_instrument_function));

void display(void *enter, void *callsite, int flag)__attribute__((no_instrument_function));

void display(void *enter, void *callsite, int flag)
{
    Dl_info info;
    char cmd[1024] = "addr2line  -e ";
    char *ptr = cmd + strlen(cmd);
    int strsize = readlink("/proc/self/exe",ptr,sizeof(cmd) - (ptr - cmd) - 1);

    if(strsize == -1)
    {
	fprintf(stderr,"readlink is failure!\n");
	exit(-1);
    }

    FILE *fp = popen(cmd,"w");

    if(!fp)
    {
	perror("popen\n");
	exit(-1);
    }
    
    if(dladdr(enter,&info) == 0)
    {
	fprintf(stderr,"addr to funcname is failure\n");
	exit(-1);
    }

    //flag=1,enter or flag=0,exit
    if(flag)
    {
	char address[256];
	printf("\n-------------------------------------------\n");
	printf("your program has entered a function!\n");
	printf("this run filename is %s\n",info.dli_fname);
	printf("this function name is %s\n",info.dli_sname);
	printf("this function's address is %p\n",enter);
	printf("this function back row is \n");
	sprintf(address,"%p\0",callsite);
	fwrite(address,1,strlen(address) + 1,fp);
    }
    else
    {
	printf("\n-------------------------------------------\n");
	printf("your program will leave this function %s\n",info.dli_sname);
	printf("-------------------------------------------\n");
    }

    pclose(fp);
}

void __cyg_profile_func_enter(void *thiz, void *callsite)
{
    display(thiz,callsite,1);
} 

void __cyg_profile_func_exit(void *thiz, void *callsite)
{
    display(thiz,callsite,0);
}


           
编译的命令一定要加上-rdynamic  -finstrument-functions  的命令。