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

Bug#787734: marked as done (libstdc++ breaks dynamic unloading)



Your message dated Fri, 18 May 2018 18:48:56 +0200
with message-id <09391a7f-229d-1548-4e75-56bc75ee7acd@debian.org>
and subject line Re: libstdc++ breaks dynamic unloading
has caused the Debian Bug report #787734,
regarding libstdc++ breaks dynamic unloading
to be marked as done.

This means that you claim that the problem has been dealt with.
If this is not the case it is now your responsibility to reopen the
Bug report if necessary, and/or fix the problem forthwith.

(NB: If you are a system administrator and have no idea what this
message is talking about, this may indicate a serious mail system
misconfiguration somewhere. Please contact owner@bugs.debian.org
immediately.)


-- 
787734: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=787734
Debian Bug Tracking System
Contact owner@bugs.debian.org with problems
--- Begin Message ---
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

--- End Message ---
--- Begin Message ---
closing this issue, feedback requested in 2016, but no reply.

--- End Message ---

Reply to: