Viewing the files the runtime linker is operating on

I picked up a neat trick on the Solaris linker mailing list this week. If you want to see the complete set of input files that are processed by the runtime linker, you can use the “-Dfiles” LD_OPTION:

$ LD_OPTIONS=-Dfiles /usr/sfw/bin/gcc -o foo foo.c

debug: 
debug: file=/usr/lib/crt1.o  [ ET_REL ]
debug: 
debug: file=/usr/lib/crti.o  [ ET_REL ]
debug: 
debug: file=/usr/lib/values-Xa.o  [ ET_REL ]
debug: 
debug: file=/usr/sfw/lib/gcc/i386-pc-solaris2.11/3.4.3/crtbegin.o  [ ET_REL ]
debug: 
debug: file=/var/tmp//cclLa4fy.o  [ ET_REL ]
debug: 
debug: file=/usr/sfw/lib/gcc/i386-pc-solaris2.11/3.4.3/libgcc.a  [ archive ] 
debug: 
debug: file=/usr/sfw/lib/gcc/i386-pc-solaris2.11/3.4.3/libgcc_eh.a  [ archive ] 
debug: 
debug: file=/usr/lib/libc.so  [ ET_DYN ]
debug: file=/usr/sfw/lib/gcc/i386-pc-solaris2.11/3.4.3/libgcc.a  reusing: originally processed as /usr/sfw/lib/gcc/i386-pc-solaris2.11/3.4.3/libgcc.a
debug: 
debug: file=/usr/sfw/lib/gcc/i386-pc-solaris2.11/3.4.3/libgcc.a  [ archive ] 
debug: file=/usr/sfw/lib/gcc/i386-pc-solaris2.11/3.4.3/libgcc_eh.a  reusing: originally processed as /usr/sfw/lib/gcc/i386-pc-solaris2.11/3.4.3/libgcc_eh.a
debug: 
debug: file=/usr/sfw/lib/gcc/i386-pc-solaris2.11/3.4.3/libgcc_eh.a  [ archive ] 
debug: 
debug: file=/usr/sfw/lib/gcc/i386-pc-solaris2.11/3.4.3/crtend.o  [ ET_REL ]
debug: 
debug: file=/usr/lib/crtn.o  [ ET_REL ]
debug: 

The Solaris linker provides numerous other debugging options, and the full list can be viewed by running ld with the “-Dhelp” option:

$ /usr/ccs/bin/ld -Dhelp

debug: 
debug: Runtime Linking
debug:     Diagnostics that trace the runtime linking of an application can be
debug:     enabled to stderr by using the environment variable setting:
debug:          LD_DEBUG=token1,token2 app ...
debug:     Alternatively, the diagnostics can be redirected to an output file
debug:     using the additional environment variable:
debug:          LD_DEBUG_OUTPUT=file
debug:     The output file is given the specified name and the process id as a
debug:     suffix.
debug: 
                              < ..... >

Viewing dynamic executable dependencies

Large dynamically linked executables can have a LOT of dependencies, which are resolved by the runtime linker when a program is executed. To see which libraries an executable and the executable’s shared dependencies depend on, the ldd utility can be used:

$ ldd /usr/sbin/metastat

        libmeta.so.1 =>  /lib/libmeta.so.1
        libc.so.1 =>     /lib/libc.so.1
        libnsl.so.1 =>   /lib/libnsl.so.1
        libadm.so.1 =>   /lib/libadm.so.1
        libdevid.so.1 =>         /lib/libdevid.so.1
        libgen.so.1 =>   /lib/libgen.so.1
        libefi.so.1 =>   /lib/libefi.so.1
        libdevinfo.so.1 =>       /lib/libdevinfo.so.1
        libscf.so.1 =>   /lib/libscf.so.1
        libmp.so.2 =>    /lib/libmp.so.2
        libmd5.so.1 =>   /lib/libmd5.so.1
        libuuid.so.1 =>  /lib/libuuid.so.1
        libnvpair.so.1 =>        /lib/libnvpair.so.1
        libdoor.so.1 =>  /lib/libdoor.so.1
        libuutil.so.1 =>         /lib/libuutil.so.1
        libsocket.so.1 =>        /lib/libsocket.so.1
        libm.so.2 =>     /lib/libm.so.2
        /platform/SUNW,Ultra-5_10/lib/libc_psr.so.1
        /platform/SUNW,Ultra-5_10/lib/libmd5_psr.so.1

To view just the shared libraries that the executable depends on, the elfdump “-d” (dump the contents of the dynamic symbol table) option can be used:

$ /usr/ccs/bin/elfdump -d /usr/sbin/metastat | grep NEEDED

       [0]  NEEDED           0x867             libmeta.so.1
       [1]  NEEDED           0x884             libc.so.1

In the examples above, metastat only requires two libraries, but those two libraries have numerous dependencies.

Linker search paths

As part of my job as a systems administrator, I occassionally need to develop scripts to start applications at system bootstrap. Periodically while developing these scripts I will encounter an application that relies on libraries in obscure locations. To allow my scripts to work with these applications, I typically need to set LD_LIBRARY_PATH so the runtime linker can locate the libraries needed to make the application work ( I say typically since setrpath can be used on occassion ). Since the Solaris linker searches for libraries in the following order:

1. Check for libraries by traversing the directories in the LD_LIBRARY_PATH environment variable

2. Check for libraries by traversing the directories in the executables’s RPATH

3. Check for libraries by traversing /lib and /usr/lib

It is usually unnecessary to explicitly add /lib and /usr/lib to the search path. To see this first hand, the ldd utility can be invoked with the “-s” (display search path) option and an executable to process:

$ ldd -s slapd

   find object=libdb-4.3.so; required by /usr/local/openldap-2.2.26/libexec/slapd
    search path=/lib:/usr/lib  (default)
    trying path=/lib/libdb-4.3.so
    trying path=/usr/lib/libdb-4.3.so
        libdb-4.3.so =>  (file not found)

   find object=libsasl.so.1; required by /usr/local/openldap-2.2.26/libexec/slapd
    search path=/lib:/usr/lib  (default)
    trying path=/lib/libsasl.so.1
    trying path=/usr/lib/libsasl.so.1
        libsasl.so.1 =>  /usr/lib/libsasl.so.1
  
   [ ..... ]

Each stanza contains the name of a library that the executable requires, along with the directories that are searched to locate the library. This is some useful stuff!

Viewing shared library search order

When debugging library search path problems, it is often useful to see which libraries are used, and the order in which they are accessed. This is easily accomplished with the Solaris ldd(1m) utilities “-s” option:

$ ldd -ss /usr/sfw/bin/wget |more

   find object=libsocket.so.1; required by /usr/sfw/bin/wget
    search path=/usr/local/lib  (LD_LIBRARY_PATH)
    trying path=/usr/local/lib/libsocket.so.1
    search path=/usr/lib  (default)
    trying path=/usr/lib/libsocket.so.1
        libsocket.so.1 =>        /usr/lib/libsocket.so.1

   find object=libnsl.so.1; required by /usr/sfw/bin/wget
    search path=/usr/local/lib  (LD_LIBRARY_PATH)
    trying path=/usr/local/lib/libnsl.so.1
    search path=/usr/lib  (default)
    trying path=/usr/lib/libnsl.so.1
        libnsl.so.1 =>   /usr/lib/libnsl.so.1

   find object=libc.so.1; required by /usr/sfw/bin/wget
    search path=/usr/local/lib  (LD_LIBRARY_PATH)
    trying path=/usr/local/lib/libc.so.1
    search path=/usr/lib  (default)
    trying path=/usr/lib/libc.so.1
        libc.so.1 =>     /usr/lib/libc.so.1

   find object=libnsl.so.1; required by /usr/lib/libsocket.so.1
    search path=/usr/local/lib  (LD_LIBRARY_PATH)
    trying path=/usr/local/lib/libnsl.so.1
    search path=/usr/lib  (default)
    trying path=/usr/lib/libnsl.so.1

   find object=libc.so.1; required by /usr/lib/libsocket.so.1
    search path=/usr/local/lib  (LD_LIBRARY_PATH)
    trying path=/usr/local/lib/libc.so.1
    search path=/usr/lib  (default)
    trying path=/usr/lib/libc.so.1

   find object=libdl.so.1; required by /usr/lib/libnsl.so.1
    search path=/usr/local/lib  (LD_LIBRARY_PATH)
    trying path=/usr/local/lib/libdl.so.1
    search path=/usr/lib  (default)
    trying path=/usr/lib/libdl.so.1
        libdl.so.1 =>    /usr/lib/libdl.so.1

   find object=libc.so.1; required by /usr/lib/libnsl.so.1
    search path=/usr/local/lib  (LD_LIBRARY_PATH)
    trying path=/usr/local/lib/libc.so.1
    search path=/usr/lib  (default)
    trying path=/usr/lib/libc.so.1

   find object=libmp.so.2; required by /usr/lib/libnsl.so.1
    search path=/usr/local/lib  (LD_LIBRARY_PATH)
    trying path=/usr/local/lib/libmp.so.2
    search path=/usr/lib  (default)
    trying path=/usr/lib/libmp.so.2
        libmp.so.2 =>    /usr/lib/libmp.so.2

   find object=libdl.so.1; required by /usr/lib/libc.so.1
    search path=/usr/local/lib  (LD_LIBRARY_PATH)
    trying path=/usr/local/lib/libdl.so.1
    search path=/usr/lib  (default)
    trying path=/usr/lib/libdl.so.1

   find object=libc.so.1; required by /usr/lib/libmp.so.2
    search path=/usr/local/lib  (LD_LIBRARY_PATH)
    trying path=/usr/local/lib/libc.so.1
    search path=/usr/lib  (default)
    trying path=/usr/lib/libc.so.1

   object=/usr/lib/libc.so.1; filter for /usr/platform/$PLATFORM/lib/libc_psr.so.1

   find object=/usr/platform/SUNW,Ultra-5_10/lib/libc_psr.so.1; required by /usr/lib/libc.so.1
        /usr/platform/SUNW,Ultra-5_10/lib/libc_psr.so.1

   object=/usr/lib/libdl.so.1; filter for /usr/lib/ld.so.1

I find this useful for finding references to outdated libraries, and libraries that are not located in standard locations (e.g., /usr/lib).

Printing the contents of a static library

While debugging a static linking problem this weekend, I needed to see which files were included in a static library. This can be accomplished with the ar(1) utilities “-t” (print table of contents) option:

$ ar -vt /usr/lib/libnsl.a | head 5
rw-rw-r--     0/     1   1752 Aug 13 10:16 2004 common.o
rw-rw-r--     0/     1   4868 Aug 13 10:16 2004 nsl_stdio_prv.o
rw-rw-r--     0/     1   3304 Aug 13 10:16 2004 des_crypt.o
rw-rw-r--     0/     1  25852 Aug 13 10:16 2004 des_soft.o
rw-rw-r--     0/     1  49088 Aug 13 10:17 2004 dial.o

If you need to extract a specific object file from a static library, you can invoke ar(1) with the “-x” option:

$ ar -vx /usr/lib/libnsl.a dial.o

$ ls -la dial.o
-rw-r--r--   1 root     other      49088 Apr 25 16:17 dial.o

In case your interested, “-v” forces the archiver to produce verbose output. I am somewhat saddened that Solaris 10 will no longer support static libraries (though I understand why).

C pre processor

I am knee deep in debugging another SEGFAULT issue, and was given a cool tip by my friend Clay this weekend. When you see lots of #ifdef, #ifndef, and #endif’s in a source file, you can see what the C pre-processor will produce by running the source through the C pre-processor command (cpp):

$ cat ack.c

#include

#ifdef SOMETHING
typedef int blah;
#endif

#ifdef SOMETHING_ELSE
typedef long foo
#endif

int main(void) {

int m = 5;

printf(“%d\n”,m);
}

$ cpp -DSOMETHING ack.c

[ … ]

typedef int blah;

int main(void) {

int m = 5;

printf(“%d\n”,m);
}

I removed all the standard includes to simplify the example. This is super useful!