Providing an Alternative to dlopen()

dlopen(3C) and dlsym(3C) are often used to load and exercise additional objects. See Runtime Linking Programming Interface. For example, the following code from libdep.so.1 loads libbar.so.1, and on success calls interfaces provided by libbar.so.1.

void dep()
{
        void *handle;

        if ((handle = dlopen("libbar.so.1", RTLD_LAZY)) != NULL) {
                int (*fptr)();

                if ((fptr = (int (*)())dlsym(handle, "bar1")) != NULL)
                        (*fptr)(arg1);
                if ((fptr = (int (*)())dlsym(handle, "bar2")) != NULL)
                        (*fptr)(arg2);
                ....
        }
}

Although very flexible, this model of using dlopen() and dlsym() is an unnatural coding style, and has some drawbacks.

  • The object in which the symbols are expected to exist must be known.

  • The calls through function pointers provide no means of verification by either the compiler, or lint(1).

This code can be simplified if the object that supplies the required interfaces satisfies the following conditions.

  • The object can be established as a dependency at link-edit time.

  • The object is always available.

By exploiting that a function reference can trigger lazy loading, the same delayed loading of libbar.so.1 can be achieved. In this case, the reference to the function bar1() results in lazy loading the associated dependency. This coding is far more natural, and the use of standard function calls provides for compiler, or lint(1) validation.

void dep()
{
        bar1(arg1);
        bar2(arg2);
        ....
}
$ cc -G -o libdep.so.1 dep.c -L. -z lazyload -lbar -lc

However, this model fails if the object that provides the required interfaces is not always available. Should the application be exercised when LD_BIND_NOW is set, or the shared object be loaded through dlopen(3C) with the RTLD_NOW flag, then all references from the associated objects are processed. Any failure to bind a symbol reference to a definition results in a fatal error.

In this case, it is desirable to test for the existence of the dependency, without having to know the dependency name. A means of testing for the availability of a dependency that satisfies a function reference is required.

A robust model for testing for the existence of a function can be achieved with deferred symbol references, or deferred dependencies, and use of dlsym(3C) with the RTLD_PROBE handle.

Deferred symbol references differ from standard symbol references in the following details.

  • Deferred references can only be established for function calls.

  • Deferred references are directly bound at runtime to the associated dependency.

  • Deferred references are not resolved as part of standard relocation processing, or LD_BIND_NOW processing, or through dlopen(3C) with the RTLD_NOW flag.

Deferred references are resolved during process execution, when the associated function is first referenced. The assurance of this delayed resolution provides a window where the caller can test for the existence of the deferred dependency before making calls to the deferred function.