Summary & Review Fundamentals of Shared Libraries

 

Chapter 41 — Summary & Review
Fundamentals of Shared Libraries — Complete Chapter Recap with Quick Reference
4
Sections Covered
2
Library Types
12+
Interview Q&As

Chapter 41 — Sections Covered in This Series
11
Finding Shared Libraries at Run Time
Dynamic linker search order, DT_RPATH, LD_LIBRARY_PATH, DT_RUNPATH, ldconfig, $ORIGIN
12
Run-Time Symbol Resolution
Symbol interposition, executable-wins rule, -Bsymbolic, LD_PRELOAD, weak symbols
13
Using Static Libraries Instead of Shared
When to choose static, ar command, -static, -Wl,-Bstatic/-Bdynamic selective linking
14
Chapter Summary (this page)
Full recap, command reference table, comprehensive interview questions

What Chapter 41 Covers — The Big Picture

Chapter 41 covers the fundamentals of shared libraries on Linux. Two types of object libraries exist on Linux:

📦 Static Libraries (.a)

Archive of compiled .o files. The linker copies needed code directly into the executable. Result: self-contained binary, no run-time dependency, but larger binary size.

🔗 Shared Libraries (.so)

Single copy loaded into memory at run time, shared among all programs that need it. Smaller executables, easy updates, but requires the .so to exist on the system.

The chapter explains how to build, install, version, and use shared libraries, and covers important advanced topics like the dynamic linker’s search algorithm and symbol resolution semantics.

Section-by-Section Key Points

§41.11 — Finding Shared Libraries at Run Time

  • The dynamic linker (ld-linux.so) finds and loads shared libraries when a program starts.
  • If a dependency string contains a slash → it’s used as a direct path (no searching).
  • Otherwise: search order is DT_RPATH → LD_LIBRARY_PATH → DT_RUNPATH → /etc/ld.so.cache → /lib → /usr/lib.
  • LD_LIBRARY_PATH is ignored for SUID/SGID programs (security).
  • $ORIGIN in rpath expands to the directory of the executable at run time — enables portable “turn-key” applications.
  • ldconfig rebuilds /etc/ld.so.cache — must be run after installing a new library.

§41.12 — Run-Time Symbol Resolution

  • Global symbols in the main executable override same-named symbols in shared libraries (symbol interposition).
  • Between multiple libraries, left-to-right order on the link command line determines priority.
  • These semantics mirror static library behavior (for historical compatibility).
  • -Bsymbolic makes a library bind its own internal symbol references to its own definitions.
  • LD_PRELOAD enables intentional interposition for testing and debugging.
  • Weak symbols (__attribute__((weak))) allow overridable default implementations.

§41.13 — Using Static Libraries

  • When both .so and .a exist, the linker prefers .so by default.
  • Three ways to force static: explicit .a path | -static flag | -Wl,-Bstatic/-Bdynamic toggles.
  • -Wl,-Bstatic/-Wl,-Bdynamic allows mixing static and shared libraries selectively.
  • Static linking is appropriate when the runtime environment lacks shared libraries or ABI stability is critical.

Complete Command Quick Reference
Command / Flag Purpose
gcc -fPIC -shared -o libfoo.so foo.c Build a shared library
ar rcs libfoo.a foo.o Build a static library
gcc -o prog main.c -L. -lfoo Link against a library (shared preferred)
gcc -Wl,-rpath,/path/to/libs Embed DT_RPATH in the executable
gcc -Wl,–enable-new-dtags,-rpath,/path Embed DT_RUNPATH instead of DT_RPATH
-Wl,-rpath,’$ORIGIN/lib’ Portable rpath relative to executable location
export LD_LIBRARY_PATH=/path Runtime library search path (env var)
sudo ldconfig Rebuild /etc/ld.so.cache after installing a library
ldconfig -p | grep libname Check if library is in the cache
ldd ./prog Show runtime library dependencies
readelf -d prog | grep -E “RPATH|RUNPATH|NEEDED” Inspect ELF dynamic section
LD_DEBUG=libs ./prog Trace library search at runtime
LD_PRELOAD=./mylib.so ./prog Preload a library (override symbols)
gcc -shared -Wl,-Bsymbolic Make library bind its own internal symbols
gcc -static Link everything statically
gcc -Wl,-Bstatic -lfoo -Wl,-Bdynamic Selectively link libfoo statically

