C++源碼調用圖生成器實現

前言

以前受知乎用戶mailto1587啓發,寫了個C++源碼的調用圖生成器,能夠以圖示法顯示C++函數的調用關係,
代碼放在了github倉庫裏,僅供參考:
CodeSnippet/python/SRCGraphviz/c++ at master · Cheukyin/CodeSnippet · GitHubpython

主要思路

利用gcc/g++-finstrument-functions的注入選項,
獲得每一個函數的調用地址信息,生成一個trace文件
而後利用addr2linec++filt函數名及其所在源碼位置從地址中解析出來,
從而獲得程序的Call Stack
而後用pygraphviz畫出來c++

使用示例

好比我如今有A.hppB.hppC.hppABCTest.cpp這幾個文件,
我想看他們的Call Graphgit

源碼以下:
圖片描述github

而後按下面編譯(instrument.c在上面github地址中能夠下載,用於注入地址信息):
g++ -g -finstrument-functions -O0 instrument.c ABCTest.cpp -o test
而後運行程序,獲得trace.txt
輸入shell命令./test
最後
輸入shell命令python CallGraph.py trace.txt test
彈出一張Call Graph
圖片描述shell

圖上標註含義:bash

  • 綠線表示程序啓動後的第一次調用函數

  • 紅線表示進入當前上下文的最後一次調用this

  • 每一條線表示一次調用,#符號後面的數字是序號,at XXX表示該次調用發生在這個文件(文件路徑在框上方)的第幾行spa

  • 在圓圈裏,XXX:YYYYYY是調用的函數名,XXX表示這個函數是在該文件的第幾行被定義的prototype

獲取C/C++調用關係

利用-finstrument-functions編譯選項,
可讓編譯器在每一個函數的開頭和結尾注入__cyg_profile_func_enter__cyg_profile_func_exit
這兩個函數的實現由用戶定義

在本例中,只用到__cyg_profile_func_enter,定義在instrument.c中,
其函數原型以下:
void __cyg_profile_func_enter (void *this_fn, void *call_site);
其中this_fn爲 被調用的地址,call_site爲 調用方的地址

顯然,假如咱們把全部的 調用方和被調用方的地址 都打印出來,
就能夠獲得一張完整的運行時Call Graph

所以,咱們的instrument.c實現以下:

/* Function prototypes with attributes */
void main_constructor( void )
    __attribute__ ((no_instrument_function, constructor));

void main_destructor( void )
    __attribute__ ((no_instrument_function, destructor));

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

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

static FILE *fp;

void main_constructor( void )
{
  fp = fopen( "trace.txt", "w" );
  if (fp == NULL) exit(-1);
}

void main_deconstructor( void )
{
  fclose( fp );
}

void __cyg_profile_func_enter( void *this_fn, void *call_site )
{
    /* fprintf(fp, "E %p %p\n", (int *)this_fn, (int *)call_site); */
    fprintf(fp, "%p %p\n", (int *)this_fn, (int *)call_site);
}

其中main_constructor在 調用main 前執行,main_deconstructor在調用main後執行,
以上幾個函數的做用就是 將全部的 調用方和被調用方的地址 寫入trace.txt

然而,如今有一個問題,就是trace.txt中保存的是地址,咱們如何將地址翻譯成源碼中的符號?
答案就是用addr2line

以上面ABCTest.cpp工程爲例,好比咱們如今有地址0x400974,輸入如下命令
addr2line 0x400aa4 -e a.out -f
結果爲

_ZN1A4AOneEv
/home/cheukyin/PersonalProjects/CodeSnippet/python/SRCGraphviz/c++/A.hpp:11

第一行該地址所在的函數名,第二行爲函數所在的源碼位置

然而,你必定會問,_ZN1A4AOneEv是什麼鬼?
爲實現重載、命名空間等功能,所以C++name mangling,所以函數名是不可讀的

咱們須要利用c++filt做進一步解析:
輸入shell命令 addr2line 0x400aa4 -e a.out -f | c++filt
結果是否是就清晰不少:

A::AOne()
/home/cheukyin/PersonalProjects/CodeSnippet/python/SRCGraphviz/c++/A.hpp:11

注意這個結果中包含了函數名、函數所在文件和行號

調用圖渲染

通過上面的步驟,咱們已經能夠把全部的(調用方, 被調用方)對分析出來了,至關於獲取到調用圖全部的節點和邊,
最後能夠用pygraphviz將 每一條調用關係 畫出來便可,代碼用python實如今 CallGraph.py

相關文章
相關標籤/搜索