[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Bug#787734: libstdc++ breaks dynamic unloading



Package: libstdc++6
Version: 4.9.2-10
Severity: normal

Where libstdc++ is dynamically loaded, in some cases it prevents an
unrelated dynamic library that was loaded earlier from being unloaded.
It also, incidentally, can't itself be unloaded, but that's much less
of a problem.  When I say that a library can't be unloaded, I mean that
dlclose() indicates success, but the library actually remains mapped.
I've been probing this with a test program, which just dlopens a sequence
of libraries and then dlcloses them in reverse sequence, looking at
what's actually mapped at each point:

$ cat try_dl.c
#include <stdio.h>
#include <dlfcn.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc, char **argv)
{
	setbuf(stdout, NULL);
	if(argc <= 1) exit(2);
	argv++; argc--;
	void **handles = malloc(sizeof(void*) * argc);
	if(!handles) exit(1);
	char maps_cmd[100];
	sprintf(maps_cmd, "sed -n 's:.* /:    /:p' /proc/%ld/maps | sort -u", (long)getpid());
	system(maps_cmd);
	for(int i = 0; i != argc; i++) {
		handles[i] = dlopen(argv[i], RTLD_LAZY);
		printf("dlopen(\"%s\", RTLD_LAZY) = %p\n", argv[i], handles[i]);
		system(maps_cmd);
		if(!handles[i]) exit(1);
	}
	for(int i = argc; i--; ) {
		int ret = dlclose(handles[i]);
		printf("dlclose(%p /*\"%s\"*/) = %d\n", handles[i], argv[i], ret);
		system(maps_cmd);
		if(ret) exit(1);
	}
	exit(0);
}
$ gcc -std=c99 try_dl.c -ldl -o try_dl
$ ./try_dl /usr/lib/x86_64-linux-gnu/libperl.so.5.20 /usr/lib/x86_64-linux-gnu/libstdc++.so.6
    /home/zefram/tmp/try_dl
    /lib/x86_64-linux-gnu/ld-2.19.so
    /lib/x86_64-linux-gnu/libc-2.19.so
    /lib/x86_64-linux-gnu/libdl-2.19.so
dlopen("/usr/lib/x86_64-linux-gnu/libperl.so.5.20", RTLD_LAZY) = 0x1761070
    /home/zefram/tmp/try_dl
    /lib/x86_64-linux-gnu/ld-2.19.so
    /lib/x86_64-linux-gnu/libc-2.19.so
    /lib/x86_64-linux-gnu/libcrypt-2.19.so
    /lib/x86_64-linux-gnu/libdl-2.19.so
    /lib/x86_64-linux-gnu/libm-2.19.so
    /lib/x86_64-linux-gnu/libpthread-2.19.so
    /usr/lib/x86_64-linux-gnu/libperl.so.5.20.2
dlopen("/usr/lib/x86_64-linux-gnu/libstdc++.so.6", RTLD_LAZY) = 0x1762ae0
    /home/zefram/tmp/try_dl
    /lib/x86_64-linux-gnu/ld-2.19.so
    /lib/x86_64-linux-gnu/libc-2.19.so
    /lib/x86_64-linux-gnu/libcrypt-2.19.so
    /lib/x86_64-linux-gnu/libdl-2.19.so
    /lib/x86_64-linux-gnu/libgcc_s.so.1
    /lib/x86_64-linux-gnu/libm-2.19.so
    /lib/x86_64-linux-gnu/libpthread-2.19.so
    /usr/lib/x86_64-linux-gnu/libperl.so.5.20.2
    /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.20
dlclose(0x1762ae0 /*"/usr/lib/x86_64-linux-gnu/libstdc++.so.6"*/) = 0
    /home/zefram/tmp/try_dl
    /lib/x86_64-linux-gnu/ld-2.19.so
    /lib/x86_64-linux-gnu/libc-2.19.so
    /lib/x86_64-linux-gnu/libcrypt-2.19.so
    /lib/x86_64-linux-gnu/libdl-2.19.so
    /lib/x86_64-linux-gnu/libgcc_s.so.1
    /lib/x86_64-linux-gnu/libm-2.19.so
    /lib/x86_64-linux-gnu/libpthread-2.19.so
    /usr/lib/x86_64-linux-gnu/libperl.so.5.20.2
    /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.20
dlclose(0x1761070 /*"/usr/lib/x86_64-linux-gnu/libperl.so.5.20"*/) = 0
    /home/zefram/tmp/try_dl
    /lib/x86_64-linux-gnu/ld-2.19.so
    /lib/x86_64-linux-gnu/libc-2.19.so
    /lib/x86_64-linux-gnu/libcrypt-2.19.so
    /lib/x86_64-linux-gnu/libdl-2.19.so
    /lib/x86_64-linux-gnu/libgcc_s.so.1
    /lib/x86_64-linux-gnu/libm-2.19.so
    /lib/x86_64-linux-gnu/libpthread-2.19.so
    /usr/lib/x86_64-linux-gnu/libperl.so.5.20.2
    /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.20

Observe that both libperl and libstdc++ remain loaded after the dlcloses,
along with all their dependencies.  But if libperl is loaded without
libstdc++, it unloads as one would expect:

$ ./try_dl /usr/lib/x86_64-linux-gnu/libperl.so.5.20
    /home/zefram/tmp/try_dl
    /lib/x86_64-linux-gnu/ld-2.19.so
    /lib/x86_64-linux-gnu/libc-2.19.so
    /lib/x86_64-linux-gnu/libdl-2.19.so
dlopen("/usr/lib/x86_64-linux-gnu/libperl.so.5.20", RTLD_LAZY) = 0xd48070
    /home/zefram/tmp/try_dl
    /lib/x86_64-linux-gnu/ld-2.19.so
    /lib/x86_64-linux-gnu/libc-2.19.so
    /lib/x86_64-linux-gnu/libcrypt-2.19.so
    /lib/x86_64-linux-gnu/libdl-2.19.so
    /lib/x86_64-linux-gnu/libm-2.19.so
    /lib/x86_64-linux-gnu/libpthread-2.19.so
    /usr/lib/x86_64-linux-gnu/libperl.so.5.20.2
dlclose(0xd48070 /*"/usr/lib/x86_64-linux-gnu/libperl.so.5.20"*/) = 0
    /home/zefram/tmp/try_dl
    /lib/x86_64-linux-gnu/ld-2.19.so
    /lib/x86_64-linux-gnu/libc-2.19.so
    /lib/x86_64-linux-gnu/libdl-2.19.so
    /lib/x86_64-linux-gnu/libpthread-2.19.so

In this case libpthread, loaded as one of libperl's dependencies, won't
unload, but this doesn't seem to be a problem.

I'll spare you the full try_dl traces for other combinations; here's what
I've learned from experimenting with it.  Most shared libraries unload
as they ought to.  libpthread and libstdc++ share the unusual feature
seen above that once loaded (either directly or as a dependency) they
won't unload, and this seems to be something inherent in those libraries.
A handful of other libraries also won't unload, but have either libpthread
or libstdc++ as a dependency, so maybe their stickiness is caused by
those two: for example librt (depending on libpthread) and libjack
(depending on libstdc++).  Not all libraries depending on those two get
this behaviour, however.

Sometimes, where a library depends on libstdc++ and others, the main
library will unload, but libstdc++ and some other dependency will not.
For example, libxatracker depends on libstdc++ and libffi (and some
others), and can itself be unloaded along with some of its dependencies,
but leaves both libstdc++ and libffi loaded.  If libffi and libstdc++ are
loaded without libxatracker, libstdc++ stays loaded but libffi unloads OK.

libstdc++ alone, not libpthread, has this behaviour of keeping loaded a
library that does not depend on it.  Only a small number of libraries
are affected by this: libperl (as seen above), libtcl, and libtk.
The stickiness only occurs if libstdc++ is loaded after the victim.
If libstdc++ is loaded first, then the victim is able to unload (though
libstdc++ itself still won't unload).

On a system I have access to with older packages, including libstdc++
4.4.3, libstdc++ does not have the effect of preventing library unloading,
either of itself or of other libraries.  I do see libpthread and librt
failing to unload, but not all of libpthread's dependents that have that
behaviour on the newer system.

The real problem that I run into due to this unloading problem is
concerned with running Perl code under Apache, for which I have custom
builds of both.  Apache likes to process its configuration twice in
each run, unloading all of its modules between runs.  When mod_perl
is shut down, it tries to unload everything that Perl loaded.  It so
happens that one of the Perl XS modules incorporates some C++ code and
so brings in libstdc++ as a dependency.  libperl, having been loaded as
a dependency of mod_perl, ought to unload when Apache unloads mod_perl,
but due to this problem with libstdc++ it does not.  In the absence of
unloading, the second time through the configuration has stale values in
Perl global variables, and the process quickly crashes when Perl follows a
hook pointing at a module that was successfully unloaded.  I need libperl
to unload properly in order to get a fresh set of its global variables.

I have a potential workaround of modifying Apache to explicitly load
libstdc++ during startup, because (as noted above) having it loaded
first seems to prevent it interfering with the unloading of other modules.

-zefram


Reply to: