2018年1月23日 星期二

程式海中的救生圈

之前「運用 gprof + graphviz 學習 Open Source」、「運用 gprof + graphviz 學習 Open Source (續)」這兩篇文章算是頗受好評。不過最近筆者工作上沒在碰 Linux 了,大多數時間都是用 Keil C。

筆者接手的這些 Keil C 程式(ARM based),自然是沒辦法放到 Linux 上編譯
、然後用 gprof + graphviz 加以分析(加上 SoC 是公司自行設計)。

除了無限期試用版 SourceInsight 外,還有什麼方法可以在前人留下的程式海中自救呢?筆者最近從網路上找到了兩只救生圈,用起來感覺還不錯,推薦給大家。

cflow2dot

從字面上就可以看出來是 "cflow to dot",dot 就是指 graphviz,這在前面提過了,這邊就不浪費篇幅解釋。cflow 其實多年前筆者就用過 Windows console 版,其實他就是一個把 c source code 裡面除了函數呼叫外的東西通通刪掉、只留下函數呼叫的工具。比方下面這個範例:
/* main.c */
int main(int argc, char **argv)
{
    foo();
    bar();
    return 0;
}

/* foo.c */
void foo(void)
{
    printf("foo\n");
    xyz();
}

/* bar.c */
void bar(void)
{
    printf("bar\n");
    xyz();
}

/* xyz.c */
void xyz(void)
{
    printf("xyz\n");
}
執行命令:
$ cflow main.c 
輸出結果:
main() <int main (int argc, char **argv) at main.c:2>:
    foo()
    bar()
不過這跟想像有點出入,很多人應該跟我一樣預期 xyz() 也應該長出來,好在 cflow 可以輸入多個檔案:
$ cflow *.c
輸出結果:
main() <int main (int argc, char **argv) at cflowtest.c:3>:
    foo() <void foo (void) at foo.c:3>:
        printf()
        xyz() <void xyz (void) at xyz.c:3>:
            printf()
    bar() <void bar (void) at bar.c:3>:
        printf()
        xyz() <void xyz (void) at xyz.c:3>:
            printf()

cflow 預設根節點為 main(),但有時我們只是想觀察某個函數,這時候可以改用:
$ cflow -m foo *.c 
輸出結果:
foo() <void foo (void) at foo.c:3>:
    printf()
    xyz() <void xyz (void) at xyz.c:3>:
        printf()
筆者以前還拿這樣的結果用 PowerPoint 傻傻的畫老半天,現在有 cflow2dot,就不用那麼辛苦了,請先參考官方網站安裝 cflow2dot,接著輸入以下指令:
$ cflow2dot -i *.c -f svg
選項 -i 代表輸入檔案,-f 代表輸出格式(此處是 SVG),目錄下會看到 cflow0.svg...cflow3.svg,用瀏覽器打開如下:

有點讓人失望,原本預料他會輸出以下畫面:


筆者試驗了很久沒試出來,按照官網的說明,他應該是一個檔案輸出一個 svg。官網聲稱可以輸出 pdf 然後有 hyper link 可以直接跳到 source code 的對應之處,這部份筆者沒有實驗出來,有哪位先進實驗成功敬請分享,感恩~

其實這樣也算夠用了,畢竟如果把所有的檔案合併輸出成一個超大 svg,那可能會大到瀏覽器無法載入,也大到不知道從何看起。

如果您覺得不夠,接下來的重型武器應該能滿足您

callgraph

callgraph 是對岸泰曉科技發佈到 github 上的幾隻 shell script,功能比起 cflow2dot 簡直有過之而無不及,甚至還有模糊批配這種東西,簡直是霸氣外露,不愧是我大天朝出品。(這幾隻 script 原本的目的是用來分析 Linux Kernel 0.11 版,看起來跟"Linux 內核設計的藝術"這本書有關聯,這本書筆者也有,不過放了很久都沒看,汗顏)。


安裝方式就不提了,都是中文請自行研究,這次我們看看能不能輸出我們想要的菱形圖,輸入以下指令:
$ callgraph -f main -d ./
選項 -f 代表 call graph 根節點,-d 代表搜尋路徑或檔案,-d 的目的是加快搜尋速度,不然 script 會找出一大堆類似名稱的函數,若檔案很多搜尋速度也會直線下降(所以知道 SSD 的重要性吧?)。輸出結果如下:
Func: main
Match: 1
File:
     0  All files under ./
     1 ./main.c:int main(int argc, char **argv)
Select: 0 ~ 1 ? 
請選 0(如果選 1,script 只會分析 main.c),然後會得到 main.all.svg,把他打開看看:

這次得到了我們想要的結果。各位,真是太爽拉~~

心得

cflow2dot 可以得到一個 .c 的全景,callgraph 則可以針對某個 function 深入探索。假如您有雙螢幕,其中一個螢幕放上這些圖,不但對您的工作極有助益,也可以顯示您是一位尊爵不凡 的 Professional Programmer,讓您的主管對您讚譽有加(這小子真行)。

不過講正經的,製作 call graph 最主要目的是"大腦超載時代的思考學"一書所強調的「記憶外部化」,如果只靠人腦死背硬記,恐怕 coding 這種工作真如某幸福企業主管講的找剛畢業的就好了。


其實類似工具還有比較多人知道的 doxygen,不過跑起來很花時間筆者使用過一次就不用了。如果您能接受 gcc 上打補丁的方式,也可以考慮一下 CodeViz

不過筆者建議分析程式應該以 call graph 為輔,以 data 為主,也就是 data 在程式中是如何工處理的,資料的流向為何?資料結構產生了哪些變化?很可惜目前還沒找到合適的工具,如果有哪位先進在這方面有經驗,也請不吝分享,謝謝!

沒有留言:

張貼留言