Subject: Re: RFC: migration to a fully dynamically linked system
To: Wolfgang Solfrank <ws@tools.de>
From: Todd Vierling <tv@wasabisystems.com>
List: tech-userlevel
Date: 01/05/2002 12:16:11
On Sat, 5 Jan 2002, Wolfgang Solfrank wrote:

: > : Hmm, did you actually try this?  I just did, and it worked like a charm.
: > : I.e. both the main program and the dlopen()ed library function used the
: > : same __sF[].
: >
: > Then it's porbably likely that ld(1) picked the dynamic libc over the static
: > one.
:
: No, that's NOT likely.  I'd prefer if people would at least check what the
: system does (let alone imagine what could be changed to make it do what we
: want) before making blanket statements like this.

Actually, it did pick a shared __sF symbol as a result of the dynamic libc
in your example, even though the symbol was physically located in the
binary.  I'm probably not being clear enough in my pseudocode examples, so
I've attached one with explanation below to illustrate the problem.

When ld(1) is binding symbols in a binary, it will put symbols needed by
libraries (in the case of your program, -lc, implicitly added by gcc) in the
dynamic symbol section of the binary.  These can then participate in the
ld.elf_so linking process.  (In the case of __sF, BSS space got allocated
too, so the symbol actually ended up in the binary rather than libc.so, both
with and without libc.a.)

Since you didn't do what I mentioned here:

: > Linking the library against -lc as a dependency, and then linking the
: > program -nostdlib with crt0, libc.a, and libgcc.a (simulating static linking
: > on a dynamic program), should do what I'm talking about.

you ended up with a program containing code from libc.a *and* a dynamic
reference to libc.so (do a "ldd" on the binary to see this) -- so __sF got
an entry in the binary's dynamic section.  Thus, both the binary and the
library ended up binding to the same symbol.

However, what we're dealing with in this thread are "static" binaries --
that is, binaries that have the ability to do dynamic loading, but which
contain no dynamic references to anything but the dynamic loader itself.
So, the resultant program *must not* have a reference to libc.so (since this
is exactly what the proponents of "static dlopen()" propose).

Attached is a shar which demonstrates this.  It prints, with %p, the value
of "stdin" (as a pointer) from three places: the main program and a global
data symbol in two libraries.

In order to make a program dynamic, it must be linked against at least one
shared object.  In Solaris, this is achieved by linking against
/etc/lib/libdl.so.1.  Since our dl*() functions are in ld.elf_so, I've
simply created a placebo library for this purpose ("libplacebo.so"), which
has no symbols at all -- and more importantly, no dependency on the shared
libc.

This Makefile can be run three ways to demonstrate the differences:

1. With no options, "prog" will be linked against libc.a but not libc.so.

2. With NORMAL_LINK=1, "prog" will be linked the same way your example was
   (with libc.a and libc.so).

3. With LIBC=-lc_pic, "libdyn.so" and "libdyn2.so" will be linked with
   libc_pic.a instead of libc.so (making the modules self-contained without
   a dependence on libc.so, which has been proposed as well).

In (1), you'll see that the main program's pointer to "stdin" is different
from that of the two libraries, but the two libraries (both dependent on
libc.so) share the symbol.

In (2), all three pointers will be the same, but we have the problem from
your example of linking against both libc.a and libc.so.

In (3), all three pointers will be different:  the program and both
libraries all have their own versions of "stdin".

The conclusion I can draw from this is that, in order to have reliable
dynamic loading support in /bin and /sbin, the binaries must both be
(a) dynamic, and (b) linked against the shared libc.

==========
# This is a shell archive.  Save it in a file, remove anything before
# this line, and then unpack it by entering "sh file".  Note, it may
# create directories; files and directories will be owned by you and
# have default permissions.
#
# This archive contains:
#
#	Makefile
#	dyn.c
#	placebo.c
#	prog.c
#
echo x - Makefile
sed 's/^X//' >Makefile << 'END-of-Makefile'
XCFLAGS=	-g
XLIBC?=	-lc
X
Xprog: prog.o libdyn.so libdyn2.so libplacebo.so
X.ifdef NORMAL_LINK
X	${LINK.c} -o $@ prog.o /usr/lib/libc.a
X.else
X	${LINK.c} -o $@ -nostdlib /usr/lib/crt0.o /usr/lib/crtbegin.o \
X		prog.o /usr/lib/crtend.o /usr/lib/libc.a /usr/lib/libgcc.a \
X		-L. -R. -lplacebo
X.endif
X
X.SUFFIXES: .so
X.c.so:
X	${COMPILE.c} -fPIC $< -o $@
X
Xlibdyn.so: dyn.so
X	${LINK.c} -shared -o $@ $> ${LIBC}
X
Xlibdyn2.so: dyn.so
X	${LINK.c} -shared -o $@ $> ${LIBC}
X
Xlibplacebo.so: placebo.so
X	${LINK.c} -shared -o $@ $>
X
Xclean:
X	-rm -f *.so *.o prog
END-of-Makefile
echo x - dyn.c
sed 's/^X//' >dyn.c << 'END-of-dyn.c'
X#include <stdio.h>
X
XFILE *fp = stdin;
END-of-dyn.c
echo x - placebo.c
sed 's/^X//' >placebo.c << 'END-of-placebo.c'
X/* Nothing here! */
END-of-placebo.c
echo x - prog.c
sed 's/^X//' >prog.c << 'END-of-prog.c'
X#include <stdio.h>
X#include <dlfcn.h>
X
Xint main(void) {
X	void *lib = dlopen("./libdyn.so", RTLD_LAZY);
X	void *lib2 = dlopen("./libdyn2.so", RTLD_LAZY);
X	FILE **fp_p, **fp2_p;
X
X	if (!lib)
X		errx(1, "can't open libdyn.so");
X
X	if (!lib2)
X		errx(1, "can't open libdyn2.so");
X
X	if (!(fp_p = dlsym(lib, "fp")))
X		errx(1, "can't get symbols in libdyn.so");
X
X	if (!(fp2_p = dlsym(lib2, "fp")))
X		errx(1, "can't get symbols in libdyn2.so");
X
X	printf("%p\n%p\n%p\n", stdin, *fp_p, *fp2_p);
X
X	return 0;
X}
END-of-prog.c
exit

==========

-- 
-- Todd Vierling <tv@wasabisystems.com>  *  Wasabi & NetBSD:  Run with it.
-- CDs, Integration, Embedding, Support -- http://www.wasabisystems.com/