在 Linux 理解大型 C/C++ 專案的輔助工具

fcamel
fcamel的程式開發心得
6 min readJan 7, 2018

記錄一下平時用來讀程式碼的輔助工具。

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 的例子:

SkCanvas class hierarchy

網頁上圖內的方格都可以點,點擊後會跳到該 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 等格式。

--

--