用 LD_PRELOAD 替換動態連結的函式庫

換掉動態連結的函式庫有不少用途, 比方說像 Scott 提的「用來驗證是否執行到特定函式」, 或如 jserv 提的「在沒有原始碼的情況下修補執行檔的行為」。最近又看到這個東西, 記一下筆記以免忘了。

來看程式碼和執行效果。

mylib.c

#include <stddef.h>
#include <stdio.h>

int putchar(int c) {
    printf("call putchar()\n");
    return c;
}

void *memset(void *s, int c, size_t n) {
    printf("call memset()\n");
    return s;
}

main.c

#include <string.h>
#include <stdio.h>

int main(void) {
    char s[10];
    memset(s, 0, 0);
    putchar('X');
    putchar('\n');
    return 0;
}

執行結果

$ gcc -Wall -fpic -shared -o libmylib.so mylib.c
$ gcc -o main main.c
# 沒用 LD_PRELOAD 的結果
$ ./main
X
# 用 LD_PRELOAD 後的結果, 記得加 "./", 不然會找不到 libmylib.so
$ LD_PRELOAD=./libmylib.so ./main
call putchar()
call putchar()
$ strings main | grep memset

可以看出 putchar 有被換成自己編的版本, 但 memset 沒有。用 strings 查看 binary 檔 main 會發覺並沒有呼叫 memset 的跡象。猜測是 compiler 發覺 memset 實際上沒任何作用, 所以沒呼叫。

修改 main.c, 將 memset(s, 0, 0) 換成 memset(s, 0, 1) 再來看看:

$ gcc -o main main.c
$ strings main | grep memset
memset
$ LD_PRELOAD=./libmylib.so ./main
call memset()
call putchar()
call putchar()

結果顯示有換到 memset, 表示之前是完全沒呼叫 memset, 才會以為沒換到。printf 也是如此, 只輸出字串時, 不會呼叫 printf, 而是用 puts。

參考資料: 《Modifying a Dynamic Library Without Changing the Source Code | Linux Journal》

留言

  1. GCC 編譯參數 -fno-builtin 可避免或抑制這類的優化

    回覆刪除
  2. 謝啦, 學到一招, man gcc 看到 -fno-builtin 的效果了。晚點來看看 __builtin_ 相關的說明, 再補充進來

    回覆刪除

張貼留言

這個網誌中的熱門文章

virtualbox 使用 USB 裝置

熟悉系統工具好處多多

如何 git merge 更改檔名的檔案