#!/usr/bin/env cram

Dependency relations:

- A is needed by B is needed by C
- Y is dlopened by B's constructor and dlclosed by B's destructor
- X is needed by Y

In what order do constructors get called by a program that links
against C?

Surely, A should run first, because it is needed by B which is needed
by C, and no requirement for X or Y is known yet.

Then, B should _begin_ to run first, at which point X should run and Y
should run, before B finishes.  Then, B having finished, C should run
last.

(Destructor order during finalization should be the reverse of the
above, of course.)

  $ cat >A.c <<EOF
  > #include <stdio.h>
  > 
  > __attribute__((constructor))
  > static void init_A(void) { fprintf(stderr, "%s\n", __func__); }
  > 
  > __attribute__((destructor))
  > static void fini_A(void) { fprintf(stderr, "%s\n", __func__); }
  > EOF

  $ cat >B.c <<EOF
  > #include <dlfcn.h>
  > #include <stdio.h>
  > 
  > static void *Y_handle;
  > __attribute__((constructor))
  > static void init_B(void)
  > {
  > 	fprintf(stderr, "%s enter\n", __func__);
  > 	Y_handle = dlopen("libY.so", RTLD_LAZY|RTLD_LOCAL);
  > 	fprintf(stderr, "%s exit\n", __func__);
  > }
  > 
  > __attribute__((destructor))
  > static void fini_B(void)
  > {
  > 	fprintf(stderr, "%s enter\n", __func__);
  > 	if (Y_handle)
  > 		dlclose(Y_handle);
  > 	fprintf(stderr, "%s exit\n", __func__);
  > }
  > EOF

  $ cat >C.c <<EOF
  > #include <stdio.h>
  > 
  > __attribute__((constructor))
  > static void init_C(void) { fprintf(stderr, "%s\n", __func__); }
  > 
  > __attribute__((destructor))
  > static void fini_C(void) { fprintf(stderr, "%s\n", __func__); }
  > EOF

  $ cat >X.c <<EOF
  > #include <stdio.h>
  > 
  > __attribute__((constructor))
  > static void init_X(void) { fprintf(stderr, "%s\n", __func__); }
  > 
  > __attribute__((destructor))
  > static void fini_X(void) { fprintf(stderr, "%s\n", __func__); }
  > EOF

  $ cat >Y.c <<EOF
  > #include <stdio.h>
  > 
  > __attribute__((constructor))
  > static void init_Y(void) { fprintf(stderr, "%s\n", __func__); }
  > 
  > __attribute__((destructor))
  > static void fini_Y(void) { fprintf(stderr, "%s\n", __func__); }
  > EOF

  $ cat >main.c <<EOF
  > #include <stdio.h>
  > 
  > int main(void) { fprintf(stderr, "hello world\n"); return 0; }
  > EOF

  $ cc -shared -fPIC -o libA.so A.c
  $ cc -shared -fPIC -o libB.so B.c -L. -Wl,-R. -lA
  $ cc -shared -fPIC -o libC.so C.c -L. -Wl,-R. -lB
  $ cc -shared -fPIC -o libX.so X.c
  $ cc -shared -fPIC -o libY.so Y.c -L. -Wl,-R. -lX
  $ cc -o test main.c -L. -Wl,-R. -lC

So we _should_ get:

$ ./test
init_A
init_B enter
init_X
init_Y
init_B exit
init_C
hello world
fini_C
fini_B enter
fini_Y
fini_X
fini_B exit
fini_A

Except, that's not how it plays out!  Instead, we get:

  $ ./test
  init_A
  init_B enter
  init_C
  init_X
  init_Y
  init_B exit
  hello world
  fini_Y
  fini_X
  fini_C
  fini_B enter
  fini_B exit
  fini_A

It's wrong in both directions!

1. init_C runs before init_B has returned.
2. fini_Y and fini_X run before fini_B has dlclosed fini_Y.
