4

The code below demonstrates the expected (and arguably, intuitive) behavior. Just as static objects in an executable are initialized before main() is entered, one would expect static objects in a dynamically loaded library to be initialized before dlopen() returns.

The issue: is this behavior for libraries loaded at runtime guaranteed in any way, or is it just a convenient accident or a lucky implementation detail? Can we rely on constructors of static objects in the libraries being invoked, or do we have to resort to alternatives such as functions marked with __attribute__((constructor)) in order to ensure some desired behavior within the scope of the dlopen() call?

// libtest.cpp
#include <iostream>

namespace
{
    class Test
    {
    public:
        Test()  { std::cerr << "In Test()...\n"; }
        ~Test() { std::cerr << "In ~Test()...\n"; }
    };

    Test    test; // when is this initialized?
}

// testso.cpp
#include <dlfcn.h>
#include <iostream>

int main( int ac, char* av[] )
{
    if ( ac < 2 )
    {
        std::cerr << "Usage: " << av[0] << "library-name\n";
        return 1;
    }
    std::cerr << "Before dlopen()...\n";
    ::dlerror();
    void*    _handle(::dlopen( av[1], RTLD_NOW ));
    std::cerr << "After dlopen()...\n";
    if ( !_handle )
    {
        std::cerr << "Error: " << ::dlerror() << ", exiting...\n";
        return 2;
    }
    ::dlclose( _handle );
    std::cerr << "After dlclose()...\n";
    return 0;
}

Compiled and run (note Test() invocation before dlopen() returns):

$ g++ -o libtest.so -shared -fPIC libtest.cpp
$ g++ -o testso -ldl testso.cpp
$ ./testso ./libtest.so
Before dlopen()...
In Test()...
After dlopen()...
In ~Test()...
After dlclose()...
$ 

.

arayq2
  • 2,502
  • 17
  • 21
  • maybe a duplicate of http://stackoverflow.com/questions/19373061/what-happens-to-global-and-static-variables-in-a-shared-library-when-it-is-dynam? – Donghui Zhang Oct 18 '16 at 18:59
  • First tip: don't use `__attribute__((constructor))` _if_ you can avoid it. It's generally considered a hack and is not portable. Use an exported function instead. Just throwing that obligatory warning label out there. – Qix - MONICA WAS MISTREATED Oct 18 '16 at 19:25
  • An exported function would have to be called from outside; whereas a constructor routine is run automagically, as it were, no outside action required (other than kicking off the load of the library itself). When the latter behavior is desired, the issue is whether a constructor of a static object can fulfill this. A "constructor function" would then be for languages such as C that don't have the constructor concept natively. – arayq2 Oct 19 '16 at 13:09

1 Answers1

4

First things first: dlopen() is incredibly platform specific and as with the case of early sickness symptoms and WebMD you should always consult your platform's relevant man page.

This holds true even though the dlopen() family of functions appears to conform to the IEEE Standard 1003.1, 2004 Edition, though I couldn't tell you how compliant today's systems are with such standards (e.g. Windows has a long standing history of shoddy POSIX compliance).


On OS/X / BSD, yes:

dlopen() examines the mach-o file specified by path. If the file is compatible with the current process and has not already been loaded into the current process, it is loaded and linked. After being linked, if it contains any initializer functions, they are called, before dlopen() returns.

Emphasis mine.


On Linux, yes:

Shared objects may export functions using the __attribute__((constructor)) and __attribute__((destructor)) function attributes. Constructor functions are executed before dlopen() returns, and destructor functions are executed before dlclose() returns.

Emphasis mine.


On Solaris, yes:

As part of loading a new object, initialization code within the object is called before the dlopen() returns. This initialization is user code, and as such, can produce errors that can not be caught by dlopen().

Emphasis mine.

Community
  • 1
  • 1
Qix - MONICA WAS MISTREATED
  • 14,451
  • 16
  • 82
  • 145
  • What if the process already has multiple threads - is it possible (accidentally) for other threads to begin using the dl before dlopen returns? Surely the initializers could do something to get other threads involved, but suppose nothing so explicit is done. – joeking Oct 18 '16 at 19:35
  • @joeking That's what good concurrency practices are for :) I wouldn't assume anything about the concurrency of system calls, _especially_ the fragile `dlopen()` family (anecdotal but generally good advice). – Qix - MONICA WAS MISTREATED Oct 18 '16 at 19:36
  • Yeah - This is an area where Windows and Linux have similar features but there are *huge* differences in the details. On Windows, "DllMain()" is what calls initializers and it is somewhat single threaded in that the OS holds the loaderlock (though exactly the effects of this aren't clearly documented). The result is that you have to be careful what OS functions get called from those initializers – joeking Oct 18 '16 at 19:43
  • @joeking Yep, Windows is the epitome of the _Initializers are a hack_ [comment](http://stackoverflow.com/questions/40115688/are-static-c-objects-in-dynamically-loaded-libraries-initialized-before-dlopen/40116528?noredirect=1#comment67504316_40115688) comment. I usually use them when injecting them into other processes, though there are even more effective ways to initialize a DLL (e.g. exported function proxying, etc.) – Qix - MONICA WAS MISTREATED Oct 18 '16 at 19:46
  • The Linux quote is ambiguous in that it could be taken to apply to explicitly marked functions only. I suppose it's reasonable to assume that real constructors are also of the `__attribute__((constructor))` type by definition, but people have been burned by assumptions before... – arayq2 Oct 18 '16 at 20:22
  • If they weren't loaded, a lot of stuff would be broken (static class members being the worst of all). All initialization routines fall under "initialization". Your assumptions are safe here :) – Qix - MONICA WAS MISTREATED Oct 18 '16 at 20:37
  • 1
    This [documentation](http://www.cs.stevens.edu/~jschauma/810/elf.html) states that the .ctors section has pointers to marked functions as well as C++ constructors, so I think that settles the issue:-) – arayq2 Oct 20 '16 at 00:15