共享库转载器有许多可供配置的环境变量,比如我们前面介绍的LD_LIBRARY_PATH环境变量。本文只重点介绍LD_PRELOAD环境变量,因为这个环境变量体现了共享库一个非常重要的特性:共享库覆盖。

下面是man手册中对于LD_PRELOAD环境变量的介绍:

[plain]  view plain  copy
 print ?
  1. <span style="color:#000099;">A  whitespace-separated  list  of additional, user-specified, ELF shared libraries to be loaded before all others.  
  2. This can be used to selectively override functions in other shared libraries.   For  set-user-ID/set-group-ID  ELF  
  3. binaries, only libraries in the standard search directories that are also set-user-ID will be loaded.</span>  

我们以一个覆盖标准库sleep函数的例子来说明如何应用LD_PRELOAD环境变量。

首先给出我们的测试程序,程序很简单,休眠1秒钟退出。

[cpp]  view plain  copy
 print ?
  1. #include <unistd.h>  
  2.   
  3. int main()  
  4. {  
  5.     return sleep (1);  
  6. }  

使用如下命令编译

[plain]  view plain  copy
 print ?
  1. gcc -o test  test.c  

使用ldd查看可执行文件test依赖的共享库:

[plain]  view plain  copy
 print ?
  1. $ ldd test   
  2.         linux-gate.so.1 =>  (0x003c5000)  
  3.         libc.so.6 => /lib/libc.so.6 (0x4e8b2000)  
  4.         /lib/ld-linux.so.2 (0x4e88f000)  


在未设置LD_PRELAOD环境变量时,程序运行将调用标准库的sleep函数。下面我们将定义自己的sleep函数,把这个函数编译到一个库,并使用这个库覆盖标准库的sleep函数。

首先给出定义sleep函数的源文件mysleep.c:

[cpp]  view plain  copy
 print ?
  1. #define _GNU_SOURCE   
  2. #include <dlfcn.h>  
  3. #include <stdio.h>  
  4.   
  5. unsigned int sleep(unsigned int milliseconds)  
  6. {  
  7.     fprintf (stderr, "my sleep () called\n");  
  8.       
  9.     static unsigned int (*funcptr) (unsigned int) = NULL;  
  10.     if (!funcptr)  
  11.         funcptr = (unsigned int (*) (unsigned int)) dlsym (RTLD_NEXT, "sleep");  
  12.     if (!funcptr) {  
  13.         fprintf (stderr, "dlsym Error:%s\n", dlerror ());  
  14.         return -1;  
  15.     }  
  16.     unsigned int seconds = milliseconds/1000;  
  17.     if (seconds%1000>=500)  
  18.         seconds++;  
  19.     if (!seconds)  
  20.         seconds = 1;  
  21.     return (*funcptr) (seconds);  
  22. }  

我们的sleep函数接受一个以毫秒为单位的参数,函数首先会调用fprint输出一行调试信息,以帮助我们了解sleep函数是否覆盖成功。然后我们使用dlsym函数获取标准库中sleep的指针,并使用标准库的sleep来实现我们自定义的sleep。需要强调的是,如果我们想要覆盖标准库的某个函数,我们自定义的函数,必须和被覆盖的函数声明相一致。

使用以下makefile文件编译库libmysleep.so.1:

[plain]  view plain  copy
 print ?
  1. CFLAGS=-Wall   
  2. LIBCFLAGS= $(CFLAGS) -fPIC  
  3. CC=gcc  
  4. LIBOBJS=mysleep.o  
  5. AR=ar rc  
  6. LIBRARY=libmysleep.so.1.0.0  
  7. SONAME=libmysleep.so.1  
  8.   
  9.   
  10. $(LIBRARY):$(LIBOBJS)  
  11.         $(CC) -shared -Wl,-soname,$(SONAME) -o $@ $(LIBOBJS) -ldl  
  12.         ln -sf $@ libmysleep.so  
  13.         ln -sf $@ $(SONAME)  
  14.   
  15. %.o:%.cpp  
  16.         $(CC) $(LIBCFLAGS) -c -o $@ $<   
  17.   
  18. clean:  
  19.         rm -rf $(LIBRARY) $(LIBOBJS) libmysleep.so* main  

使用下面的命令添加库目录到共享库装载程序的搜索目录:

[plain]  view plain  copy
 print ?
  1. export LD_LIBRARY_PATH=`pwd`  

把我们自己的库libmysleep.so.1添加到LD_PRELOAD环境变量的覆盖库列表中:

[plain]  view plain  copy
 print ?
  1. export LD_PRELOAD=libmysleep.so.1  

然后使用ldd查看可执行文件test依赖的共享库:
[plain]  view plain  copy
 print ?
  1. $ ldd test   
  2.         linux-gate.so.1 =>  (0x001e3000)  
  3.         libmysleep.so.1 => /home/wayz11/tem/lib_test/preload/libmysleep.so.1 (0x0089f000)  
  4.         libc.so.6 => /lib/libc.so.6 (0x4e8b2000)  
  5.         libdl.so.2 => /lib/libdl.so.2 (0x4ea5f000)  
  6.         /lib/ld-linux.so.2 (0x4e88f000)  
前后对比可发现比没有设置LD_PRELOAD环境变量时多了两个共享库依赖,其中一个是我们自己的覆盖库libmysleep.so.1,另一个是libmysleep.so.1库的依赖库libdl.so.2,因为我们调用了dlsym函数。

好了,我们已经成功完成了标准库sleep函数的覆盖,运行程序输出如下:

[plain]  view plain  copy
 print ?
  1. $ ./test  
  2. my sleep () called  

最后一步也很重要,请删除LD_PRELOAD环境变量,除非你想在当前Session中让自己的库一直覆盖下去,也许你只是在测试:

[plain]  view plain  copy
 print ?
  1. $ unset LD_PRELOAD  
Logo

更多推荐