LD_PRELOAD 和联动
LD_PRELOAD and linkage
我有这个小测试码atfork_demo.c
:
#include <stdio.h>
#include <pthread.h>
void hello_from_fork_prepare() {
printf("Hello from atfork prepare.\n");
fflush(stdout);
}
void register_hello_from_fork_prepare() {
pthread_atfork(&hello_from_fork_prepare, 0, 0);
}
现在,我用两种不同的方式编译它:
gcc -shared -fPIC atfork_demo.c -o atfork_demo1.so
gcc -shared -fPIC atfork_demo.c -o atfork_demo2.so -lpthread
我的演示主要atfork_demo_main.c
是这样的:
#include <dlfcn.h>
#include <stdio.h>
#include <unistd.h>
int main(int argc, const char** argv) {
if(argc <= 1) {
printf("usage: ... lib.so\n");
return 1;
}
void* plib = dlopen("libpthread.so.0", RTLD_NOW|RTLD_GLOBAL);
if(!plib) {
printf("cannot load pthread, error %s\n", dlerror());
return 1;
}
void* lib = dlopen(argv[1], RTLD_LAZY);
if(!lib) {
printf("cannot load %s, error %s\n", argv[1], dlerror());
return 1;
}
void (*reg)();
reg = dlsym(lib, "register_hello_from_fork_prepare");
if(!reg) {
printf("did not found func, error %s\n", dlerror());
return 1;
}
reg();
fork();
}
我是这样编译的:
gcc atfork_demo_main.c -o atfork_demo_main.exec -ldl
现在,我有另一个小演示 atfork_patch.c
,我想在其中覆盖 pthread_atfork
:
#include <stdio.h>
int pthread_atfork(void (*prepare)(void), void (*parent)(void), void (*child)(void)) {
printf("Ignoring pthread_atfork call!\n");
fflush(stdout);
return 0;
}
我是这样编译的:
gcc -shared -O2 -fPIC patch_atfork.c -o patch_atfork.so
然后我设置 LD_PRELOAD=./atfork_patch.so
,并执行这两个调用:
./atfork_demo_main.exec ./atfork_demo1.so
./atfork_demo_main.exec ./atfork_demo2.so
在第一种情况下,pthread_atfork
的 LD_PRELOAD
-覆盖起作用,而在第二种情况下,它没有。我得到输出:
Ignoring pthread_atfork call!
Hello from atfork prepare.
所以,现在回答问题:
- 为什么第二种情况不行?
- 我怎样才能使它在第二种情况下也能工作,即也覆盖它?
在我的实际用例中,
atfork_demo
是一些我无法更改的库。我也无法更改 atfork_demo_main
但我可以让它加载任何其他代码。如果我可以通过 atfork_patch
. 中的一些更改来完成,我会更愿意
如果您还使用 LD_DEBUG=all
,您将获得更多调试输出。对于第二种情况,这一点可能很有趣:
841: symbol=__register_atfork; lookup in file=./atfork_demo_main.exec [0]
841: symbol=__register_atfork; lookup in file=./atfork_patch_extended.so [0]
841: symbol=__register_atfork; lookup in file=/lib/x86_64-linux-gnu/libdl.so.2 [0]
841: symbol=__register_atfork; lookup in file=/lib/x86_64-linux-gnu/libc.so.6 [0]
841: binding file ./atfork_demo2.so [0] to /lib/x86_64-linux-gnu/libc.so.6 [0]: normal symbol `__register_atfork' [GLIBC_2.3.2]
因此,它搜索符号 __register_atfork
。我将它添加到 atfork_patch_extended.so
但它没有找到它,而是从 libc
中使用它。我怎样才能让它找到并使用我的 __register_atfork
?
附带说明一下,我的主要目标是在调用 fork()
时忽略 atfork 处理程序,但这不是这里的问题,而是 。一个似乎可行的解决方案是通过以下方式覆盖 fork()
本身:
pid_t fork(void) {
return syscall(SYS_clone, SIGCHLD, 0);
}
它不适用于第二种情况,因为没有什么可以覆盖。您的第二个库与 pthread 库静态链接:
$ readelf --symbols atfork_demo1.so | grep pthread_atfork
7: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND pthread_atfork
54: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND pthread_atfork
$ readelf --symbols atfork_demo2.so | grep pthread_atfork
41: 0000000000000000 0 FILE LOCAL DEFAULT ABS pthread_atfork.c
47: 0000000000000830 31 FUNC LOCAL DEFAULT 12 __pthread_atfork
49: 0000000000000830 31 FUNC LOCAL DEFAULT 12 pthread_atfork
因此它每次都会使用本地 pthread_atfork
,而不管 LD_PRELOAD
或任何其他加载的库。
如何克服呢?看起来对于所描述的配置是不可能的,因为无论如何您都需要修改 atfork_demo
库或主要可执行文件。
在回答这个问题之前,我要强调 这对于任何生产应用程序来说都是一个非常糟糕的主意。
如果您使用的第三方库设置了此类约束,请考虑替代解决方案,例如尽早分叉以维护“helper”进程,使用你和它之间的管道...然后,当你需要调用 exec()
时,你可以请求它代表你完成工作(fork()
、exec()
)。
修补或以其他方式回避系统调用的服务,例如 pthread_atfork()
只是自找麻烦(错过事件、内存泄漏、崩溃等)。
正如@Sergio 指出的那样,pthread_atfork()
实际上内置于 atfork_demo2.so
中,因此您不能做任何事情来覆盖它...但是检查 [=17= 的反汇编/源代码] 给你一个体面的提示,告诉你如何实现你的要求:
0000000000000830 <__pthread_atfork>:
830: 48 8d 05 f9 07 20 00 lea 0x2007f9(%rip),%rax # 201030 <__dso_handle>
837: 48 85 c0 test %rax,%rax
83a: 74 0c je 848 <__pthread_atfork+0x18>
83c: 48 8b 08 mov (%rax),%rcx
83f: e9 6c fe ff ff jmpq 6b0 <__register_atfork@plt>
844: 0f 1f 40 00 nopl 0x0(%rax)
848: 31 c9 xor %ecx,%ecx
84a: e9 61 fe ff ff jmpq 6b0 <__register_atfork@plt>
或来源(来自here):
int
pthread_atfork (void (*prepare) (void),
void (*parent) (void),
void (*child) (void))
{
return __register_atfork (prepare, parent, child, &__dso_handle == NULL ? NULL : __dso_handle);
}
如您所见,pthread_atfork()
除了调用 __register_atfork()
之外什么都不做...所以改为修补它!
atfork_patch.c
的内容现在变成:(使用__register_atfork()
的原型,来自here / here)
#include <stdio.h>
int __register_atfork (void (*prepare) (void), void (*parent) (void),
void (*child) (void), void *dso_handle) {
printf("Ignoring pthread_atfork call!\n");
fflush(stdout);
return 0;
}
这对两个演示都有效:
$ LD_PRELOAD=./atfork_patch.so ./atfork_demo_main.exec ./atfork_demo1.so
Ignoring pthread_atfork call!
$ LD_PRELOAD=./atfork_patch.so ./atfork_demo_main.exec ./atfork_demo2.so
Ignoring pthread_atfork call!
我有这个小测试码atfork_demo.c
:
#include <stdio.h>
#include <pthread.h>
void hello_from_fork_prepare() {
printf("Hello from atfork prepare.\n");
fflush(stdout);
}
void register_hello_from_fork_prepare() {
pthread_atfork(&hello_from_fork_prepare, 0, 0);
}
现在,我用两种不同的方式编译它:
gcc -shared -fPIC atfork_demo.c -o atfork_demo1.so
gcc -shared -fPIC atfork_demo.c -o atfork_demo2.so -lpthread
我的演示主要atfork_demo_main.c
是这样的:
#include <dlfcn.h>
#include <stdio.h>
#include <unistd.h>
int main(int argc, const char** argv) {
if(argc <= 1) {
printf("usage: ... lib.so\n");
return 1;
}
void* plib = dlopen("libpthread.so.0", RTLD_NOW|RTLD_GLOBAL);
if(!plib) {
printf("cannot load pthread, error %s\n", dlerror());
return 1;
}
void* lib = dlopen(argv[1], RTLD_LAZY);
if(!lib) {
printf("cannot load %s, error %s\n", argv[1], dlerror());
return 1;
}
void (*reg)();
reg = dlsym(lib, "register_hello_from_fork_prepare");
if(!reg) {
printf("did not found func, error %s\n", dlerror());
return 1;
}
reg();
fork();
}
我是这样编译的:
gcc atfork_demo_main.c -o atfork_demo_main.exec -ldl
现在,我有另一个小演示 atfork_patch.c
,我想在其中覆盖 pthread_atfork
:
#include <stdio.h>
int pthread_atfork(void (*prepare)(void), void (*parent)(void), void (*child)(void)) {
printf("Ignoring pthread_atfork call!\n");
fflush(stdout);
return 0;
}
我是这样编译的:
gcc -shared -O2 -fPIC patch_atfork.c -o patch_atfork.so
然后我设置 LD_PRELOAD=./atfork_patch.so
,并执行这两个调用:
./atfork_demo_main.exec ./atfork_demo1.so
./atfork_demo_main.exec ./atfork_demo2.so
在第一种情况下,pthread_atfork
的 LD_PRELOAD
-覆盖起作用,而在第二种情况下,它没有。我得到输出:
Ignoring pthread_atfork call!
Hello from atfork prepare.
所以,现在回答问题:
- 为什么第二种情况不行?
- 我怎样才能使它在第二种情况下也能工作,即也覆盖它?
在我的实际用例中,
atfork_demo
是一些我无法更改的库。我也无法更改atfork_demo_main
但我可以让它加载任何其他代码。如果我可以通过atfork_patch
. 中的一些更改来完成,我会更愿意
如果您还使用 LD_DEBUG=all
,您将获得更多调试输出。对于第二种情况,这一点可能很有趣:
841: symbol=__register_atfork; lookup in file=./atfork_demo_main.exec [0]
841: symbol=__register_atfork; lookup in file=./atfork_patch_extended.so [0]
841: symbol=__register_atfork; lookup in file=/lib/x86_64-linux-gnu/libdl.so.2 [0]
841: symbol=__register_atfork; lookup in file=/lib/x86_64-linux-gnu/libc.so.6 [0]
841: binding file ./atfork_demo2.so [0] to /lib/x86_64-linux-gnu/libc.so.6 [0]: normal symbol `__register_atfork' [GLIBC_2.3.2]
因此,它搜索符号 __register_atfork
。我将它添加到 atfork_patch_extended.so
但它没有找到它,而是从 libc
中使用它。我怎样才能让它找到并使用我的 __register_atfork
?
附带说明一下,我的主要目标是在调用 fork()
时忽略 atfork 处理程序,但这不是这里的问题,而是 fork()
本身:
pid_t fork(void) {
return syscall(SYS_clone, SIGCHLD, 0);
}
它不适用于第二种情况,因为没有什么可以覆盖。您的第二个库与 pthread 库静态链接:
$ readelf --symbols atfork_demo1.so | grep pthread_atfork
7: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND pthread_atfork
54: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND pthread_atfork
$ readelf --symbols atfork_demo2.so | grep pthread_atfork
41: 0000000000000000 0 FILE LOCAL DEFAULT ABS pthread_atfork.c
47: 0000000000000830 31 FUNC LOCAL DEFAULT 12 __pthread_atfork
49: 0000000000000830 31 FUNC LOCAL DEFAULT 12 pthread_atfork
因此它每次都会使用本地 pthread_atfork
,而不管 LD_PRELOAD
或任何其他加载的库。
如何克服呢?看起来对于所描述的配置是不可能的,因为无论如何您都需要修改 atfork_demo
库或主要可执行文件。
在回答这个问题之前,我要强调 这对于任何生产应用程序来说都是一个非常糟糕的主意。
如果您使用的第三方库设置了此类约束,请考虑替代解决方案,例如尽早分叉以维护“helper”进程,使用你和它之间的管道...然后,当你需要调用 exec()
时,你可以请求它代表你完成工作(fork()
、exec()
)。
修补或以其他方式回避系统调用的服务,例如 pthread_atfork()
只是自找麻烦(错过事件、内存泄漏、崩溃等)。
正如@Sergio 指出的那样,pthread_atfork()
实际上内置于 atfork_demo2.so
中,因此您不能做任何事情来覆盖它...但是检查 [=17= 的反汇编/源代码] 给你一个体面的提示,告诉你如何实现你的要求:
0000000000000830 <__pthread_atfork>:
830: 48 8d 05 f9 07 20 00 lea 0x2007f9(%rip),%rax # 201030 <__dso_handle>
837: 48 85 c0 test %rax,%rax
83a: 74 0c je 848 <__pthread_atfork+0x18>
83c: 48 8b 08 mov (%rax),%rcx
83f: e9 6c fe ff ff jmpq 6b0 <__register_atfork@plt>
844: 0f 1f 40 00 nopl 0x0(%rax)
848: 31 c9 xor %ecx,%ecx
84a: e9 61 fe ff ff jmpq 6b0 <__register_atfork@plt>
或来源(来自here):
int
pthread_atfork (void (*prepare) (void),
void (*parent) (void),
void (*child) (void))
{
return __register_atfork (prepare, parent, child, &__dso_handle == NULL ? NULL : __dso_handle);
}
如您所见,pthread_atfork()
除了调用 __register_atfork()
之外什么都不做...所以改为修补它!
atfork_patch.c
的内容现在变成:(使用__register_atfork()
的原型,来自here / here)
#include <stdio.h>
int __register_atfork (void (*prepare) (void), void (*parent) (void),
void (*child) (void), void *dso_handle) {
printf("Ignoring pthread_atfork call!\n");
fflush(stdout);
return 0;
}
这对两个演示都有效:
$ LD_PRELOAD=./atfork_patch.so ./atfork_demo_main.exec ./atfork_demo1.so
Ignoring pthread_atfork call!
$ LD_PRELOAD=./atfork_patch.so ./atfork_demo_main.exec ./atfork_demo2.so
Ignoring pthread_atfork call!