Article 6489 of comp.unix.internals: Path: ornews.intel.com!ssd.intel.com!news.kei.com!sol.ctr.columbia.edu!spool.mu.edu!olivea!koriel!newscast.West.Sun.COM!news2me.EBay.Sun.COM!exodus.Eng.Sun.COM!appserv.Eng.Sun.COM!cyber!barts From: barts@cyber.Eng.Sun.COM (Bart Smaalders) Newsgroups: comp.unix.internals Subject: Re: Dynamic libraries do not give a second chance. Date: 27 Sep 1993 17:02:47 GMT Organization: Sun Microsystems Inc., Mountain View, CA, USA Lines: 258 Distribution: world Message-ID: References: <2060@yetti.UUCP> NNTP-Posting-Host: cyber In-reply-to: minas@cs.yorku.ca's message of 24 Sep 93 19:24:36 GMT In article <2060@yetti.UUCP> minas@cs.yorku.ca (Minas Spetsakis) writes: I have the following problem related to dynamic libraries on SunOs. I have a program that loads an object module in responce to a user's request. It uses dlopen, dlsym etc. The problem is that the user does not get a second chance if he does a mistake. For instance, if a function is undefined it just calls exit(). If the object module is not a dynamicly loadable module, the same. It typicaly prints a message like: ld.so: call to undefined procedure _sinh from 0xf76f21a8 The problem described here is that the shared library being loaded cannot find a function it depends on. A suggestion when building these libraries under Solaris 2.x is to specify all their dependancies. Note that if these dependancies are shared libraries they are _not_ linked in, but a reference to the library is stored so that it may be included when the dlopen is done. Here is a note from a coworker regarding shared libraries. It covers this in some detail (thanks Joe!): Building Well Behaved Shared Objects On Solaris 2.x [ This note assumes the reader already has a passing understanding of shared objects. For a good discussion on this topic take a look at the SunOS 5.x Linker and Libraries Manual (Part No. 801-2869-10). ] Shared objects (aka dynamic or shared libraries) have become an essential component of software development here at Sun, and while most engineers have a passing understanding of how they work relatively few know how to generate one, and even fewer know how to generate a well behaved one. What is a well behaved shared object? It is one that has the following characteristics: 1. It is self contained. 2. It is versioned. 3. It is shareable. Let's look at these characteristics in a bit more detail. 1. Self Contained Historically libraries have not been self contained. I.e. when you linked with a library you were forced to know what libraries it depended on and add those to your link line as well. This is bad for two reasons: 1. It is inconvenient to the library user. But more importantly 2. It exposes the library's implementation to the library user. For example an XView client should not need to know that libxview requires libolgx. XView clients don't make calls to libolgx, and at some point in the future XView may change its implementation and use something other than libolgx. If this happens previous link lines will no longer be correct. (As it turns out libxview is currently not self contained so we need to specify "-lolgx" on our application link lines. A bad thing). So any symbols a shared object requires should be resolved at its link time (the shared objects link time, not the executable's). Also a hint as to the location of these libraries should also be specified so the end user isn't required to set LD_LIBRARY_PATH to find them at runtime. There is one thing to note here. In the example above if my XView client was unusual and happened to make libolgx calls directly then I would still need to specify "-lolgx" on my link line even if XView was self contained. The loader insists that you explicitly specify any interfaces you use. You can't "inherit" them from libxview's dependency list since they are part of libxview's implementation, not its interface. 2. Versioned Since shared objects are loaded at run time the loader must be able to distinguish incompatible versions of a library. This is controlled by a version number associated with the shared object's name which is embedded in the shared object. You increment the version number any time you make an incompatible change to the library's interface. 3. Shareability There are a number of things you can do when writing a shared object to maximize shareability. These are covered in the Linker and Library Manual. There are a couple of things you can do at link time that are described below. So how do I build a well behaved shared object? You do it in 3 steps: Step 1: Compile the .c's into .o's. For example: cc -c -O -K pic -xstrconst -I/usr/openwin/include foo.c -o foo.o Two points here: -K pic (or -K PIC, see cc man page) produces position-independent code that is required for shared objects. -xstrconst Forces string literals into the text segment of the object file. This makes them shareable. Step 2: Link the .o's into the .so For example: cc -G -z text -z defs -h libfoo.so.2 -i -R/usr/openwin/lib -L/usr/openwin/lib -lc -lnsl -lX11 -o libfoo.so.2 Lots of stuff here: cc Use cc (not ld) to generate the shared object. This makes sure that the .init and .fini sections of the shared object are correctly initialized. For C++ use CC instead of ld. -G Tells ld to produce a shared object -z text Enforces shareability of the text segment. This generates an error if any relocations against the text segment remain. These relocations are bad because they force a copy-on-write at runtime of the *entire* text segment which eliminates shareability. What could trigger these relocations? The simple case is if you forget to specify -K pic when compiling the .o's. Another more subtle example is if you initialize a constant pointer function like this (thanks to Bart Smaalders for the example): typedef int (*fp)(); extern int foo(); const fp fun = & foo; In this case "-z text" would cause the link to fail because in a shared library, the address of foo is (likely) different for each process, hence fun cannot be declared const and placed in the text segment. If you don't specify "-z text" then the link would have worked, but parts of your .so would not be shareable and would need patching at runtime. -z defs Ensures the shared object is self contained by generating an error if any undefined symbols remain at the end of the link. -h libfoo.so.2 For versioning. "2" is the version number. This records "libfoo.so.2" in the shared object (in the SONAME field). Any executables that link with this shared object will require "libfoo.so.2" at runtime. -i Ignore LD_LIBRARY_PATH. Typically LD_LIBRARY_PATH is used to influence runtime behavior and -i prevents it from interfering with our link step. (It will still have effect at runtime). It's a good idea to use this when building executables as well. -R/usr/openwin/lib Specifies a search path for locating the libraries libfoo depends on at runtime. This is so end users don't have to set LD_LIBRARY_PATH at runtime. Use -R when building executables as well. The goal is for users to never have to set LD_LIBRARY_PATH if they install stuff in the default locations. This value is stored in the RPATH field of the shared object. -L/usr/openwin/lib For self containment. Specifies the libraries -lc -lnsl -lXll this shared object requires. '-z defs' makes sure we get this right. Step 3: Install the shared object. mcs -d libfoo.so.2 mcs -a "Whatever comment string you want to add" install -m 755 -f $DESTDIR/lib libfoo.so.2 ln -s ./libfoo.so.2 $DESTDIR/lib/libfoo.so mcs -d libfoo.so.2 Strips the rather lengthy .comment section from the shared object. This is to save disk space only. It has no effect on the runtime image. This should be done on executables too. mcs -a "..." Adds a short comment if you so desire. mcs -p will print the comment section. install -m 755 ... Copy shared object to the destination directory ln -s ./libfoo.so.2 ... Create a symbolic link that is the name of the library minus the version number. Why? Because when a user specifies "-lfoo" at link time the linker looks for "libfoo.so" -- no version number. But at runtime we need the name with the version number. That's it. You are done! Make sure you understand all of the above as your project may have different requirements. You can use dump(1) to peak inside a shared object to get all sorts of useful information like what other shared objects it depends on, its name, the run path, etc. (Don't confuse dump(1) with the 4.x backup utility of the same name). sidewinder$ dump -Lv libX11.so.4 libX11.so.4: **** DYNAMIC SECTION INFORMATION **** .dynamic : [INDEX] Tag Value [1] NEEDED libsocket.so.1 [2] NEEDED libnsl.so.1 [3] NEEDED libc.so.1 [4] NEEDED libdl.so.1 [5] NEEDED libw.so.1 [6] SONAME libX11.so.4 [7] RPATH /usr/openwin/lib/X11:/usr/openwin/lib [8] HASH 0x94 [9] STRTAB 0x52b4 [10] SYMTAB 0x1bc4 [11] STRSZ 0x3494 [12] SYMENT 0x10 [13] PLTGOT 0x60d10 [14] PLTSZ 0xef4 [15] PLTREL 0x7 [16] JMPREL 0xb85c [17] The LA 0x8748 [18] RELASZ 0x4008 [19] RELAENT 0xc