Shared Library Major Version Upgrade Creating a new major version, updating sonam

 

Shared Library Major Version Upgrade
TLPI Chapter 41 ยท Section 41.9 โ€” Creating a new major version, updating soname & linker name symlinks
๐Ÿ“ฆ
Major Version
๐Ÿ”—
Symlinks
โš™๏ธ
ldconfig

Key Concepts
Major Version Minor Version soname symlink linker name symlink ldconfig ABI Compatibility libdemo.so.2.0.0

What is a Major Version Upgrade?

Shared libraries use a three-number versioning scheme: major.minor.patch. For example, libdemo.so.1.0.2 has major=1, minor=0, patch=2.

A major version increment (e.g., 1.x โ†’ 2.0.0) signals ABI-breaking changes โ€” the new library is NOT backward compatible with programs compiled against the old one. Both versions can coexist on the same system because they have different sonames (libdemo.so.1 vs libdemo.so.2).

Key Rule: Running programs keep using the old version. Only after they are restarted will they pick up the new version (via the updated soname symlink).

The Three Symlink Levels (Inline Diagram)

Every shared library on a Linux system has three name levels. Understanding them is critical before you can do version upgrades.

Name Type Example Who Creates It Purpose
Real name libdemo.so.2.0.0 Developer (gcc) Actual .so file on disk
soname libdemo.so.2 โ†’ libdemo.so.2.0.0 ldconfig (auto) Dynamic linker uses this at runtime
Linker name libdemo.so โ†’ libdemo.so.2 Developer (manual) Used during gcc -ldemo at compile time

libdemo.so
(linker name)
โ†’
libdemo.so.2
(soname)
โ†’
libdemo.so.2.0.0
(real file)

Step-by-Step: Creating a New Major Version (2.0.0)

Here is the complete sequence of commands to release a new major version of a shared library alongside the existing one.

Step 1: Compile the source files with -fPIC (Position Independent Code)

# Compile all modules for the new major version
gcc -g -c -fPIC -Wall mod1.c mod2.c mod3.c

Step 2: Link the shared library with the new soname

# Create the new shared library with soname = libdemo.so.2
gcc -g -shared -Wl,-soname,libdemo.so.2 \
    -o libdemo.so.2.0.0 \
    mod1.o mod2.o mod3.o
The -Wl,-soname,libdemo.so.2 flag embeds the soname inside the .so file. The dynamic linker reads this soname at runtime, not the filename.

Step 3: Install the new library into /usr/lib

mv libdemo.so.2.0.0 /usr/lib

Step 4: Run ldconfig to auto-create soname symlink

# ldconfig scans /usr/lib and creates soname symlinks automatically
ldconfig -v | grep libdemo
# Output:
#   libdemo.so.1 -> libdemo.so.1.0.2
#   libdemo.so.2 -> libdemo.so.2.0.0  (changed)
ldconfig automatically creates libdemo.so.2 โ†’ libdemo.so.2.0.0. You do NOT need to create this manually.

Step 5: Manually update the linker name symlink

cd /usr/lib
# Point the linker name to the NEW major version soname
ln -sf libdemo.so.2 libdemo.so
This step is manual! ldconfig does NOT update the linker name symlink. If you skip this, new compilations with gcc -ldemo will still link against version 1.

Coding Example 1: Full Major Version Upgrade Script

A shell script that automates building and installing both old and new major versions side by side.

#!/bin/bash
# demo_version_upgrade.sh
# Demonstrates creating v1 and v2 of a shared library side by side

set -e

mkdir -p /tmp/libdemo_demo
cd /tmp/libdemo_demo

# ---- Create source files ----
cat > mod1.c << 'EOF'
#include <stdio.h>

// v2 has a NEW function signature (ABI break)
void mod1_hello(const char *name, int times) {
    for (int i = 0; i < times; i++)
        printf("[mod1 v2] Hello, %s!\n", name);
}
EOF

cat > mod2.c << 'EOF'
#include <stdio.h>
void mod2_info(void) {
    printf("[mod2 v2] Library version 2.0.0\n");
}
EOF

cat > main.c << 'EOF'
#include <stdio.h>
void mod1_hello(const char *name, int times);
void mod2_info(void);

int main(void) {
    mod1_hello("Linux", 3);
    mod2_info();
    return 0;
}
EOF

echo "=== Compiling object files ==="
gcc -g -c -fPIC -Wall mod1.c mod2.c

echo "=== Building libdemo.so.2.0.0 with soname libdemo.so.2 ==="
gcc -g -shared -Wl,-soname,libdemo.so.2 \
    -o libdemo.so.2.0.0 mod1.o mod2.o

echo "=== Checking the embedded soname ==="
readelf -d libdemo.so.2.0.0 | grep SONAME
# Expected: (SONAME)  Library soname: [libdemo.so.2]

echo "=== Creating soname symlink manually ==="
ln -sf libdemo.so.2.0.0 libdemo.so.2

echo "=== Creating linker name symlink ==="
ln -sf libdemo.so.2 libdemo.so

echo "=== Building the program ==="
gcc -g -Wall -o prog main.c -L. -ldemo -Wl,-rpath,.

echo "=== Running the program ==="
./prog

echo "=== ldd output ==="
ldd prog

echo "Done!"
Run this script: bash demo_version_upgrade.sh โ€” it compiles, links, and runs everything in /tmp/libdemo_demo.

Coding Example 2: Verifying soname is Embedded Correctly

Use readelf and objdump to inspect what version information is actually baked into a shared library.

#!/bin/bash
# inspect_soname.sh โ€” tools to verify soname embedding

LIB="libdemo.so.2.0.0"

echo "=== Method 1: readelf -d ==="
readelf -d $LIB | grep -E 'SONAME|NEEDED'
# SONAME tells the dynamic linker which symlink to look for

