5-Oct-2017

Exactly how one goes about building .so files* isn’t widely understood, and the documentation on it is overcomplicated IMHO. So, I figure the web needs a tutorial demonstrating how to build an extremely simple.so file. Et voilà, here’s a step by step guide for building a .so file on Linux or a similarly flavored Unix using a GNU-compatible toolchain.

We’re going to make two .o files, “call_me.o” and “die.o”, and combine them into a single “call_me_and_die” shared library. Making a library is very much like making an executable, just with some tweaks to the build commands and some extra steps at the end. To make our “call_me_and_die” library, we start the way we would with a “call_me_and_die” executable: writing a .h file for the common function signatures. File so_tut.h:

extern void call_me(void);
extern void die_die_die(void);

We need to have implementations of these functions, of course. File call_me.c:

Now that that’s out of the way, let’s get building!
Building the object files for the shared library is the same as building the object files for a single executable, except that the extra argument “-fPIC” is provided. This tells the compiler to emit “position independent code” (a.k.a. “relocatable code”).$ cc -Wall -fPIC -o call_me.o -c call_me.c
$ cc -Wall -fPIC -o die.o -c die.c

Now that we have the two .o files, we can link them together into a shared object. This is the same as linking the code together into a single executable, except that you use the extra argument “-shared” to tell the linker to make a shared object.$ cc -shared call_me.o die.o -o libcall_me_and_die.so.0.0.0 -Wl,--soname=libcall_me_and_die.so.0

You’ll also want to have that weird -Wl,--soname thing at the end, which is important for .so file versioning. which I’ll get to later.

All right, we have this shared object file now, so what do we do with it? Normally, one would install such a thing into the file system someplace like /usr/lib or /usr/local/lib. For this demonstration, we’ll just create a “lib” subdirectory right in our current directory to use. This is just like how we’d install an executable, except it goes in a directory named “lib”, instead of “bin” where the executables go.$ install -d lib
$ install libcall_me_and_die.so.0.0.0 lib

If we were installing an executable, we’d be done at this point, but shared library installs have two more steps.$ ldconfig -n lib
$ ln -s libcall_me_and_die.so.0.0.0 lib/libcall_me_and_die.so

The ldconfig program will look inside the library to find the “soname” specified with that weird -Wl,--soname compiler flag, and create a symlink from the soname to the library. This is used in running programs which link to the library. The ln command manually creates a symlink from “libcall_me_and_die.so” to the library. This is used in building programs which link to the library.

To understand that distinction, let’s create a tiny program which links to the call_me_and_die library and only calls its two functions. File demo.c:

Uh-oh, what happened? Well, we put our shared library into the “lib” subdirectory of the current directory, and the linker doesn’t know to look there. We can tell it to look there by setting the LPATH environment variable:$ export LPATH=$PWD/lib
$ cc demo.o -o so_tut -lcall_me_and_die

Great, that built! Time to run our demo:$ ./so_tut./so_tut: error while loading shared libraries: libcall_me_and_die.so.0: cannot open shared object file: No such file or directory

Hey, we set LPATH, so what gives? Well, LPATH controls where the build looks for libraries, but when the executable itself runs and looks for the libraries, it doesn’t use LPATH, it instead uses LD_LIBRARY_PATH.$ export LD_LIBRARY_PATH=$PWD/lib
$ ./so_tutMy line!

It’s alive!

(You won’t need to use either of those environment settings for any library installed in the system’s library directories, such as /usr/lib.)

Now that we have the demo working, let’s talk about .so file versioning. Especially careful readers may have noticed that the link line to build the so_tut binary didn’t specify the .0 extension, but the error message from so_tut when we tried to run it did. Where’d that come from? When one links a shared library into a program, the linker uses the library’s “soname” to determine what filename the executable should use to find the library — even though that’s different from the filename the linker is actually using!

The reason for that is that shared libraries are supposed to be updated without imposing a need to re-link the executables which link against them. Thus the so_tut program is supposed to work as-is if libcall_me_and_die.so.0.0.0 is uninstalled and replaced with libcall_me_and_die.so.0.1.2. That will break if so_tut looks for libcall_me_and_die.so.0.0.0, hence instead it looks for libcall_me_and_die.so.0.0.0’s soname, libcall_me_and_die.so.0. Because ldconfig symlinks sonames to library filenames, it redirects the libcall_me_and_die.so.0 symlink to libcall_me_and_die.so.0.1.2 when it’s run as part of the libcall_me_and_die.so.0.1.2 install, and so_tut continues to work without having to be re-linked.

However, libraries often go through changes which are drastic enough that they can’t support being called through their prior interface, and so require any programs linked against them to be rebuilt in order to work with the new library version. To support that, the library should update its soname (e.g., to libcall_me_and_die.so.1), which prevents any older executables from trying to load the new, incompatible library, because those older executables will only be looking for the old soname.

[back] * shared objects are the Unix equivalent of .DLL files** on Windows.