Diverting trains of thought, wasting precious time
Initialization order of static state is a thorny problem. It's particularly tricky to get right portably. But until recently I didn't realise how tricky it could be even when restricting oneself to GNU tools on Unix platforms. Consider the following three-part program, consisting of an executable prog and two shared libraries lib1 and lib2. The dependency order is left-to-right in that list: prog depends on lib1 which depends on lib2.
/* prog.c */ #include <stdio.h> /* from lib1 */ void greeting(void); /* constructor */ static void init(void) __attribute__((constructor)); static void init(void) { fprintf(stderr, "Initializing prog\n"); } int main(void) { greeting(); return 0; } /* end prog.c */ /* lib1.c */ #include <stdio.h> /* from lib2 */ void hello(void); /* constructor */ static void init(void) __attribute__((constructor)); static void init(void) { fprintf(stderr, "Initializing lib1\n"); } void greeting(void) { hello(); } /* end lib1.c */ /* lib2.c */ #include <stdio.h> /* constructor */ static void init(void) __attribute__((constructor)); static void init(void) { fprintf(stderr, "Initializing lib2\n"); } void hello(void) { printf("Hello, world!\n"); } /* end lib2.c */
Here's a GNU Makefile to tie it all together.
CFLAGS := -g -fPIC LDFLAGS := -L$$(pwd) -Wl,-R$$(pwd) LDLIBS := -l1 -l2 default: lib1.so lib2.so prog %.so: %.c $(CC) $(CFLAGS) -shared -o "$@" "$<" clean: rm -f lib1.so lib2.so prog
Now when you do make (or gmake) it will build a program that initializes its libraries in right-to-left order: from the “most depended on” to the “least depended on”. We can verify this by running the program.
$ ./prog Initializing lib2 Initializing lib1 Initializing prog Hello, world!
Moreover, if you try flipping around the link order in the LDLIBS line, the link will fail with undefined reference to `hello', because the reference to hello (in lib1) is introduced after the reference to lib2, and the linker's defined behaviour is to avoid re-scanning for new undefined references---it's up to the invoker to order the libraries so that this works.
Let's try this on a BSD system. I have a NetBSD 5.0 VM hanging around, so I'll try that. It has recent GNU make, GCC and GNU binutils installed.
$ gmake cc -g -fPIC -shared -o "lib1.so" "lib1.c" cc -g -fPIC -shared -o "lib2.so" "lib2.c" cc -g -fPIC -L$(pwd) -Wl,-R$(pwd) prog.c -l1 -l2 -o prog $ ./prog Initializing lib1 Initializing lib2 Initializing prog Hello, world!
Strangely, our initialization order is flipped. This doesn't matter for our program, but if lib1 consumed some static state in lib2, it would matter quite a bit. What happens if we flip the link order around to compensate? We edit the LDLIBS line and re-make.
$ nano Makefile $ gmake clean && gmake rm -f lib1.so lib2.so prog cc -g -fPIC -shared -o "lib1.so" "lib1.c" cc -g -fPIC -shared -o "lib2.so" "lib2.c" cc -g -fPIC -L$(pwd) -Wl,-R$(pwd) prog.c -l2 -l1 -o prog $ ./prog Initializing lib2 Initializing lib1 Initializing prog Hello, world!
This has done what we want. But what's going on? This link order didn't even work on GNU/Linux. Not only does it work on BSD, but it's required if we want a sensible initialization order. Our initializers run in left-to-right order, so we need to put the “most depended on” libraries first, not last. This isn't a BSD quirk per se, because we're using the GNU linker in both cases. I suspect the linker scripts are nevertheless different in the two cases. However, I haven't had time to look into the details of why. I'd be interested to hear, if anyone knows. I guess this is the sort of pecularity that gives libtool a reason to exist.
[/devel] permanent link contact