共享库转载器有许多可供配置的环境变量,比如我们前面介绍的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.
首先给出我们的测试程序,程序很简单,休眠1秒钟退出。
#include <unistd.h> int main() { return sleep (1); }
使用如下命令编译
gcc -o test test.c
$ 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 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
$ unset LD_PRELOAD