lord.garbage lord.garbage - 1 year ago 87
C Question

Are functions marked with __attribute__(constructor) run again when a shared library is reloaded?

Assume that no other executable linked against a shared library

has already been loaded. And assume
contains one function marked with
and one function marked with
. When executable that is linked against
is started,
will be loaded and the corresponding function marked with
is run exactly once. But what happens if the shared library can be reloaded e.g. via a user defined signal such as
? From my testing it seems that the
is not run again. Is that correct or is there a standard saying otherwise?

Answer Source

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].


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.


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].

  1. If the flags argument to dlopen has RTLD_NODELETE, which we can avoid.
  2. If profiling is enabled, the profile map [not related to our dlopen] gets DF_1_NODELETE set
  3. If a symbol has a bind type (e.g. LOCAL, GLOBAL, WEAK, etc.) that is type 10 (STB_GNU_UNIQUE)
  4. A symbol is referenced by a non-dynamic object as an UNDEF [in its symbol table] that can't be removed [because the referent object has DF_1_NODELETE set]. This does not apply because of the pre-conditions above.
  5. There was 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.