为什么在初始化之前通过 LD_PRELOAD 操作加载库?
why is library loaded via LD_PRELOAD operating before initialization?
在下面的最小示例中,通过 LD_PRELOAD
加载的库具有拦截 fopen
和 openat
的函数显然在其初始化之前运行。 (Linux 是 CentOS 7.3)。为什么??
库文件comm.c
:
#define _GNU_SOURCE
#include <dlfcn.h>
#include <stdarg.h>
#include <stdio.h>
#include <fcntl.h>
typedef FILE *(*fopen_type)(const char *, const char *);
// initialize to invalid value (non-NULL)
// init() should initialize this correctly
fopen_type g_orig_fopen = (fopen_type) 1;
typedef int (*openat_type)(int, const char *, int, ...);
openat_type g_orig_openat;
void init() {
g_orig_fopen = (fopen_type)dlsym(RTLD_NEXT,"fopen");
g_orig_openat = (openat_type)dlsym(RTLD_NEXT,"openat");
}
FILE *fopen(const char *filename, const char *mode) {
// have to do this here because init is not called yet???
FILE * const ret = ((fopen_type)dlsym(RTLD_NEXT,"fopen"))(filename, mode);
printf("g_orig_fopen %p fopen file %s\n", g_orig_fopen, filename);
return ret;
}
int openat(int dirfd, const char* pathname, int flags, ...) {
int fd;
va_list ap;
printf("g_orig_fopen %p openat file %s\n", g_orig_fopen, pathname);
if (flags & (O_CREAT)) {
va_start(ap, flags);
fd = g_orig_openat(dirfd, pathname, flags, va_arg(ap, mode_t));
}
else
fd = g_orig_openat(dirfd, pathname, flags);
return fd;
}
编译:
gcc -shared -fPIC -Wl,-init,init -ldl comm.c -o comm.so
我有一个空的子目录 subdir
。然后似乎在 init
:
之前调用了库函数 fopen
#LD_PRELOAD=./comm.so find subdir
g_orig_fopen 0x1 fopen file /proc/filesystems
g_orig_fopen 0x1 fopen file /proc/mounts
subdir
g_orig_fopen 0x7f7b2e574620 openat file subdir
很明显,fopen
是在comm.so
初始化之前调用的。有趣的是在 fopen()
中放置一个断点以便理解(检查 this link 以获得各种包的调试符号)。我得到这个回溯:
(gdb) bt
#0 fopen (filename=0x7ffff79cd2e7 "/proc/filesystems", mode=0x7ffff79cd159 "r") at comm.c:28
#1 0x00007ffff79bdb0e in selinuxfs_exists_internal () at init.c:64
#2 0x00007ffff79b5d98 in init_selinuxmnt () at init.c:99
#3 init_lib () at init.c:154
#4 0x00007ffff7de88aa in call_init (l=<optimized out>, argc=argc@entry=1, argv=argv@entry=0x7fffffffdf58, env=env@entry=0x7fffffffdf68) at dl-init.c:72
#5 0x00007ffff7de89bb in call_init (env=0x7fffffffdf68, argv=0x7fffffffdf58, argc=1, l=<optimized out>) at dl-init.c:30
#6 _dl_init (main_map=0x7ffff7ffe170, argc=1, argv=0x7fffffffdf58, env=0x7fffffffdf68) at dl-init.c:120
#7 0x00007ffff7dd9c5a in _dl_start_user () from /lib64/ld-linux-x86-64.so.2
#8 0x0000000000000001 in ?? ()
#9 0x00007fffffffe337 in ?? ()
#10 0x0000000000000000 in ?? ()
很明显,comm.so
依赖于其他库(libdl.so
需要 libselinux.so
)。 comm.so
并不是唯一声明 init 函数的库。 libdl.so
和 libselinux.so
也声明了一个。
因此,comm.so
是第一个要加载的库(因为它是用 LD_PRELOAD
声明的)但是,comm.so
依赖于 libdl.so
(因为 -ldl
在编译期间)和 libdl.so
取决于 libselinux.so
。因此,为了加载 comm.so
,之前调用了 libdl.so
和 libselinux.so
的初始化函数。最后,来自 libselinux.so
的初始化函数调用 fopen()
就个人而言,我通常在第一次调用符号时解析动态符号。像这样:
FILE *fopen(const char *filename, const char *mode) {
static FILE *(*real_fopen)(const char *filename, const char *mode) = NULL;
if (!real_fopen)
real_fopen = dlsym(RTLD_NEXT, "fopen");
return real_fopen(filename, mode);
}
在下面的最小示例中,通过 LD_PRELOAD
加载的库具有拦截 fopen
和 openat
的函数显然在其初始化之前运行。 (Linux 是 CentOS 7.3)。为什么??
库文件comm.c
:
#define _GNU_SOURCE
#include <dlfcn.h>
#include <stdarg.h>
#include <stdio.h>
#include <fcntl.h>
typedef FILE *(*fopen_type)(const char *, const char *);
// initialize to invalid value (non-NULL)
// init() should initialize this correctly
fopen_type g_orig_fopen = (fopen_type) 1;
typedef int (*openat_type)(int, const char *, int, ...);
openat_type g_orig_openat;
void init() {
g_orig_fopen = (fopen_type)dlsym(RTLD_NEXT,"fopen");
g_orig_openat = (openat_type)dlsym(RTLD_NEXT,"openat");
}
FILE *fopen(const char *filename, const char *mode) {
// have to do this here because init is not called yet???
FILE * const ret = ((fopen_type)dlsym(RTLD_NEXT,"fopen"))(filename, mode);
printf("g_orig_fopen %p fopen file %s\n", g_orig_fopen, filename);
return ret;
}
int openat(int dirfd, const char* pathname, int flags, ...) {
int fd;
va_list ap;
printf("g_orig_fopen %p openat file %s\n", g_orig_fopen, pathname);
if (flags & (O_CREAT)) {
va_start(ap, flags);
fd = g_orig_openat(dirfd, pathname, flags, va_arg(ap, mode_t));
}
else
fd = g_orig_openat(dirfd, pathname, flags);
return fd;
}
编译:
gcc -shared -fPIC -Wl,-init,init -ldl comm.c -o comm.so
我有一个空的子目录 subdir
。然后似乎在 init
:
fopen
#LD_PRELOAD=./comm.so find subdir
g_orig_fopen 0x1 fopen file /proc/filesystems
g_orig_fopen 0x1 fopen file /proc/mounts
subdir
g_orig_fopen 0x7f7b2e574620 openat file subdir
很明显,fopen
是在comm.so
初始化之前调用的。有趣的是在 fopen()
中放置一个断点以便理解(检查 this link 以获得各种包的调试符号)。我得到这个回溯:
(gdb) bt
#0 fopen (filename=0x7ffff79cd2e7 "/proc/filesystems", mode=0x7ffff79cd159 "r") at comm.c:28
#1 0x00007ffff79bdb0e in selinuxfs_exists_internal () at init.c:64
#2 0x00007ffff79b5d98 in init_selinuxmnt () at init.c:99
#3 init_lib () at init.c:154
#4 0x00007ffff7de88aa in call_init (l=<optimized out>, argc=argc@entry=1, argv=argv@entry=0x7fffffffdf58, env=env@entry=0x7fffffffdf68) at dl-init.c:72
#5 0x00007ffff7de89bb in call_init (env=0x7fffffffdf68, argv=0x7fffffffdf58, argc=1, l=<optimized out>) at dl-init.c:30
#6 _dl_init (main_map=0x7ffff7ffe170, argc=1, argv=0x7fffffffdf58, env=0x7fffffffdf68) at dl-init.c:120
#7 0x00007ffff7dd9c5a in _dl_start_user () from /lib64/ld-linux-x86-64.so.2
#8 0x0000000000000001 in ?? ()
#9 0x00007fffffffe337 in ?? ()
#10 0x0000000000000000 in ?? ()
很明显,comm.so
依赖于其他库(libdl.so
需要 libselinux.so
)。 comm.so
并不是唯一声明 init 函数的库。 libdl.so
和 libselinux.so
也声明了一个。
因此,comm.so
是第一个要加载的库(因为它是用 LD_PRELOAD
声明的)但是,comm.so
依赖于 libdl.so
(因为 -ldl
在编译期间)和 libdl.so
取决于 libselinux.so
。因此,为了加载 comm.so
,之前调用了 libdl.so
和 libselinux.so
的初始化函数。最后,来自 libselinux.so
的初始化函数调用 fopen()
就个人而言,我通常在第一次调用符号时解析动态符号。像这样:
FILE *fopen(const char *filename, const char *mode) {
static FILE *(*real_fopen)(const char *filename, const char *mode) = NULL;
if (!real_fopen)
real_fopen = dlsym(RTLD_NEXT, "fopen");
return real_fopen(filename, mode);
}