13

I've been searching a way to make a shared library (let's name the library libbar.so) delay loaded on Linux and it should hopefully be realized with a help from only a linker, not modifying anything on the source code written in C++; I mean I don't want to invoke dlopen() nor dlsym() in the source code of the parent library (let's name it libfoo.so) to invoke a function of libbar.so because they make the source code messy and the maintenance process difficult. (In short, I'm expecting to go on the similar way to Visual Studio's /DELAYLOAD option even on Linux)

Anyway, I've found some uncertain information pieces related to my question on the internet so far, so it would be very nice to have the answers from you all for the following questions to make the information clear.

  1. Does GNU ld support any delay loading mechanism on Linux?
  2. If it doesn't, how about Clang?
  3. Is the dlopen() family the only way to make a shared library delay loaded on Linux?

I tested to pass -zlazy flag to GCC (g++) with a path to the library, it seemed to accept the flag but the behavior did not look making libbar.so delay loaded (Not having libbar.so, I was expecting to have an exception at the first call of libbar.so, but the exception actually raised before entering to libfoo.so). On the other hand, Clang (clang++) left a warning that it ignored the option flag.

Best regards,

yugr
  • 19,769
  • 3
  • 51
  • 96
Doofah
  • 384
  • 3
  • 12
  • clang uses gnu ld for linking (at least for 3.4 and earlier), so you won't find any help there. – Mats Petersson May 01 '14 at 07:42
  • @ Mats, thanks for your comment. Okay, I'll never mind Clang on the question and edit the original description above later. – Doofah May 01 '14 at 07:51
  • Was asked before: http://stackoverflow.com/questions/2957292/delay-load-equivalent-in-unix-based-systems – oakad May 01 '14 at 08:35
  • Roughly the same question, but this one is more specific. – MSalters May 01 '14 at 08:38
  • 1
    @ oakad, thank you :) I've also found the post, too. So do you believe that GNU ld does really not support any delay loading mechanism and will we have to put the dlopen()s onto our source code? (Oh, I wouldn't want to accept the fact even if it were true...) – Doofah May 01 '14 at 08:46
  • @ MSalters, thank you for leaving your comment! – Doofah May 01 '14 at 08:46
  • 1
    By my reading of the ld man page -zlazy (should be -z lazy) makes the library you are linking, in your case libfoo.so, delay loaded for its callers. For what you want this flag would have to be applied when building libbar.so. However the man page also says "lazy binding is the default" so libbar.so should already be linked with this. – msc Oct 11 '18 at 02:55
  • 1
    My immediately preceding comment is wrong. I misunderstood the man page. `-z lazy` is for lazy binding of symbols NOT lazy loading of the library. – msc Oct 11 '18 at 23:06

3 Answers3

13

Delay loading is NOT a runtime feature. MSVC++ implemented it without help from Windows. And like dlopen is the only way on Linux, GetProcAddress is the only runtime method on Windows.

So, what is delay loading then? It's very simple: Any call to a DLL has to go through a pointer (since you don't know where it will load). This has always been handled by the compiler and linker for you. But with delay loading, MSVC++ sets this pointer initially to a stub that calls LoadLibrary and GetProcAddress for you.

Clang can do the same without help from ld. At runtime, it's just an ordinary dlopen call, and Linux cannot determine that Clang inserted it.

Andrey Belykh
  • 2,578
  • 4
  • 32
  • 46
MSalters
  • 173,980
  • 10
  • 155
  • 350
  • @ MSalters, thanks again. Do you mean Clang can handle delay loading by itself? I'm actually expecting to implicitly have a helper function like MSVC++ provides us by passing a option flags to the compiler or linker (I forgot the name of the helper function though) because the mechanism hides everything behind the delay loaded functions call (so a user does not have to care such a helper function is called beneath the our source code). – Doofah May 01 '14 at 08:59
  • Yes, **__delayLoadHelper2** is the helper function! Probably some compilers or linkers (like Sun's something or Apple's ld64) must have provided such a helper function but I don't know what's going on Linux. (And I'm a very new to Linux ;) – Doofah May 01 '14 at 09:04
  • @user3591878: GCC on Linux has the unfortunate habit of muddling the distinction between OS and compiler, but that's a UNIX tradition. (See `ld` versus `ld.so`). It really shouldn't matter that you're new to Linux, because Linux should not matter. GCC and CLang do. They _could_ implement Delay Loading on any OS which has `dlopen`. Doesn't mean that they have actually done so. – MSalters May 01 '14 at 09:30
  • @ MSalters, I see what you mean and thank you very much for making the issue more clearer :) Anyway, the results I've got seem to be showing to call the dlopen()s is the only way to go at this case... – Doofah May 01 '14 at 09:49
  • @user3591878: Nope, sorry. – MSalters May 01 '14 at 10:12
  • 2
    @MSalters "Doesn't mean that they have actually done so" - there is not builtin solution but can be done with minor effort, see [Implib.so](https://github.com/yugr/Implib.so). – yugr Feb 24 '20 at 09:23
6

This functionality can be achieved in a portable way using Proxy design pattern.

In code it may look something like this:

#include <memory>

// SharedLibraryProxy.h
struct SharedLibraryProxy
{
    virtual ~SharedLibraryProxy() = 0;

    // Shared library interface begin.
    virtual void foo() = 0;
    virtual void bar() = 0;
    // Shared library interface end.

    static std::unique_ptr<SharedLibraryProxy> create();
};

// SharedLibraryProxy.cc
struct SharedLibraryProxyImp : SharedLibraryProxy
{
    void* shared_lib_ = nullptr;
    void (*foo_)() = nullptr;
    void (*bar_)() = nullptr;

    SharedLibraryProxyImp& load() {
        // Platform-specific bit to load the shared library at run-time.
        if(!shared_lib_) { 
            // shared_lib_ = dlopen(...);
            // foo_ = dlsym(...)
            // bar_ = dlsym(...)
        }
        return *this;
    }

    void foo() override {
        return this->load().foo_();
    }

    void bar() override {
        return this->load().bar_();
    }
};

SharedLibraryProxy::~SharedLibraryProxy() {}

std::unique_ptr<SharedLibraryProxy> SharedLibraryProxy::create() {
    return std::unique_ptr<SharedLibraryProxy>{new SharedLibraryProxyImp};
}

// main.cc
int main() {
    auto shared_lib = SharedLibraryProxy::create();
    shared_lib->foo();
    shared_lib->bar();
}
Maxim Egorushkin
  • 131,725
  • 17
  • 180
  • 271
  • @ Maxim, thanks for leaving the practical pseudo code. That design pattern looks to be very helpful to bridge the both libraries :) – Doofah May 01 '14 at 09:55
  • This only works for functions. There is no way for variables unless it is implemented in the runtime linker. Question still is if this is possible at all. – Lothar Jun 23 '15 at 12:35
  • @Lothar How did you come to that conclusion? This mechanism works for both functions and objects. – Maxim Egorushkin Jun 23 '15 at 12:37
2

To add to MSalters answer, one can easily mimic Windows approach to lazy loading on Linux by creating a small static stub library that would try to dlopen needed library on first call to any of it's functions (emitting diagnostic message and terminating if dlopen failed) and then forwarding all calls to it.

Such stub libraries can be written by hand, generated by project/library-specific script or generated by universal tool Implib.so:

$ implib-gen.py libxyz.so
$ gcc myapp.c libxyz.tramp.S libxyz.init.c ...
yugr
  • 19,769
  • 3
  • 51
  • 96