> I don't know how exactly is $ORIGIN handled by the run-time linker, ...
There are many things at work here. First, the kernel passes ld.so.1
the pathname of the executable that starts the process. Typically,
because the shells prepend each element of PATH to the command being
executed, you get an exec() of a full pathname. If however, you have
"." in your path, or explicitly execute a relative path (say ./foo),
then the relative name is passed to exec() and then onto ld.so.1.
If ld.so.1 is given a full path it can process any later $ORIGIN
expansion by using this path. Otherwise it must determine the full
pathname. Typically this is only relevant to the executable.
Dependencies are normally found by taking the needed
name, say libfoo.so.1, and prepending runpaths or default search paths,
hence they usually resolve to a full pathname as a process of loading them.
If the executable results in a relative path then we have to determine
the full path in case the current working directory is changed prior to
$ORIGIN expansion being required. Probably not very typical, but you
could have a runpath with $ORIGIN that wasn't used until a dependency was
lazy loaded or dlopen'ed.
To get a full path, ld.so.1 performs a getcwd(), and although streamlined
compared to libc's version this is still a lot of work (getdents, stats etc.).
So, we defer any pathname expansion until the time we determine it needs
to be done (ie. when we first come across a $ORIGIN). But, the application
may have changed directory before this expansion. Thus the flags are there
to force relative path expansion up front.
So, if an application uses $ORIGIN, there will be a cost associated with
any relative path expansion. Unless dependencies use relative paths there's
probably never going to be any cost in $ORIGIN use in a dependency.
As any applications runpaths are usually exhausted to find its immediate
dependencies (they'll even be used to find things like libc), the chances
of a change in the current working directory before this expansion occurs
is minimal (although you could try dlopen("$ORIGIN/lib/libfoo.so.1") after
you've chdir()'d :-).
There is work in progress to make getcwd() have less overhead (use of
the new /etc/mnttab helps), and because of other requests that ld.so.1
provide full pathnames when tools attach to processes and use librtld_db,
the expansion of any relative pathname will probably become default, and
thus make -z origin and LD_ORIGIN obsolete.
> Relative to the library; `ldd -s' shows it nicely. However, -zlazyload
> seems to work only for the main executable. If a.out depends on libA (lazy
> or not) and libA depends on libB (lazy) then both libA and libB will be
> loaded (when the process encounters the first function from libA, even if
> that function doesn't need anything from libB; this in case libA was
> linked with -zlazyload).
> Is it actually impossible to implement lazy loading for shared libraries or
> it has just not been done yet?
You can lazyload anything . Note that
lazy loading is triggered by relocations. If libA references a data item
in libB, or if libA *hasn't* been built -Kpic, you'll have data references
to libB's functions rather than references that go through a .plt. Data
relocations are carried out immediately when an object is loaded, thus any
lazy-loading of the dependency will occur immediately, making you think
that lazy-loading wasn't occurring.
You can see if lazy-loading is occurring with ldd much better in 2.8
(see -L option), or via ld.so.1's debugging (2.7 and up):
% LD_DEBUG=files ld main.o
10302: file=libelf.so.1; lazy loaded; bindings \
from file=`/usr/lib/libld.so.2' sym=`elf_version'