Comprehensive Example — From Source to Deployed Application

This end-to-end example builds a shared library, installs it, and shows all the concepts together.

/* ===== libstring.c — our shared library ===== */
#include <string.h>
#include <ctype.h>

/* Count vowels in a string */
int count_vowels(const char *s) {
    int count = 0;
    while (*s) {
        char c = tolower((unsigned char)*s);
        if (c=='a'||c=='e'||c=='i'||c=='o'||c=='u') count++;
        s++;
    }
    return count;
}

/* Reverse a string in-place */
void reverse_str(char *s) {
    int len = strlen(s);
    for (int i = 0, j = len-1; i < j; i++, j--) {
        char tmp = s[i]; s[i] = s[j]; s[j] = tmp;
    }
}

/* Weak symbol — can be overridden by application */
__attribute__((weak)) void on_error(const char *msg) {
    fprintf(stderr, "[libstring default error] %s\n", msg);
}
/* ===== app.c — the application ===== */
#include <stdio.h>
#include <string.h>

int count_vowels(const char *s);
void reverse_str(char *s);

/* Override the weak symbol from the library */
void on_error(const char *msg) {
    fprintf(stderr, "[APP] Error: %s\n", msg);
}

int main(void) {
    char buf[] = "Hello EmbeddedPathashala";
    printf("Vowels in '%s': %d\n", buf, count_vowels(buf));
    reverse_str(buf);
    printf("Reversed: %s\n", buf);
    return 0;
}
# ===== Build and deploy script =====

# 1. Compile shared library
gcc -fPIC -shared -o libstring.so.1 libstring.c
ln -sf libstring.so.1 libstring.so

# 2. Build app with $ORIGIN rpath (portable)
gcc -o app app.c -L. -lstring -Wl,-rpath,'$ORIGIN'

# 3. Check dependencies
ldd ./app
# libstring.so.1 => ./libstring.so.1

# 4. Run — works from current directory (rpath=$ORIGIN)
./app
# Vowels in 'Hello EmbeddedPathashala': 10
# Reversed: alahsahtaPdeddebmE olleH

# 5. System-wide installation alternative:
sudo cp libstring.so.1 /usr/local/lib/
sudo ldconfig
ldconfig -p | grep libstring
# libstring.so.1 => /usr/local/lib/libstring.so.1

# 6. Verify binary details
readelf -d app | grep -E "NEEDED|RPATH|RUNPATH"
file app

