在 Linux 理解大型 C/C++ 專案的輔助工具
記錄一下平時用來讀程式碼的輔助工具。
gj
一般專案可以用 grep / git grep / ag 這類沒有建 index 的搜尋工具,但有百萬行以上的程式碼時,沒建 index 的搜尋太慢了,搜個字串可能等上數分鐘還不會搜完。反之,透過 index 搜尋只要幾秒,index 已在記憶體內則是 <0.1s。
針對自己的需求,基於 id-utils 作了可在命令列或 vim 內使用的工具,細節見 github 上的說明。
若找不到 symbol,有可能使用的名稱其實是 #define
的結果,參考《gcc 展開前置處理的技巧》找出原始名稱。
cquery
我自己用慣 gj + vim 後沒有換用別套工具瀏覽程式碼。不過 cquery 看起來很威,有機會可以試試。
doxygen
doxygen 會分析程式碼產生文件,我主要是用它產生 class hierarchy,下圖是 skia 的 SkCanvas 的例子:
網頁上圖內的方格都可以點,點擊後會跳到該 class 的文件,列出 member function 等資訊。
安裝:
$ sudo apt-get install doxygen graphviz
設定:
$ doxygen -g myconfig
然後編輯 myconfig,需要填的欄位包含:
- INPUT = # 預設讀目前目錄
- RECURSIVE = YES
- EXCLUDE = … # 稍微減少執行時間
執行:
$ doxygen myconfig
會產生目錄 html,內含一包網頁。
gdb
我比較常用 gdb 看 backtrace 了解目標函式的進入點,還有在程式 crash 後讀取 core dump 得知程式掛掉的原因。網路上關於 gdb 的教學相當多,這裡不多提。《gdb 雜項心得》是我自己用的備忘。
strace
strace 會列出程式執行的 system call,效率很好且不需要 debug symbol。我比較常用它找出影響程式行為設定檔的位置。
實例: ps 從那裡取得 process 資訊?
$ strace -f -e open ps
...
open("/proc/31702/stat", O_RDONLY) = 6
open("/proc/31702/status", O_RDONLY) = 6
open("/proc/31703/stat", O_RDONLY) = 6
open("/proc/31703/status", O_RDONLY) = 6
+++ exited with 0 +++
由此可知 ps
從 /proc/ 讀取 process 的資訊。《使用 strace 了解程式讀取資料的來源》有更多例子。
ltrace
ltrace 用來看第三方函式庫用的檔案,不過我個人的使用經驗是程式稍大的時候,使用 ltrace 會讓程式當掉,比較少用它。在會用 SystemTap 後,需要用它的時候也可以改用 SystemTap,似乎不太有使用的時機。
實例:
$ cat hello.c
#include <stdio.h>int main(void) {
printf("hello, world!\n");
printf("hello, %s!\n", "world");
return 0;
}$ make hello
cc hello.c -o hello$ ltrace ./hello
__libc_start_main(0x400566, 1, 0x7ffc34b91428, 0x400590 <unfinished ...>
puts("hello, world!"hello, world!
) = 14
printf("hello, %s!\n", "world"hello, world!
) = 14
+++ exited (status 0) +++
ltrace 的輪出表示編譯器將第一個 printf() 換成 puts,第二個才用 printf()。
SystemTap
SystemTap 將 SystemTap script 編成 C 程式、編成 kernel module、載入 kernel,然後在觀察結束後再移除剛載入的 kernel module。所有 process 呼叫的函式都會經過 kernel,所以可以用 SystemTap 注入觀察程式,觀察函式呼叫的順序、相關變數等,只要有 debug symbol,kernel 或 user space 的程式都適用。
以 skia 為例,用 debug build 編好 viewer 後,使用以下 script 觀察用到那些 SkCanvas 的函式:
$ cat skia.stp
probe begin {
printf("ready\n");
}probe process("out/Debug/viewer").function("*@*core/SkCanvas.*").call {
printf("%s\n", pp());
}
執行 skia viewer,然後執行 SystemTap:
$ sudo stap skia.stp
ready
接著切換 viewer 顯示的畫面, stap 會輸出執行到的函式:
大概知道想觀察的範圍在某個目錄或檔案時,用 SysmtemTap 可以很方便地找出執行到的程式碼,縮小要讀程式的範圍。之前遇過的實例有:
- 用 gdb 或塞 log 發現沒執行到 member function X,於是改用 SystemTap 掃整個檔案,發覺在進入 X 前的函式有用多型且有 subclass 實作,所以進入 X 前就跳到 subclass 的實作去了。
- 之前有手動 patch 某個第三方函式庫,升級函式庫後某段 patch 完全沒被執行,用 gdb 在上層或上上層設中斷點也都沒反應。於是用 SystemTap 掃相關套件整個目錄,發覺第三方函式庫換了另一套實作,繼而找出需要 patch 的新進入點。
關於 SystemTap 的安裝說明,寫在《用 SystemTap 找出送 SIGKILL 的 process》,更詳細的用法寫在《用 SystemTap 追踪 user space 程式執行的流程》。
GenMyModel
閱讀多個 class 互動時,畫出 class diagram 標示 class 之間的使用權、ownership、creator,對理解整體概念很有幫助。我是用 GenMyModel 畫 class diagram (UML diagram)。這是付費軟體,價格不高,和省去的時間相比很划算。這裡和這裡有畫出來的例子。
相信也有很多替代用的免費軟體,例如 draw.io 還不錯用,可離線使用,輸出 XML、PNG 等格式。