Shared libraries are the reason your system doesn't need a separate copy of printf() in every program. Instead, one copy of libc.so lives on disk, and every program shares it at runtime. This saves disk space, memory, and makes updating libraries possible without recompiling every program.
Understanding shared libraries explains why you see ".so" files, why "library not found" errors happen, and how versioning keeps old programs working.
A shared library is a position-independent ELF executable that can be loaded at any address. It's built with three key concepts:
This three-level naming lets you update a library (bump 1.2.3 to 1.2.4) without breaking programs linked against soname "libfoo.so.1".
Building a shared library:
# Compile with -fPIC (position-independent code) gcc -fPIC -c foo.c -o foo.o gcc -fPIC -c bar.c -o bar.o # Create the shared library gcc -shared -o libfoo.so.1.2.3 foo.o bar.o # Create versioned symlinks ln -s libfoo.so.1.2.3 libfoo.so.1 # soname ln -s libfoo.so.1 libfoo.so # linker name # The soname is embedded in the library readelf -d libfoo.so.1.2.3 | grep SONAME # 0x000000000000000e (SONAME) Library soname: [libfoo.so.1]
At compile time, the linker reads the soname from the library and stores it in your program's .dynamic section:
gcc -o myprog myprog.c -L. -lfoo readelf -d myprog | grep NEEDED # 0x0000000000000001 (NEEDED) Shared library: [libfoo.so.1] # Note: it says libfoo.so.1, NOT libfoo.so.1.2.3
At runtime, ld.so searches for "libfoo.so.1" and follows the symlink chain to find the actual file.
$ ls -la /usr/lib/x86_64-linux-gnu/libc.so*
lrwxrwxrwx 1 root root 14 libc.so.6 symlink libc.so.6
$ readelf -d /usr/lib/x86_64-linux-gnu/libc.so.6 | grep SONAME
0x000000000000000e (SONAME) Library soname: [libc.so.6]
# The linker name (for gcc -lc) is a separate file:
$ file /usr/lib/x86_64-linux-gnu/libc.so
/usr/lib/x86_64-linux-gnu/libc.so: ASCII text
$ cat /usr/lib/x86_64-linux-gnu/libc.so
OUTPUT_FORMAT(elf64-x86-64)
GROUP ( /usr/lib/x86_64-linux-gnu/libc.so.6
/usr/lib/x86_64-linux-gnu/libc_nonshared.a
AS_NEEDED ( /usr/lib/x86_64-linux-gnu/libdl.so.2 ) )On Debian/Ubuntu, the linker name is actually a text script that specifies which real libraries to link. This is how the linker resolves -lc to the correct libc version.
# See the soname of any shared library readelf -d /usr/lib/x86_64-linux-gnu/libc.so.6 | grep SONAME # Build your own shared library # mylib.c: int my_func(int x) returns x * 2 gcc -fPIC -shared -Wl,-soname,libmylib.so.1 -o libmylib.so.1.0 mylib.c ln -s libmylib.so.1.0 libmylib.so.1 ln -s libmylib.so.1 libmylib.so # Verify soname readelf -d libmylib.so.1.0 | grep SONAME # See library search order ldconfig -p | head -10
Why does ELF use a three-level naming scheme (real name, soname, linker name)?
You upgrade libfoo.so.1.2.3 to libfoo.so.1.2.4. Do you need to recompile programs that use libfoo?
Shared libraries are found and loaded by the dynamic linker (ld.so). The next concept covers how ld.so searches for libraries, processes relocations, and sets up the runtime environment.