Comprehensive Chapter Review — Interview Questions
Q1. What are the two types of object libraries in Linux? Compare them.
Static libraries (.a files): archives of object files; code is copied into the executable at link time; binary is self-contained and portable but large. Shared libraries (.so files): loaded at run time from a separate file; all programs share one memory copy; smaller binaries, easy updates, but requires the .so to be present and findable at run time.
Q2. What is the role of the dynamic linker?
The dynamic linker (ld-linux.so) is invoked automatically when a dynamically linked executable is run. It: locates all required shared libraries using its search rules, loads them into the process’s address space, relocates all addresses (applies fixups), resolves all symbol references (function pointers, global variable addresses), and then transfers control to the program’s entry point.
Q3. Write the dynamic linker’s full library search order from memory.
1. If dependency string has a slash → use it as a direct path
2. DT_RPATH directories (if no DT_RUNPATH in the binary)
3. LD_LIBRARY_PATH directories (skipped for SUID/SGID binaries)
4. DT_RUNPATH directories
5. /etc/ld.so.cache
6. /lib then /usr/lib
Q4. What is symbol interposition? Give a real-world use case.
Symbol interposition is when a global symbol defined in the main executable (or an LD_PRELOAD library) overrides a same-named symbol in a shared library. Real-world use: LD_PRELOAD=./malloc_tracker.so ./app — this intercepts all malloc() calls to add memory tracking, without modifying the application’s source code. Also used in unit testing to replace external dependencies (like network calls) with mock implementations.
Q5. Explain $ORIGIN and why it matters for distribution.
$ORIGIN is a token in rpath that expands at run time to the directory containing the executable itself. Without $ORIGIN, rpath must contain an absolute path (e.g., /opt/myapp/lib), which breaks if the user installs the application elsewhere. With $ORIGIN, you write -Wl,-rpath,’$ORIGIN/lib’, and the library search is always relative to wherever the binary lives. This enables “turn-key” packages the user can install anywhere without any system configuration.
Q6. A new shared library was installed to /usr/local/lib but programs still get “library not found”. What’s wrong?
The /etc/ld.so.cache was not rebuilt. The dynamic linker uses this cache for fast lookups. After installing a new library to a standard path, you must run sudo ldconfig to scan the directories and rebuild the cache. Only then will the new library be found via the cache lookup step.
Q7. You want to distribute a self-contained tool that runs on any Linux machine without dependencies. What linking strategy do you use?
Use static linking: gcc -o tool main.c -static. This creates a single binary containing all library code. The downside is a large binary, but it will run on any Linux machine regardless of what .so files are installed, as long as the architecture matches. Go and Rust often produce static or nearly-static binaries for exactly this reason.
Q8. What is the soname and versioning scheme for shared libraries?
A shared library uses the naming convention: libname.so.MAJOR.MINOR.PATCH. The soname is libname.so.MAJOR. Symbolic links are created: libname.so → libname.so.MAJOR → libname.so.MAJOR.MINOR.PATCH. Programs link against libname.so.MAJOR (the soname). A new MAJOR version means ABI-incompatible change (programs using old MAJOR still use old .so). A new MINOR/PATCH is backwards-compatible (all programs using this MAJOR get the fix by replacing the file).
Q9. How do you see the exact path where a shared library will be loaded at run time?
Use ldd ./prog — it shows each required library and its resolved path. Example output: “libfoo.so.1 => /usr/local/lib/libfoo.so.1”. For debugging the search process step by step, use LD_DEBUG=libs ./prog which prints every directory checked and which file was ultimately loaded.
Q10. What is the difference between linking with -lm and linking with /usr/lib/libm.a?
-lm lets the linker choose between libm.so (shared, default) and libm.a (static). It picks .so if both exist. Specifying /usr/lib/libm.a explicitly forces use of the static archive. The explicit path form is Method 1 of forcing static linking, and also removes any ambiguity about which directory’s version is used.
Q11. Why should you avoid global symbol names in shared libraries that might clash with other libraries?
Because of symbol interposition semantics, duplicate global names can cause silent hijacking of one library’s internal calls by another. Best practice: prefix all exported symbols with your library’s name (e.g., mylib_init() instead of init()), make internal helper functions static so they don’t appear in the global symbol table at all, and use __attribute__((visibility(“hidden”))) for symbols that should be internal to the library but can’t be static.
Q12. A shared library is upgraded but a running application crashes. How would you diagnose and fix this?
Diagnosis:
1. ldd ./app — check which .so version is loaded now
2. Check if the MAJOR version changed (ABI break) vs MINOR (should be compatible)
3. readelf -d app | grep NEEDED — see what version was expected
4. LD_DEBUG=all ./app 2>&1 — trace symbol resolution failures

Fixes:
– If ABI-incompatible upgrade: keep old .so alongside new (different soname), rebuild app against new version
– If bug in new library: revert to old .so
– Long-term: statically link the library to pin the exact version

Leave a Reply

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