echo ""
echo "=== Method 2: objdump -p ==="
objdump -p $LIB | grep SONAME

echo ""
echo "=== Method 3: objdump -p on executable ==="
# After linking prog against this library:
objdump -p prog | grep NEEDED
# You should see: NEEDED libdemo.so.2
# This means prog will look for the soname symlink, not the real filename

echo ""
echo "=== Show all symlinks in current directory ==="
ls -la libdemo* 2>/dev/null | awk '{print $9, $10, $11}'
# Should show:
# libdemo.so -> libdemo.so.2
# libdemo.so.2 -> libdemo.so.2.0.0
# libdemo.so.2.0.0  (real file)

Coding Example 3: Old Programs Keep Using Old Version

This example shows how a running program continues to use the old library even after a new version is installed. You need to understand how the dynamic linker resolves shared libraries at process startup.

/* long_runner.c โ€” simulates a long-running process */
#include <stdio.h>
#include <unistd.h>

// Imagine this is in libdemo.so.1
void mod1_greet(void);  // old v1 signature

int main(void) {
    printf("Process PID = %d started. Using libdemo.so.1\n", getpid());

    for (int i = 0; i < 5; i++) {
        mod1_greet();
        sleep(2);
    }

    printf("Process finished. Still used v1 throughout its life.\n");
    return 0;
}

/*
 * EXPLANATION:
 * ------------
 * When this process was STARTED, the dynamic linker resolved
 * "libdemo.so.1" (from the NEEDED entry) to libdemo.so.1.0.2
 * via the soname symlink. That resolution is LOCKED for the
 * process lifetime.
 *
 * Even if you later:
 *   ldconfig  (which creates libdemo.so.1 -> libdemo.so.1.2.0)
 * ...this running process is NOT affected.
 *
 * Only NEW processes started after ldconfig picks up the update.
 *
 * Compile: gcc -o long_runner long_runner.c -ldemo_v1 -Wl,-rpath,.
 */
Key Insight: The dynamic linker resolves libraries when a process starts (or when dlopen() is called). After that, the resolved file handles are kept open. Updating symlinks has no effect on already-running processes.

System State: Before vs After Major Upgrade
Before Upgrade (v1 only)
/usr/lib/
libdemo.so.1.0.2 ย ย ย โ† real file
libdemo.so.1 โ†’ libdemo.so.1.0.2 ย โ† soname
libdemo.so ย โ†’ libdemo.so.1 ย ย ย ย โ† linker name
After Upgrade (v1 + v2 coexist)
/usr/lib/
libdemo.so.1.0.2 ย ย ย โ† v1 real file (still here!)
libdemo.so.1 โ†’ libdemo.so.1.0.2 โ† v1 soname
libdemo.so.2.0.0 ย ย ย โ† v2 real file (new)
libdemo.so.2 โ†’ libdemo.so.2.0.0 โ† v2 soname (new, by ldconfig)
libdemo.so ย โ†’ libdemo.so.2 ย ย ย ย โ† linker name updated manually

Interview Questions
Q1. What is the difference between a minor version upgrade and a major version upgrade of a shared library?
A minor version upgrade is backward-compatible โ€” existing programs linked against the old minor version will work with the new one because the ABI (Application Binary Interface) is unchanged. The soname stays the same (e.g., libdemo.so.1), so ldconfig just updates the soname symlink to point to the new real file. A major version upgrade breaks ABI compatibility (changed function signatures, removed symbols, etc.), so a new soname is introduced (e.g., libdemo.so.2). Both major versions can coexist on the system.
Q2. Which symlink does ldconfig create automatically, and which one must you create manually?
ldconfig automatically creates and maintains the soname symlink (e.g., libdemo.so.2 โ†’ libdemo.so.2.0.0). The linker name symlink (e.g., libdemo.so โ†’ libdemo.so.2) must be created and updated manually by the package maintainer using ln -sf.
Q3. Why don’t already-running programs switch to a new library version automatically after ldconfig is run?
The dynamic linker resolves shared library references when a process starts. At that point, it opens the real library file and holds an open file descriptor to it. Even if symlinks are updated later, the running process still has its file descriptor to the old real file. Only when the process terminates and is restarted does the dynamic linker re-resolve library names, at which point it follows the updated symlinks.
Q4. What flag do you pass to gcc when creating a shared library to embed its soname?
You use the -Wl,-soname,libname.so.MAJOR flag. The -Wl, prefix passes the following comma-separated argument to the linker. Example: gcc -shared -Wl,-soname,libdemo.so.2 -o libdemo.so.2.0.0 mod1.o mod2.o. Without this flag, the soname is not embedded and the dynamic linker uses the filename directly, which breaks versioning.
Q5. Can two major versions of the same shared library coexist on a Linux system? How?
Yes. They coexist because they have different real filenames (libdemo.so.1.x.y and libdemo.so.2.0.0) and different soname symlinks (libdemo.so.1 and libdemo.so.2). When a program is compiled, its NEEDED entry records the soname (e.g., libdemo.so.1). At runtime, the dynamic linker follows exactly that soname symlink. So program A using v1 and program B using v2 both work correctly on the same system.
Q6. How do you verify what soname is embedded inside a shared library?
Use either readelf -d libdemo.so.2.0.0 | grep SONAME or objdump -p libdemo.so.2.0.0 | grep SONAME. These inspect the ELF dynamic section of the library and display the embedded soname string.
Q7. What does the -v flag of ldconfig show?
The -v (verbose) flag makes ldconfig print all the directories it scans and all the soname symlinks it creates or updates, along with a note of which ones changed. This is useful for verifying that a newly installed library was picked up and that its soname symlink was created correctly.

Leave a Reply

Your email address will not be published. Required fields are marked *