Reputation: 5960
Assume that no other executable linked against a shared library libshlib
has already been loaded. And assume libshlib
contains one function marked with __attribute__(constructor)
and one function marked with __attribute__(destructor)
. When executable that is linked against libshlib
is started, libshlib
will be loaded and the corresponding function marked with __attribute(constructor)
is run exactly once. But what happens if the shared library can be reloaded e.g. via a user defined signal such as SIGUSR1
? From my testing it seems that the __attribute__(constructor)
is not run again. Is that correct or is there a standard saying otherwise?
Upvotes: 0
Views: 1598
Reputation: 33601
I assume you have a program that was linked via (e.g.):
cc -o mypgm mypgm.o -lshlib
Upon execution, once the ELF interpreter has loaded libshlib.so
and executed the constructor(s), the library is never loaded again. Side note: To find your interpreter do: readelf -a mypgm | grep interpreter:
If the program receives a signal (e.g. SIGUSR1
), the signal is either caught by a signal handler (assuming signal
or sigaction
has been called to set one up), or the default action is taken (which is [IIRC] program termination for SIGUSR1
). This does not cause the library to be reloaded.
There is no other action that can cause the library to be reloaded either. Only the destructor(s) will be called upon program exit (e.g. main
returns or exit
is called).
Even manually calling the destructors has no effect because the constructors and destructors are independent. (e.g. The constructor could do able = malloc(...)
and the destructor could do free(able)
. But, the destructor could do free(baker)
instead.). Calling a destructor does not "reset" a constructor.
To get the "reload" effect, the library would need to be dynamically loaded/unloaded via dlopen/dlsym/dlclose
. That is, the link command would be:
cc -o mypgm mypgm.o
Then, mypgm
would [at some point] call dlopen("libshlib.so")
(and the constructor(s) would be called). When [and if] mypgm
calls dlclose
, libshlib.so
will be unloaded (and destructor(s) called).
If mypgm
then called dlopen("libshlib.so")
a second time, the contructors would be called [again].
UPDATE:
Note that calling
dlclose
does not necessarily unload the library or call destructors.
I just checked the code [in glibc
]. The library has a refcount. The library will be unloaded if the refcount is 1 upon entry to dlclose
, which should be the case for libshlib.so
above with dlopen
[as nobody else bumps it up].
In other words, to force the "desired" behavior nothing else should refer to libshlib
via an -lshlib
. Not the program or any other .so
. This lays the groundwork.
Note that if libshlib.so
wanted glibc
, but so did the program, unloading libshlib
will bump down the glibc
refcount, but glibc
will remain because its refcount is [still] >0.
There are conditions where the library can't be unloaded (in fact, these conditions are much more common then conditions when the library can be unloaded).
Again, this is dependent upon the refcount and [possibly] some state. When the library is loaded from a "static" linkage (vs. dlopen
), the refcount gets an extra increment, so it won't get yanked.
The code also handles the case where a constructor calls dlopen
on its own library.
For a given libA
, if it needs libB
, B's refcount gets upped/downed by A's load/unload.
If the library is not unloaded, then it's not well defined whether destructors will run, and whether the subsequent dlopen will run constructors again
The whole point of using dlopen
this way for libshlib
is to guarantee the loading at dlopen
and unloading at dlclose
[along with constructor/destructor action]. This will be true if there is no static reference to it or cyclic dependency, which was the starting criteria.
UPDATE #2:
The part about "as nobody else bumps it up" is way too simplistic.
Don't confuse prose with substance.
As mentioned above: This will be true if there is no static reference to it or cyclic dependency.
This means that only the executable/object that does the dlopen/dlclose
for shlib
refers to a symbol in shlib
.
And, this is only via dlsym
. Otherwise, it's a static reference (i.e. in the object's symbol table as UNDEF)].
And, no shared library that shlib
drags in refers to a symbol defined in shlib
[the cyclic dependency].
Look at all the places where DF_1_NODELETE is set during symbol resolution.
Yes, I did look.
DF_1_NODELETE
is set in only the following places. None of them apply to this situation [or most dlopen
scenarios].
dlopen
has RTLD_NODELETE
, which we can avoid.dlopen
] gets DF_1_NODELETE
setLOCAL
, GLOBAL
, WEAK
, etc.) that is type 10 (STB_GNU_UNIQUE
)malloc
failure when adding a dependency [from a _different object]. This code doesn't even execute for the case herein.And, OP's usage aside, there are legitimate reasons for dlopen/dlclose
to work as I've set up/described.
See my answer here: Is it possible to perform munmap based on information in /proc/self/maps?
There, OP needed to have a nonstop program that could run for months/years (e.g. a hi-reliabilty, mission-critical application). If an updated version of one of its shared libraries was installed [via a package manager, etc.], the program had to dynamically, on-the-fly, without a re-exec, be able to load the newer version.
Upvotes: 3