| Author: | John M. Gabriele |
|---|---|
| Date: | May 2007 |
| Back to: | homepage |
[I originally wrote this article for the LinuxQuestions Wiki but also wanted to keep a pristine html copy here for myself. :) Enjoy.]
A software developer can write code that doesn't necessarily compile into a standalone program. Instead, a source code file (or a related group of files) can be compiled into a reusable compartmentalized module called a library. A given library is one file, for example: /usr/lib/libSDL-1.2.so.0.0.4. Here's what the file command has to say about that particular library on my system:
bash$ file /usr/lib/libSDL-1.2.so.0.0.4
/usr/lib/libSDL-1.2.so.0.0.4: ELF 32-bit LSB shared object,
Intel 80386, version 1 (SYSV), stripped
Elsewhere, when another software developer wants to create an executable program, they almost never write the whole thing themselves -- instead, they leverage existing ready-to-use libraries and link their code to the already-built library.
A program can be linked to a library forming one big executable, but that wastes disk space and memory since many programs on the same system might want to use the same library. That's called static linking and is only used very sparingly these days. For example, static linking might be used if some program uses a heavily modified library that it specifically requires but that no other programs would want to use. Libraries used this way are called static libraries and have the .a filename extension.
The modern and customary way to use libraries is for a program to request (from the operating system) a library (or libraries) when the program starts up -- that is, at runtime. Then, the program's code dynamically links to the library code right then-and-there just when it needs the library. These types of libraries are referred to as "dynamically linked libraries" or "shared libraries". On GNU/Linux, we sometimes call them shared objects, since their file names have the extension .so (usually plus some version numbers). On MS Windows they're called "DLL's". On Mac OS X, shared lib filenames end in .dylib.
The rest of this article discusses how a program gets linked to the shared objects it needs at runtime, and the various system executables and files that make this possible.
Shared libraries have names like libGL.so.1.2. In this example, that's the library which implements OpenGL on my Debian Stable system. It's located in /usr/X11R6/lib, but there is a symlink to it in /usr/lib, along with some other symlinks:
libGL.so --> libGL.so.1.2 libGL.so.1 --> libGL.so.1.2 libGL.so.1.2 --> ../X11R6/lib/libGL.so.1.2
The "1" indicates major revision 1, and "2" indicates minor revision 2. All libraries with the same major revision number should present the same API to users of that library -- that is, if you can compile your code against libFoo.so.2.4, you should be able to compile it against libFoo.so.2.32 without any changes. Changes in major revision number indicate a change in the external interface presented to users of that library. Changes in minor revision number indicate bug fixes. :)
You can easily find out which shared libs a given app (or even another shared lib) requires using the ldd command. For example, for the glxgears program:
bash$ ldd /usr/X11R6/bin/glxgears
libGL.so.1 => /usr/X11R6/lib/libGL.so.1 (0x4001a000)
libXext.so.6 => /usr/X11R6/lib/libXext.so.6 (0x40080000)
libX11.so.6 => /usr/X11R6/lib/libX11.so.6 (0x4008e000)
libpthread.so.0 => /lib/libpthread.so.0 (0x40168000)
libm.so.6 => /lib/libm.so.6 (0x4017c000)
libc.so.6 => /lib/libc.so.6 (0x4019d000)
libdl.so.2 => /lib/libdl.so.2 (0x402ba000)
/lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)
ldd first asks the object file what libs it will need to run, then for each of those asks the operating system where it plans on finding them. On Mac OS X, the ldd command is spelled otool -L.
Now, a properly built shared object (in ELF format, which is the format GNU/Linux uses) will have a soname built into it. This is a name that is specified at the library's build-time, to GCC's link editor (ld) with the -soname option. You can inspect any sensibly-built library's soname like so:
bash$ objdump -p /usr/X11R6/lib/libGL.so.1.2 | grep SONAME SONAME libGL.so.1
In this case, libGL.so.1.2's soname is "libGL.so.1". The point of the soname is for the creator of the library to be able to provide version compatibility information to the system. In this case, libGL.so.1.2's soname says that libGL.so.1.2 is compatible with any program that says it needs libGL.so.1.
Programs know what libs they need to link to at runtime -- they know the soname's they want. When an OpenGL program starts up and asks the system for libGL.so.1, the system delivers libGL.so.1.2. (Recall, this is on my system. Your system may (I would hope :) have a more recent version of OpenGL installed.)
Note, at build-time, when you link a program that requires libFoo.so, you specify -lFoo as an option to the link editor on the g++ command line. What's going on at that time is this:
You tell g++ you want to set things up so your app makes use of libFoo.so at runtime.
After compiling, g++ passes this information to its link editor ld.
ld pokes around in /lib, /usr/lib, and anywhere else you specified for it to look using the -L g++ option, and then comes up with, say, libFoo.so.1.22.
If you have multiple incompatible libFoo.so's on your system (say, for example libFoo.so.1.22 and libFoo.so.2.0.1), either be sure to use the -L option, or else specify the lib's location explicitly (as in /usr/local/lib/libFoo.so.2.0.1 (note, no -l option is needed here)).
Your app links to this specific version of libFoo.so, and has libFoo.so's soname string embedded into itself. The reason for this will be obvious in a moment (keep reading :).
A final note: if you are writing your own shared library (see instructions below), remember to properly use the -soname option (for example, -soname=libFoo.so.2).
Above, we noted how shared libs have symlinks with names differing only by version number:
libGL.so --> libGL.so.1.2 libGL.so.1 --> libGL.so.1.2 libGL.so.1.2 --> ../X11R6/lib/libGL.so.1.2
The user doesn't make these symlinks manually. Rather, the program /sbin/ldconfig takes care of it for us. When you run ldconfig it does for us the following:
When you run a program, a series of events happen culminating in the app being loaded into memory as well as any shared objects the program needs which weren't already loaded. Here's a walkthrough of those events:
Of course, if you had 2 incompatible versions of libFoo.so on your system -- say, libFoo.so.1.4 and libFoo.so.2.0.1 -- each would have a different soname: "libFoo.so.1" and "libFoo.so.2". As such, ldconfig and ld.so would be able to tell the difference between the two, and you could have them both installed on your system simultaneously. That is, applications that depend on "libFoo.so" being installed would always get the right one at runtime.
Debian users can safely skip this section. ;) If you properly install a debian package using a tool like apt-get, any required configuration is taken care of for you. Of course, on the other hand, if you are creating a package yourself you'll need to be doing the configuration for your users, as part of the install script. :)
If you do need to install your shared libs by-hand:
If you're a programmer and want to create your own library (from, say, files foo.cpp and bar.cpp -- and maybe link it to some main.o), the compiler and linker commands you're looking to put into your makefile are:
For a shared library:
g++ -fPIC -c foo.cpp bar.cpp g++ -shared -o libFooBar.so.1.2 -Wl,-soname="libFooBar.so.1" foo.o bar.o g++ -o your_app main.o -L. -lFooBar
For a static library (or "archive"):
ar -r libFooBar.a foo.o bar.o gcc -o your_app main.o libFooBar.a