顯示具有 tracing code 標籤的文章。 顯示所有文章
顯示具有 tracing code 標籤的文章。 顯示所有文章

2013年8月19日 星期一

用 doxygen 產生 class hierarchy diagram

最近需要讀比較複雜 C++ 程式, 常常看到一堆 class 有個祖宗十八代, 要找出眾多子代的 class 或是某個 method 到底是那一代祖先實作的, 有些麻煩。

原本想自己玩看看 clang, 用 clang 分析原始碼產生 graph, 再套 graphviz 畫出來。轉念一想, 這麼常見的需求, 應該有人做好了。況且, 自己處理單檔可能不會麻煩, 但若多個檔案需要不同 include path (-I) 時, 到也有些頭痛。

上網查一下, 發覺 stackoverflow 有人推薦用 doxygen, 一試就靈, 最棒的是 doxygen 不需另外讀編譯 C/C++ 的設定檔, 它直接讀取指定的目錄。先跑 doxygen -g myconfig 產生設定檔 myconfig, 再修改 myconfig:

  • INPUT
  • RECURSIVE = YES

最後執行 doxygen myconfig 即可。

產生的網頁裡可看到各個 class 的祖譜, 非常方便。我猜 doxygen 可能是直接做文字分析, 不是用 compiler 下手。

實測的結果也的確有問題, Ubuntu 12.04 的 doxygen 有點舊, class 有用到 C++11 的 keyword final 時, final 反而會被當成 class name。幸好在 doxygen 1.8.2 版已解決, 先裝 doxygen 舊版, 再自己抓原始碼編譯, 用 checkinstall 封裝成 package 即可解掉這問題。

用 checkinstall 封裝的好處是, 可從 dpkg -L PKG_NAME 看裝的內容, 日後有問題也可用 dpkg -r PKG_NAME 刪除。

Btw, doxygen 參數一堆, 有閒再來研究看看有什麼好用的東西。

2013-09-10 更新

若遇到 macro 造成 doxygen 分析錯誤, 可試著要求 doxygen 展開特定的 macro。作法是修改下列的設定:

  • ENABLE_PREPROCESSING = YES
  • MACRO_EXPANSION = YES
  • EXPAND_ONLY_PREDEF = YES
  • PREDEFINED = ABC=xyz
  • EXPAND_AS_DEFINED = ABC

其中 EXPAND_AS_DEFINED 填入希望展開的 macro。若執行後沒有展開成功, 可以設 PREDEFINED 告知 doxygen 如何展開目標 macro。見 Doxygen Manual: Preprocessing 了解和其它前處理相關的設定。

2013年8月15日 星期四

用 python gdb 客製化 backtrace 的結果 (2)

幫之前寫的指令 bt 加上參數 -s, 使用 -s 時, 會一併記錄 backtrace 每個 frame 附近的原始碼。

以下是一個範例輸出:

(gdb) bt -s
#0   A::hello at b.cpp:8
  | class A
  | {
  | public:
  |   void hello(int n)
  |   {
->|     std::cout << n << std::endl;
  |   }
  |
  |   void foo(int n)
  |   {

#1   A::bar at b.cpp:18
  |     bar(n + 1);
  |   }
  |
  |   void bar(int n)
  |   {
->|     hello(n + 10);
  |   }
  | };
  |
  | int main(void) {

#2   A::foo at b.cpp:13
  |     std::cout << n << std::endl;
  |   }
  |
  |   void foo(int n)
  |   {
->|     bar(n + 1);
  |   }
  |
  |   void bar(int n)
  |   {

#3   main at b.cpp:24
  |   }
  | };
  |
  | int main(void) {
  |   A a;
->|   a.foo(5);
  |   return 0;
  | }

修改後的 python script 如下:

作法大致和 《用 python gdb 客製化 backtrace 的結果》一樣, 只是改取檔案的 fullname, 再自己讀出相關的原始碼。這部份用 python 實作, 相當地輕鬆寫意。

2013年8月10日 星期六

用 macro 組合變動式參數, 強化 log 函式

程式除錯時常需要加些 log 印出變數內容, 通常會希望附帶印出所在的函式。每次要自己重打一次函式名稱太麻煩了, 可以利用 C 的 macro 避免重覆的動作。

先來看個範例程式:

以及輸出結果:

$ g++ a.cpp -o a; ./a
result: 3
$ g++ -DDEBUG_PRINT a.cpp -o a; ./a
int Calculator::add(int, int) this=0x7ffff03c1d7f a = 2, b = 1
result: 3

有 #define DEBUG_PRINT 的才會真的執行 DebugPrintf, 除了兼顧原本 printf 有的變動式參數外, 順便自動補上函式名稱和目前物件的指標; 反之則完全不會執行到, 不會增加額外負擔。

有需要的話, 可以稍微修改 DebugPrintf, 如同 《trace C/C++ function call 的方法》 記錄檔名和行數。

關於巨集這行:

#define DebugPrintf(format, args...) fprintf(stderr, "%s this=%p " format, __PRETTY_FUNCTION__, this, ##args)

用到的語法包含:

  • C 的 "abc" "def" 會組成 "abcdef"
  • args... 比 __VA_ARGS__ 易於閱讀
  • 只有一個參數的時候, __VA_ARGS__ 會自動移掉前一個 ","

可以參照 Variadic Macros - The C Preprocessor 了解細節。

2013年8月3日 星期六

用 python gdb 客製化 backtrace 的結果

需求

想要了解模組之間函式呼叫的關係時, 與其一層層比對多個類別之間的呼叫關係, 不如直接在最後一個呼叫函式放中斷點, 直接顯示 backtrace。但是當函式裡有太多參數或 template 時, backtrace 的 frame 訊息會變得很長, 不易閱讀。我的目的只是找出呼叫的函式名稱、檔名和行數, 函式帶的參數反而是困擾。

作法一: 用 gdb.execute()

一個簡單的作法是截取 gdb 的輸出, 然後解析文字去掉不要的部份:

Btw, 上面的作法還順便幫行首的標號上色。

但是, 使用 cgdb 時會無法運作, 理由是 cgdb 使用 GDB MI, gdb.execute('backtrace') 的結果不是原本看到的格式, 難以解析。

作法二: 用 gdb.Frame() API

只好改用中規中矩的方式逐一讀取 frame, 取出需要的資訊:

將上面的 script 存到 /path/to/gdb/scripts/backtrace.py, 接著在 $HOME/.gdbinit 裡加入以下設定:

python
sys.path.insert(0, '/path/to/gdb/scripts')
import backtrace
end

之後就能用 bt 顯示精簡後的 backtrace 了, 也方便手動複製貼上到筆記裡。以下是一個輸出例子:

(gdb) bt
# 0  A::hello at a.cpp:8
# 1  A::bar at a.cpp:13
# 2  A::foo at a.cpp:18
# 3  main at a.cpp:25

Btw, 若是需求比較簡單, 可以試看看 Print Settings, 有些選項可以改變 backtrace 顯示的訊息。

參考資料:

在 Fedora 下裝 id-utils

Fedora 似乎因為執行檔撞名,而沒有提供 id-utils 的套件 ,但這是使用 gj 的必要套件,只好自己編。從官網抓好 tarball ,解開來編譯 (./configure && make)就是了。 但編譯後會遇到錯誤: ./stdio.h:10...