LD_PRELOAD和链接

我有这个小测试atfork_demo.c

 #include  #include  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  #include  #include  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  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_atforkLD_PRELOAD -override工作,而在第二种情况下,它没有。 我得到输出:

 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); } 

在回答这个问题之前,我要强调的是,对于任何生产应用程序来说这都是一个非常糟糕的主意

如果你正在使用第三方库来实现这些约束,那么考虑一个替代解决方案,例如早期分支维护一个“ 帮助 ”过程,在你和它之间有一个管道…然后,当你需要时调用exec() ,你可以代表你请求它完成工作( fork()exec() )。

修补或以其他方式支持系统调用(例如pthread_atfork()的服务只是要求麻烦(错过事件,内存泄漏,崩溃等等)。


正如@Sergio指出的那样, pthread_atfork()实际上内置于atfork_demo2.so ,所以你无法做任何事情来覆盖它…但是检查pthread_atfork()的反汇编/来源会给你一个关于如何实现你的体面提示问:

 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> 

或来源(从这里 ):

 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()的原型,从这里 / 这里 )

 #include  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! 

它不适用于第二种情况,因为没有什么可以覆盖。 您的第二个库与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 

因此,无论LD_PRELOAD还是任何其他加载的库,它每次都将使用本地pthread_atfork

如何克服这个? 看起来像所描述的配置是不可能的,因为您无论如何都需要修改atfork_demo库或主可执行文件。