許多開發者都有介入中大型專案的經驗,有時候也常常需要理解原始程式的設計,因此或多或少都有在程式碼迷宮中找路的經驗。有些專案,程式碼結構嚴謹,軟體設計時應用設計模式 (Design Patterns),往往只要見名稱、參數,即可推斷程式結構,閱讀上如沐春風。
但若碰到未經重構的成年老專案,程式邏輯因為年久失修,塞滿各個開發階段的臨時解決方案,常常已經複雜到難以一眼望穿理解。若是記憶力虛弱如我,常常追了後面幾千行、跳了三個檔,就忘了前面幾個檔案的函式名稱。於是常常以紙本畫流程圖輔助理解,但是手繪圖往往不敵跳來繞去的程式碼邏輯。還是得靠程式碼解析視覺化工具來協助理解。
繪製 call graph 的工具非常多,一般可以分作 dynamic analysis 與 static analysis 兩種作法。在臺灣,最知名的商業版本工具,大概是 Source Insight。不過我不用 Windows,對於缺乏原始碼的開發工具興趣也不大。
開源的 dynamic analysis 包含諸如 Jserv 介紹過的 CodeViz 或是 ncc。不過這類工具需要 patch GCC,不適合嵌入式系統專案。因爲原始碼常常只支援特定平臺,或是無法取得編譯工具原始碼。此外,不同版本的 compiler 偶爾會造成不同的問題,造成使用上的困難。
一種比較乾淨的作法是像 KCachegrind 利用 valgrind 來收集資料,或 Gprof2Dot 利用 gprof 的輸出資料。再者是利用 GCC 的除錯功能,把 internal representation (RTL) 倒出來,再用 egypt 或 Python-RTL 來判讀或繪圖。
至於開源的 static analysis,則包括 Fred Chien 介紹過的 cflow,或是 Doxygen 也有類似的繪圖功能。也有工具是配合 cscope 或 GLOBAL,例如有人幫 cscope 刻過圖形界面,Vim 有個 CCTree 可用。
以上這些工具各有優缺點。最常見的問題是許多工具無法處理 function pointer/dynamic dispatch,最終還是需要人力介入。另外一個使用上的困擾是,這些程式會一口氣畫出整個程式碼的結構圖。
太多資訊其實妨礙理解,因爲用途常常只是追一個臭蟲,程式開發者只想畫出特定路徑來釐清問題。而 context-sensitive 的 call graph 測試工具又耗費資源。
若用 CodeLite、Code::Blocks、Eclipse CDT 等開發工具,這些工具已經內建或者整合了 cscope/GLOBAL,提供 symbol lookup 功能。開發者很容易用滑鼠查閱函式定義或實做,也可以搭配 LXR 來瀏覽程式碼。已經不需要像是 cbrowser 這類專屬的程式碼瀏覽工具。
如果需要手動將目前程式情境視覺化的工具,也可以在網路上找到。網路上已經有其他開發者做了 Bash: C Call Trees and Graphs 或是 global-calltree。或是像 Yao-Po Wang 的方法,記錄函式進出點,再手動繪圖。
上述工具大多是整合 shell scripts,操作上有點不便。另外我也不喜歡 Call Tree 的圖式,因爲樹狀圖無法表現遞迴或交互關係。
於是查找一下,決定拿 cscope 加上 Graphviz 的 DOT 語法來用,改了一個 CallGraphviz。它的功能是以 Graphviz 做為前端,後端還是使用 cscope 查 symbols,為了可以即時瀏覽便取用 xdot 當作介面。xdot 是以 PyGtk 開發,非常容易更改,不到三百行就加入我需要的功能。
▲ 圖1 使用 CallGraphviz 自動畫出的程式邏輯流程圖
使用方法:
* 執行 python visualizer.py
* 按下【New】,選擇要分析 C/C++ 專案目錄。
* 於「Search symbol」鍵入要追蹤的函式名稱。
* 每次鍵入新名稱,他會自動對應圖中已輸入函式是否爲 caller or callee,並自動畫圖。
CallGraphviz 可以將繪圖結果存成 dot 格式檔案,然後再利用 dot 指令轉換格式。不過它只紀錄曾經查過的名稱,開啟時重新查 cscope 而已。若圖大時,每次開啟可能會十分緩慢。
CallGraphviz 原始碼可於 github 下載,授權採用 GNU Lesser General Public License。
延伸閱讀:Python Call Graph
蔡志展 (Rex Tsai) 或網名 chihchun,現為自由工作者,從事開源軟體顧問或開發服務。倡議並推廣自由軟體與開放源碼,早期 KaLUG 成員,現常出席 Tossug、Hacking Thursday 聚會,亦是開源人年會(COSCUP)籌備志工。長期 Debian、OpenWrt 使用者。關注議題甚廣,進一步資訊請參考 https://people.debian.org.tw/~chihchun/ 。
評論
thanks for the code. It's been a very helpful tool while writing my thesis.
FYI, I've forked your project on github to fulfill my requirements. Just added a button to toggle rank direction between 'LR' and 'TB', plus recursive add/delete syntax, eg. 'symbol/*' and '-symbol'
You can find it here:
github.com/.../callgraphviz