Linux 共享庫LD_PRELOAD環境變量

共享庫轉載器有許多可供配置的環境變量,比如我們前面介紹的LD_LIBRARY_PATH環境變量。本文只重點介紹LD_PRELOAD環境變量,因爲這個環境變量體現了共享庫一個非常重要的特性:共享庫覆蓋。

下面是man手冊中對於LD_PRELOAD環境變量的介紹:

              A  whitespace-separated  list  of additional, user-specified, ELF shared libraries to be loaded before all others.
              This can be used to selectively override functions in other shared libraries.   For  set-user-ID/set-group-ID  ELF
              binaries, only libraries in the standard search directories that are also set-user-ID will be loaded.

我們以一個覆蓋標準庫sleep函數的例子來說明如何應用LD_PRELOAD環境變量。

首先給出我們的測試程序,程序很簡單,休眠1秒鐘退出。

#include <unistd.h>

int main()
{
    return sleep (1);
}

使用如下命令編譯

gcc -o test  test.c

使用ldd查看可執行文件test依賴的共享庫:

$ ldd test 
        linux-gate.so.1 =>  (0x003c5000)
        libc.so.6 => /lib/libc.so.6 (0x4e8b2000)
        /lib/ld-linux.so.2 (0x4e88f000)


在未設置LD_PRELAOD環境變量時,程序運行將調用標準庫的sleep函數。下面我們將定義自己的sleep函數,把這個函數編譯到一個庫,並使用這個庫覆蓋標準庫的sleep函數。

首先給出定義sleep函數的源文件mysleep.c:

#define _GNU_SOURCE 
#include <dlfcn.h>
#include <stdio.h>

unsigned int sleep(unsigned int milliseconds)
{
    fprintf (stderr, "my sleep () called\n");
    
    static unsigned int (*funcptr) (unsigned int) = NULL;
    if (!funcptr)
        funcptr = (unsigned int (*) (unsigned int)) dlsym (RTLD_NEXT, "sleep");
    if (!funcptr) {
        fprintf (stderr, "dlsym Error:%s\n", dlerror ());
        return -1;
    }
    unsigned int seconds = milliseconds/1000;
    if (seconds%1000>=500)
        seconds++;
    if (!seconds)
        seconds = 1;
    return (*funcptr) (seconds);
}

我們的sleep函數接受一個以毫秒爲單位的參數,函數首先會調用fprint輸出一行調試信息,以幫助我們瞭解sleep函數是否覆蓋成功。然後我們使用dlsym函數獲取標準庫中sleep的指針,並使用標準庫的sleep來實現我們自定義的sleep。需要強調的是,如果我們想要覆蓋標準庫的某個函數,我們自定義的函數,必須和被覆蓋的函數聲明相一致。

使用以下makefile文件編譯庫libmysleep.so.1:

CFLAGS=-Wall 
LIBCFLAGS= $(CFLAGS) -fPIC
CC=gcc
LIBOBJS=mysleep.o
AR=ar rc
LIBRARY=libmysleep.so.1.0.0
SONAME=libmysleep.so.1


$(LIBRARY):$(LIBOBJS)
        $(CC) -shared -Wl,-soname,$(SONAME) -o $@ $(LIBOBJS) -ldl
        ln -sf $@ libmysleep.so
        ln -sf $@ $(SONAME)

%.o:%.cpp
        $(CC) $(LIBCFLAGS) -c -o $@ $< 

clean:
        rm -rf $(LIBRARY) $(LIBOBJS) libmysleep.so* main

使用下面的命令添加庫目錄到共享庫裝載程序的搜索目錄:

export LD_LIBRARY_PATH=`pwd`

把我們自己的庫libmysleep.so.1添加到LD_PRELOAD環境變量的覆蓋庫列表中:

export LD_PRELOAD=libmysleep.so.1

然後使用ldd查看可執行文件test依賴的共享庫:
$ ldd test 
        linux-gate.so.1 =>  (0x001e3000)
        libmysleep.so.1 => /home/wayz11/tem/lib_test/preload/libmysleep.so.1 (0x0089f000)
        libc.so.6 => /lib/libc.so.6 (0x4e8b2000)
        libdl.so.2 => /lib/libdl.so.2 (0x4ea5f000)
        /lib/ld-linux.so.2 (0x4e88f000)
前後對比可發現比沒有設置LD_PRELOAD環境變量時多了兩個共享庫依賴,其中一個是我們自己的覆蓋庫libmysleep.so.1,另一個是libmysleep.so.1庫的依賴庫libdl.so.2,因爲我們調用了dlsym函數。

好了,我們已經成功完成了標準庫sleep函數的覆蓋,運行程序輸出如下:

$ ./test
my sleep () called

最後一步也很重要,請刪除LD_PRELOAD環境變量,除非你想在當前Session中讓自己的庫一直覆蓋下去,也許你只是在測試:

$ unset LD_PRELOAD


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章