天天看點

手動跟蹤函數的調用過程

           今天是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  的指令。