Default Symbol Lookup Model
For each object added by a basic
dlopen
(3C), the runtime linker first
looks for the symbol in the executable. The
runtime linker then looks in each of the objects
provided during the initialization of the process.
If the symbol is still not found, the runtime linker
continues the search. The runtime linker next looks
in the object acquired through the
dlopen
(3C) and in any of its
dependencies.
The default symbol lookup model provides for a transition into a lazy loading environment. If a symbol can not be found in the currently loaded objects, any pending lazy loaded objects are processed in an attempt to locate the symbol. This loading compensates for objects that have not fully defined their dependencies. However, this compensation can undermine the advantages of a lazy loading.
In the following example, the executable
prog
and the shared object
B.so.1
have the following
dependencies.
$ ldd prog A.so.1 => ./A.so.1 $ ldd B.so.1 C.so.1 => ./C.so.1
If prog
acquires the shared
object B.so.1
by
dlopen
(3C), then any symbol required
to relocate the shared objects
B.so.1
and
C.so.1
will first be looked
for in prog
, followed by
A.so.1
, followed by
B.so.1
, and finally in
C.so.1
. In this simple case,
think of the shared objects acquired through the
dlopen
(3C) as if they had been added
to the end of the original link-edit of the
application. For example, the objects referenced in
the previous listing can be expressed
diagrammatically as shown in the following
figure.
A Single dlopen
()
Request
Any symbol lookup required by the objects acquired
from the
dlopen
(3C), that is shown as shaded
blocks, proceeds from the executable
prog
through to the final
shared object C.so.1
.
This symbol lookup is established by the attributes assigned to the objects as they were loaded. Recall that the executable and all the dependencies loaded with the executable are assigned global symbol visibility, and that the new objects are assigned world symbol search scope. Therefore, the new objects are able to look for symbols in the original objects. The new objects also form a unique group in which each object has local symbol visibility. Therefore, each object within the group can look for symbols within the other group members.
These new objects do not affect the normal symbol
lookup required by either the application or the
applications initial dependencies. For example, if
A.so.1
requires a function
relocation after the previous
dlopen
(3C) has occurred, the runtime
linker normal search for the relocation symbol is to
look in prog
and then
A.so.1
. The runtime linker
does not follow through and look in
B.so.1
or
C.so.1
.
This symbol lookup is again a result of the attributes assigned to the objects as they were loaded. The world symbol search scope is assigned to the executable and all the dependencies loaded with it. This scope does not allow them to look for symbols in the new objects that only offer local symbol visibility.
These symbol search and symbol visibility attributes
maintain associations between objects. These
associations are based on their introduction into
the process address space, and on any dependency
relationship between the objects. Assigning the
objects associated with a given
dlopen
(3C) to a unique group ensures
that only objects associated with the same
dlopen
(3C) are allowed to look up
symbols within themselves and their related
dependencies.
This concept of defining associations between objects
becomes more clear in applications that carry out
more than one
dlopen
(3C). For example, suppose the
shared object D.so.1
has the
following dependency.
$ ldd D.so.1
E.so.1 => ./E.so.1
and the prog
application used
dlopen
(3C) to load this shared object
in addition to the shared object
B.so.1
. The following figure
illustrates the symbol lookup relationship between
the objects.
Multiple dlopen
()
Requests
Suppose that both B.so.1
and
D.so.1
contain a definition
for the symbol foo
, and both
C.so.1
and
E.so.1
contain a relocation
that requires this symbol. Because of the
association of objects to a unique group,
C.so.1
is bound to the
definition in B.so.1
, and
E.so.1
is bound to the
definition in D.so.1
. This
mechanism is intended to provide the most intuitive
binding of objects that are obtained from multiple
calls to
dlopen
(3C).
When objects are used in the scenarios
that have so far been described, the order in which
each
dlopen
(3C) occurs has no effect on
the resulting symbol binding. However, when objects
have common dependencies, the resultant bindings can
be affected by the order in which the
dlopen
(3C) calls are made.
In the following example, the shared objects
O.so.1
and
P.so.1
have the same common
dependency.
$ ldd O.so.1 Z.so.1 => ./Z.so.1 $ ldd P.so.1 Z.so.1 => ./Z.so.1
In this example, the prog
application will
dlopen
(3C) each of these shared
objects. Because the shared object
Z.so.1
is a common dependency
of both O.so.1
and
P.so.1
,
Z.so.1
is assigned to both of
the groups that are associated with the two
dlopen
(3C) calls. This relationship
is shown in the following figure.
Multiple dlopen
() Requests
With A Common Dependency
Z.so.1
is available for both
O.so.1
and
P.so.1
to look up symbols.
More importantly, as far as
dlopen
(3C) ordering is concerned,
Z.so.1
is also be able to
look up symbols in both O.so.1
and P.so.1
.
Therefore, if both O.so.1
and
P.so.1
contain a definition
for the symbol foo
, which is
required for a Z.so.1
relocation, the actual binding that occurs is
unpredictable because it is affected by the order of
the
dlopen
(3C) calls. If the
functionality of symbol foo
differs between the two shared objects in which it
is defined, the overall outcome of executing code
within Z.so.1
might vary
depending on the application's
dlopen
(3C) ordering.