1

I have this small testcode 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);
}

Now, I compile it in two different ways:

gcc -shared -fPIC atfork_demo.c -o atfork_demo1.so
gcc -shared -fPIC atfork_demo.c -o atfork_demo2.so -lpthread

My demo main atfork_demo_main.c is this:

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

Which I compile like this:

gcc atfork_demo_main.c -o atfork_demo_main.exec -ldl

Now, I have another small demo atfork_patch.c where I want to override 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;
}

Which I compile like this:

gcc -shared -O2 -fPIC patch_atfork.c -o patch_atfork.so

And then I set LD_PRELOAD=./atfork_patch.so, and do these two calls:

./atfork_demo_main.exec ./atfork_demo1.so
./atfork_demo_main.exec ./atfork_demo2.so

In the first case, the LD_PRELOAD-override of pthread_atfork worked and in the second, it did not. I get the output:

Ignoring pthread_atfork call!
Hello from atfork prepare.

So, now to the question(s):

  • Why did it not work in the second case?
  • How can I make it work also in the second case, i.e. also override it? In my real use case, atfork_demo is some library which I cannot change. I also cannot change atfork_demo_main but I can make it load any other code. I would prefer if I can just do it with some change in atfork_patch.

You get some more debug output if you also use LD_DEBUG=all. Maybe interesting is this bit, for the second case:

   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]

So, it searches for the symbol __register_atfork. I added that to atfork_patch_extended.so but it doesn't find it and uses it from libc instead. How can I make it find and use my __register_atfork?


As a side note, my main goal is to ignore the atfork handlers when fork() is called, but this is not the question here, but actually here. One solution to that, which seems to work, is to override fork() itself by this:

pid_t fork(void) {
  return syscall(SYS_clone, SIGCHLD, 0);
}
Albert
  • 65,406
  • 61
  • 242
  • 386
  • Does last output snippet relate to the second run only? Or first line is for the first run? – Sergio Oct 20 '17 at 09:03
  • @Sergio: Both calls. – Albert Oct 20 '17 at 09:08
  • The answer depends on what you are able and willing to change... You could for example resolve the symbol `pthread_atfork` dynamically with `dlsym()` in atfork_demo.c, then it should do what you expect... – Ctx Oct 20 '17 at 09:34
  • @Ctx: I extended it. Basically I would prefer to just do it via atfork_patch.c. I cannot change atfork_demo.c. – Albert Oct 20 '17 at 09:42

2 Answers2

2

Before answering this question, I would stress that this is a really bad idea for any production application.

If you are using a third party library that puts such constraints in place, then think about an alternative solution, such as forking early to maintain a "helper" process, with a pipe between you and it... then, when you need to call exec(), you can request that it does the work (fork(), exec()) on your behalf.

Patching or otherwise side-stepping the services of a system call such as pthread_atfork() is just asking for trouble (missed events, memory leaks, crashes, etc...).


As @Sergio pointed out, pthread_atfork() is actually built into atfork_demo2.so, so you can't do anything to override it... However examining the disassembly / source of pthread_atfork() gives you a decent hint about how achieve what you're asking:

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>

or the source (from 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);
}

As you can see, pthread_atfork() does nothing aside from calling __register_atfork()... so patch that instead!


The content of atfork_patch.c now becomes: (using __register_atfork()'s prototype, from 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;
}

This works for both demos:

$ 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!

