Linux下庫打樁機制分析 function Interposition


[時間:2017-08] [狀態:Open]
[關鍵詞:linux, libray,打樁,interposition,函數替換,鏈接器,gcc,malloc,free]

0 引言

本文主要參考《深入理解計算機系統》(原書第三版)ch7.13。作為個人知識整理和后續參考使用。

庫打樁(interposition)這個名詞比較陌生,這是由Linux鏈接器所提供的技術,允許用戶截獲對共享庫函數的調用,並執行自己的代碼(當然是在普通權限下,管理員權限通常是禁止使用該技術的)。
使用打樁機制,可以追蹤某個特殊庫函數的調用次數、驗證並追蹤其輸入輸出,甚至把它替換成一個完全不同的實現。

2 基本原理

打樁機制的基本實現原理如下:
給定需要打樁的目標函數,常見一個wrapper函數,其原型和目標函數一致。
利用特殊的打樁機制,可以實現讓系統調用你的wrapper函數而不是目標函數。
wrapper函數中通常會執行自己的邏輯,然后調用目標函數,再將目標函數的返回值傳遞給調用者。

打樁可以發生在編譯時、鏈接時或者程序被加載執行的運行時。不同的階段都有對應的打樁機制,也有其局限性。
下文將以c標准庫中的malloc和free函數的打樁來說明不同打樁機制。基本目標是用打樁來追蹤程序運行時對malloc和free的調用。

3 示例一:編譯時打樁

編譯時打樁說白就是將對目標函數的調用替換為對應wrapper的調用。實現方式很簡單,通過指定編譯指令來實現。
下面代碼實現了一個demo,用於說明如何使用預處理器實現編譯時打樁。包裝函數實現如下:

// malloc.h
#ifndef COMPILE_TIME
#define malloc(size) mymalloc(size)
#define free(ptr) myfree(ptr)
#endif

void * mymalloc(size_t size);
void myfree(void *ptr);
// mymalloc.cpp
#ifdef COMPILE_TIME
#include <stdio.h>
#include <malloc.h>

// malloc wrapper function
void * mymalloc(size_t size) {
    void * ptr = ::malloc(size);
    printf("malloc %p size %u\n", ptr, size);
    return ptr;
}

// free wrapper function
void myfree(void *ptr) {
    ::free(ptr);
    printf("free %p\n", ptr);
}
#endif

在wrapper函數中我們調用目標函數,並打印追蹤記錄。本地的malloc.h頭文件用於替換系統的目標函數調用邏輯,將其切換到對應的包裝函數中。
所有源碼可以在我的SampleCode[https://git.oschina.net/Tocy/SampleCode.git]的interposition-打樁\compile目錄找到,可以使用下面函數指令編譯代碼:

gcc -DCOMPILE_TIME -c mymalloc.cpp
gcc -I. -o intpos main.cpp mymalloc.o

編譯后執行可執行文件,就可以得到將對malloc/free的調用轉到我們的wrapper函數中。

4 示例二:鏈接時打樁

Linux靜態鏈接器支持用--wrap f標志進行鏈接時打樁。這個標志告訴鏈接器,把對符號f的引用解析成__wrap_f(前綴是兩個下划線),還要對符號__real_f的引用解析成f。
我們的wrap函數實現如下:

#ifdef LINK_TIME
#include <stdio.h>

extern "C" {
void * __real_malloc(size_t size);
void __real_free(void * ptr);

// malloc wrapper function
void * __wrap_malloc(size_t size) {
    printf("%s enter %u\n", __FUNCTION__, size);
    void * ptr = __real_malloc(size);
    printf("malloc %p size %u\n", ptr, size);
    return ptr;
}

// free wrapper function
void __wrap_free(void *ptr) {
    __real_free(ptr);
    printf("free %p\n", ptr);
}
}
#endif

所有源碼可以在我的SampleCode[https://git.oschina.net/Tocy/SampleCode.git]的interposition-打樁\link目錄找到使用下面命令編譯:

gcc -DLINK_TIME -c mymalloc.cpp
gcc -c main.cpp
gcc -Wl,--wrap,malloc -Wl,--wrap,free -o intpos main.o mymalloc.o

注意這里是WL(L小寫,不是數字1,Linker option)。-Wl,option標志把option傳遞給鏈接器。option中的每個逗號都會替換為一個空格。即-Wl,--wrap,malloc就是把--wrap malloc傳遞給鏈接器。上面編譯必須分開,否則可能會出錯。

通過鏈接器的命令打樁也可以實現我們的目的,但是有一個缺點,你需要重新連接所有需要監測的模塊。

5 示例三:運行時打樁

編譯時打樁需要訪問程序的源代碼,連接時打樁需要能夠訪問程序的可重定位的對象文件。不過運行時打樁僅需要訪問可執行目標文件即可,它的基本原理是基於動態鏈接器的LD_PRELOAD環境變量的。
如果LD_PRELOAD環境變量被設置為一個共享庫路徑的列表(以空格或分號分隔),那么當你加載和執行一個程序,需要解析未定義的引用時,動態鏈接器會先搜做LD_PRELOAD中給定的庫,然后才搜索任何其他的庫。有了這個機制,當你加載和執行任意可執行文件時,可以對任何共享庫中任意函數打樁,包括libc.so中的malloc和free。

我們的wrapper函數實現如下

#ifdef RUNTIME
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>

// malloc wrapper function
void * malloc(size_t size) {
    printf("%s enter %u\n", __FUNCTION__, size);
    void *(* mallocp)(size_t size);
    char * error;
    
    // get address of libc malloc
    mallocp = dlsym(RTLD_NEXT, "malloc");
    if ((error = dlerror()) != NULL) {
        fputs(error, stderr);
        exit(1);
    }
    void * ptr = mallocp(size);
    printf("malloc %p size %u\n", ptr, (int)size);
    return ptr;
}

// free wrapper function
void free(void *ptr) {
    void (* freep)(void *ptr);
    char * error;
    
    // get address of libc free
    freep = dlsym(RTLD_NEXT, "free");
    if ((error = dlerror()) != NULL) {
        fputs(error, stderr);
        exit(1);
    }

    freep(ptr);
    printf("free %p\n", ptr);
}
#endif

所有源碼可以在我的SampleCode[https://git.oschina.net/Tocy/SampleCode.git]的interposition-打樁\runtime目錄找到,需要通過下面指令編譯:
gcc -DRUNTIME -shared -fpic -o mymalloc.so mymalloc.cpp -ldl -fpermissive

主函數不做修改。但運行時需要使用下面指令:
LD_PRELOAD="./mymalloc.so" ./intpos
如此就可以達到預期的效果,監測對malloc和free函數的調用。

6 補充知識:malloc調試變量——__malloc_hook

如果單純為了處理malloc/free的調用,可以參考下萬能的manual。
__malloc_hook是glibc提供的malloc調試變量中的一個,詳情參考MALLOC_HOOK
只要在代碼中添加__malloc_hook= my_malloc_hook;語句,當前程序中關於的malloc調用都會使用my_malloc_hook函數,簡單方便。但是這組調試變量不是線程安全的,很多新的編譯器已經將該功能廢棄。有興趣的可以參考下。

7 總結

到此,本文開頭的問題已經解決。整理此文目的只是為了加深記憶。
同時了解下基本的內存泄露分析方法。

8 參考


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM