The symbols foo and bar will be searched for in the file foo.so.1, followed by any dependencies that are associated with this file. The function foo is then called with the single argument bar as part of the return() statement.
If the application prog is built using the above file main.c and its initial dependencies are:
$ ldd prog libdl.so.1 => /usr/lib/libdl.so.1 libc.so.1 => /usr/lib/libc.so.1 |
then if the file name specified in the dlopen(3DL) had the value 0, the symbols foo and bar will be searched for in prog, followed by /usr/lib/libdl.so.1, and finally /usr/lib/libc.so.1.
Once the handle has indicated the root at which to start a symbol search, the search mechanism follows the same model as described in "Symbol Lookup".
If the required symbol cannot be located, dlsym(3DL) will return a NULL value. In this case, dlerror(3DL) can be used to indicate the true reason for the failure. In the following example the application prog was unable to locate the symbol bar.
$ prog dlsym: ld.so.1: main: fatal: bar: can't find symbol |
Testing for Functionality
The special handle RTLD_DEFAULT enables an application to test for the existence of another symbol. The symbol search follows the same model as used to relocate the calling object. See "Default Symbol Lookup Model". For example, if the application prog contained the following code fragment:
if ((fptr = (int (*)())dlsym(RTLD_DEFAULT, "foo")) != NULL) (*fptr)(); |
then foo will be searched for in prog, followed by /usr/lib/libdl.so.1, and then /usr/lib/libc.so.1. If this code fragment was contained in the file B.so.1 from the example shown in Figure 3-1, then the search for foo will continue into B.so.1 and then C.so.1.
This mechanism provides a robust and flexible alternative to the use of undefined weak references, discussed in "Weak Symbols".
Using Interposition
The special handle RTLD_NEXT enables an application to locate the next symbol in a symbol scope. For example, if the application prog contained the following code fragment:
if ((fptr = (int (*)())dlsym(RTLD_NEXT, "foo")) == NULL) { (void) printf("dlsym: %s\n", dlerror()); exit (1); } return ((*fptr)()); |
then foo will be searched for in the shared objects associated with prog, which in this case are /usr/lib/libdl.so.1 and then /usr/lib/libc.so.1. If this code fragment was contained in the file B.so.1 from the example shown in Figure 3-1, then foo will be searched for in the associated shared object C.so.1 only.
Using RTLD_NEXT provides a means to exploit symbol interposition. For example, a function within an object can be interposed upon by a preceding object, which can then augment the processing of the original function. For example, if the following code fragment is placed in the shared object malloc.so.1:
#include <sys/types.h> #include <dlfcn.h> #include <stdio.h> void * malloc(size_t size) { static void * (* fptr)() = 0; char buffer[50]; if (fptr == 0) { fptr = (void * (*)())dlsym(RTLD_NEXT, "malloc"); if (fptr == NULL) { (void) printf("dlopen: %s\n", dlerror()); return (0); } } (void) sprintf(buffer, "malloc: %#x bytes\n", size); (void) write(1, buffer, strlen(buffer)); return ((*fptr)(size)); } |
then by interposing this shared object between the system library /usr/lib/libc.so.1 where malloc(3C) usually resides, any calls to this function will be interposed on before the original function is called to complete the allocation:
$ cc -o malloc.so.1 -G -K pic malloc.c $ cc -o prog file1.o file2.o ..... -R. malloc.so.1 $ prog malloc: 0x32 bytes malloc: 0x14 bytes .......... |
Alternatively, this same interposition can be achieved by:
$ cc -o malloc.so.1 -G -K pic malloc.c $ cc -o prog main.c $ LD_PRELOAD=./malloc.so.1 prog malloc: 0x32 bytes malloc: 0x14 bytes .......... |
Note - Users of any interposition technique must be careful to handle any possibility of recursion. The previous example formats the diagnostic message using sprintf(3C), instead of using printf(3C) directly, to avoid any recursion caused by printf(3C)'s possible use of malloc(3C).
The use of RTLD_NEXT within a dynamic executable or preloaded object provides a predictable and useful interposition technique. Be careful when using this technique in a generic object dependency, as the actual load order of objects is not always predictable.
Feature Checking
Dynamic objects built by the link-editor sometimes require new runtime linker features. The function _check_rtld_feature() can be used to check if the runtime features required for execution are supported by the running runtime linker. The runtime features currently identified are listed in Table 7-46.
Debugging Aids
A debugging library and mdb(1) module are provided with the Solaris linkers. The debugging library enables you to trace the runtime linking process in more detail. The mdb(1) module enables interactive process debugging.
Debugging Library
This debugging library helps you understand, or debug, the execution of applications and dependencies. Although the type of information displayed using this library is expected to remain constant, the exact format of the information might change slightly from release to release.
Some of the debugging output might be unfamiliar to those who do not have an intimate knowledge of the runtime linker. However, many aspects may be of general interest to you.
Debugging is enabled by using the environment variable LD_DEBUG. All debugging output is prefixed with the process identifier and by default is directed to the standard error. This environment variable must be augmented with one or more tokens to indicate the type of debugging required.
The tokens available with this debugging option can be displayed by using LD_DEBUG=help. Any dynamic executable can be used to solicit this information, as the process itself will terminate following the display of the information. For example:
$ LD_DEBUG=help prog 11693: 11693: For debugging the runtime linking of an application: 11693: LD_DEBUG=token1,token2 prog 11693: enables diagnostics to the stderr. The additional 11693: option: 11693: LD_DEBUG_OUTPUT=file 11693: redirects the diagnostics to an output file created 11593: using the specified name and the process id as a 11693: suffix. All diagnostics are prepended with the 11693: process id. 11693: 11693: 11693: basic provide basic trace information/warnings 11693: bindings display symbol binding; detail flag shows 11693: absolute:relative addresses 11693: detail provide more information in conjunction with other 11693: options 11693: files display input file processing (files and libraries) 11693: help display this help message 11693: libs display library search paths 11693: move display move section processing 11693: reloc display relocation processing 11693: symbols display symbol table processing; 11693: detail flag shows resolution and linker table addition 11693: versions display version processing 11693: audit display runtime link-audit processing |
This example shows the options meaningful to the runtime linker. The exact options might differ from release to release.
The environment variable LD_DEBUG_OUTPUT can be used to specify an output file for use instead of the standard error. The process identifier is added as a suffix to the output file.
Debugging of secure applications is not allowed.
One of the most useful debugging options is to display the symbol bindings that occur at runtime. The following example uses a very trivial dynamic executable that has a dependency on two local shared objects.
$ cat bar.c int bar = 10; $ cc -o bar.so.1 -Kpic -G bar.c $ cat foo.c foo(int data) { return (data); } $ cc -o foo.so.1 -Kpic -G foo.c $ cat main.c extern int foo(); extern int bar; main() { return (foo(bar)); } $ cc -o prog main.c -R/tmp:. foo.so.1 bar.so.1 |