貌似簡單的問題:「如何得知 malloc/free 的呼叫次數?」

簡單的作法

  • 定義全域變數來紀錄: int malloc_count = 0, free_count = 0;

  • 透過巨集 #define MALLOC(x) do { if (malloc(x)) malloc_count++; } while (0)

  • 這有什麼問題?

    • 要改寫原始程式碼,將 malloc 換成 MALLOC 巨集,沒更換到的話,就不會追蹤到
    • 對 C++ 不適用,即便底層 libstdc++ 也用 malloc()/free() 來實做 new 和 delete
    • 使用到的函式庫 (靜態和動態) 裡頭若呼叫到 malloc()/free(),也無法追蹤到
  • 要徹底解決這問題,其實就需要理解動態連結器 (dynamic linker) 的協助

  • 以 GNU/Linux 搭配 glibc 為例

  • File: malloc_count.c

  • malloc_count.c
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <dlfcn.h>

void* malloc(size_t size)
{
    char buf[32];
    static void* (*real_malloc)(size_t) = NULL;

    if (real_malloc == NULL) {
        *(void **)(&real_malloc) = dlsym(RTLD_NEXT, "malloc");
    }

    sprintf(buf, "malloc called, size = %zu\n", size);
    write(2, buf, strlen(buf));
    return real_malloc(size);
}

編譯和執行:

$ gcc -D_GNU_SOURCE -shared -fPIC -o /tmp/libmcount.so malloc_count.c -ldl
$ LD_PRELOAD=/tmp/libmcount.so ls

即可得知每次 malloc() 呼叫對應的參數,甚至可以統計記憶體配置,完全不需要變更原始程式碼。這樣的技巧,我們稱為 interpositioning。可能的應用是遊戲破解, 執行時期追蹤, sandboxing / software fault isolation (SFI), profiling,或者效能最佳化的函式庫 (如 TCMalloc)。

透過設定 LD_PRELOAD 環境變數,glibc 的 dynamic linker (ld-linux.so) 會在載入和重定位 (relocation) libc.so 之前,載入我們撰寫的 /tmp/libmcount.so 動態連結函式庫,如此一來,我們實做的 malloc 就會在 libc.so 提供的 malloc 函式之前被載入。當然,我們還是需要「真正的」 malloc,否則無法發揮作用,所以透過 dlsym 去從 libc.so 載入 malloc 程式碼,這裡 RTLD_NEXT 參數告知動態連結器,我們想從下一個載入的動態函式庫載入 malloc 的程式碼位址。


书籍推荐