Attie
  • 6,690
  • 2
  • 24
  • 34
  • Well, the problem is, there is one external library which installs such a broken atfork handler (OpenBlas), and then there are other external libs which might create subprocesses. Of course, in my code, I can do such a workaround with such a helper, but it's bad to hope that no other code will call `fork` at some point, and I have that case. Also, why do you think this is such a bad idea, if there will never be any fork without following exec by my code and also any code from other external libraries? – Albert Oct 20 '17 at 11:58
  • It's a bad idea because libraries install these handlers to deal with `fork()`s. One good example that I've personally encountered would be libusb, where closing a netlink socket in a child destroys any hope of the parent's functionality continuing. https://stackoverflow.com/q/42145020/1347519 – Attie Oct 20 '17 at 12:10
  • As you have already commented "_bad to hope that no other code will call fork at some point_", but you then go on to **_hope_** that "_there will never be any fork without following exec by my code and also any code from other external libraries_". Even if this is true, things can break. – Attie Oct 20 '17 at 12:11
  • Well, so you have the choice: Either fork without exec might crash/deadlock your program, or just fork (no matter with or without exec) will definitely crash/deadlock your program. Isn't first option much better in all cases? – Albert Oct 20 '17 at 12:17
  • If those are your options, I would argue that you've not found the solution yet. – Attie Oct 20 '17 at 12:19
  • Well, I have no choice. OpenBlas installs the broken atfork handler, and currently I need to use OpenBlas. And even if not in my code, there will definitely be other code which might want to spawn subprocesses. This also cannot be avoided. Under this circumstances, I don't see any other solution. But please correct me if there is a better solution. – Albert Oct 20 '17 at 12:22
  • I don't know about OpenBlas, but a "_broken atfork handler_" sounds like a bug... have you contacted the developers with a bug report, in hope of a fix? Or perhaps even contacted them with a patch? =) – Attie Oct 20 '17 at 12:25
  • The OpenBlas bug is reported [here](https://github.com/xianyi/OpenBLAS/issues/240). Also, it might actually be an issue with OpenMP in general, see [here](http://bisqwit.iki.fi/story/howto/openmp/#OpenmpAndFork). It sounds a bit like a wontfix to me. – Albert Oct 20 '17 at 12:29
  • Hmm. I would be tempted to open a new SO question outlining what you're trying to do, and what isn't working properly... Talk upfront about OpenBlas, mention the reports. I'm afraid I don't feel qualified to help further. – Attie Oct 20 '17 at 12:37
  • I've just seen your [issue](https://github.com/tensorflow/tensorflow/issues/13802) - best of luck!... – Attie Oct 20 '17 at 12:40
  • 1
    Ok, yes, maybe that's a good suggestion. Thanks anyway for the help! – Albert Oct 20 '17 at 12:40
0

It doesn't work for the second case because there is nothing to override. Your second library is linked statically with pthread library:

$ 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

So it will use local pthread_atfork each time, regardless of LD_PRELOAD or any other loaded libraries.

How to overcome that? Looks like for described configuration it is not possible since you need to modify atfork_demo library or main executable anyway.

Sergio
  • 8,099
  • 2
  • 26
  • 52
  • @Albert just link against pthreads dynamically, i.e. without `-lpthread` option. – Sergio Oct 20 '17 at 09:34
  • Well, this is my demo. In practice, I cannot control that. It's a library installed on my system and I cannot replace it. Btw., I extended my question a bit. It does some symbol lookup for `__pthread_atfork`. How can I make it such that it finds my symbol? – Albert Oct 20 '17 at 09:36
  • @Albert you could try to define your `pthread_atfork` right in executable, i.e. in `atfork_demo_main.c`. System loader will always start search from main executable for each dependency library (except if library has `DT_SYMBOLIC` flag, but looks like it is not your case). – Sergio Oct 20 '17 at 09:49
  • @Albert I see your updated question. Looks like there is no way to override `pthread_atfork` for described setup. You have to modify `atfork_demo` or `atfork_demo_main` anyway. – Sergio Oct 20 '17 at 09:55
  • Why do you think so? Or maybe put different: As you see in the ld debug output, it actually does a dynamic search for the `__register_atfork` symbol, and it checks `atfork_patch_extended.so` first. Why does it not find it there? What can I make that it finds it there? – Albert Oct 20 '17 at 09:57
  • Actually, it works to override `__register_atfork`. I just had a typo. Also see the answer by Attie. – Albert Oct 20 '17 at 11:59