trace C/C++ function call 的方法

目的

方法一: 使用 gcc 參數 -finstrument-functions

參考資料:

優點:

  • 自動含蓋所有函式。

缺點:

  • 可能增加太多執行負擔, 雖然可以配合 -finstrument-functions-exclude-file-list 去除不想觀察的函式, 對於大專案來說有些不方便。

方法二: 自己寫 logger

使用 C/C++ 或 gcc 提供的語法, 自己寫 logger 沒有想像中的麻煩:

  • 可用 gcc 的 __PRETTY_FUNCTION__ 或標準的 __func__ 取得函式名稱
  • __LINE__ 取得行數
  • __FILE__ 取得檔名

於是可定個巨集:

#define LOG_IT() MyFunctionLogger logger(__FILE__, __LINE__, __PRETTY_FUNCTION__)

然後用 vim 的全域取代:

:% s/^{/&\r  LOG_IT();\r\r/c

這會逐個詢問是否取代下列字串:

void foo()
{
  // blah blah

void foo()
{
  LOG_IT();

  // blah blah

Btw, 由此可知, 函式的左大括號放在行首比放在行尾來得方便。

MyFunctionLogger 只是個 wrapper, 目的是利用 RAII 記錄進入和離開函式, 這樣即使函式中間有任何 return, 都不會漏記離開函式:

class MyFunctionLogger
{
public:
  MyFunctionLogger(const char *file, int line, const char *func)
      : m_file(file), m_line(line), m_func(func)
  {
    MyLogger::Instance()->EnterFunctionCall(m_file, m_line, m_func);
  }

  ~MyFunctionLogger()
  {
    MyLogger::Instance()->LeaveFunctionCall(m_file, m_line, m_func);
  }

private:
  const char *m_file;
  int m_line;
  const char *m_func;
};

MyLogger 可以做的事很多, 比方說:

  • 用 file 做為 tag 分類輸出。
  • 在 EnterFunctionCall() 和 LeaveFunctionCall() 記錄層級, 輸出 function call 時, 視 function call 的深度做對應的縮排。
  • 在 EnterFunctionCall() 和 LeaveFunctionCall() 記錄目前時間, 可在 LeaveFunctionCall() 時計算執行時間, 協助 profiling。

若 MyLogger 會被多個 thread 使用, 別忘了將 MyLogger 寫成 thread-safe, 不過以觀察或除錯的角度來說, 偷懶不作應該也OK吧。

若目標模組是純 C 的程式, 可用 gcc 的 cleanup attribute 做出 RAII 的效果, 達到和 MyFunctionLogger 一樣的效果。

留言

這個網誌中的熱門文章

(C/C++ ) 如何在 Linux 上使用自行編譯的第三方函式庫

熟悉系統工具好處多多

virtualbox 使用 USB 裝置