ECE344 Fall 2026 (Sec 1) Lec 3 - Libraries
Watch on YouTube →
Overview
Jon Eyolfson defines an operating system as a kernel plus the libraries required by its applications, explaining why Android and Debian can share the Linux kernel yet remain distinct systems. He compares dynamic `.so` and static `.a` libraries, demonstrates how changing a C struct’s layout can break a dynamic library’s ABI, and shows how tools such as `ldd`, `LD_PRELOAD`, Valgrind, and AddressSanitizer help inspect programs and memory use.
Key takeaways
- An operating system is usefully understood as a kernel plus the libraries needed by its intended applications; sharing the Linux or Darwin kernel does not make two application environments identical.
- Dynamic libraries share code at runtime, but all clients depend on a compatible ABI; static libraries embed code at build time and require relinking to receive updates.
- A public C struct’s field order is part of its ABI: changing the order can make an old executable read the wrong values even when the library’s function API is unchanged.
- Forward-declaring a struct in a public header and keeping its fields private lets a library change its internal representation without forcing clients to adopt the new layout.
- `ldd`, `LD_LIBRARY_PATH`, and `LD_PRELOAD` expose runtime library dependencies, alter library selection, and enable function interception, respectively.
- Valgrind and AddressSanitizer can report different memory behavior because library-owned buffers may be intentionally retained for reuse until process exit, while sanitizers can identify leaks in instrumented application code.
Chapters
- Apple’s macOS, iOS, iPadOS, watchOS, and tvOS all use the Darwin kernel, but their application environments distinguish them as operating systems.
- Android and Debian share the Linux kernel and can run compatible ARM terminal programs, but their graphical application libraries differ.
- A practical definition is a kernel plus the libraries needed by the applications a system is expected to run.
- C compilation turns source files into object files and links them into an executable; a multi-file program can combine functions such as `main`, `util`, and `bar`.
- On Linux, dynamic libraries commonly use the `.so` extension and hold compiled function definitions separately from executables.
- Multiple programs can use one loaded copy of the C standard library, sharing functions such as `printf`, `malloc`, and `exit`.
- A program linked to a dynamic library looks up and loads that library when it runs rather than embedding its code at compile time.
- `ldd` lists an executable’s dynamic dependencies; Jon Eyolfson uses it to inspect programs such as `ls` and Chrome.
- A static library uses the `.a` extension, short for archive, and packages object files containing functions such as `util`, `foo`, and `bar`.
- Linking a static library copies the needed code into the executable at compile time.
- Static linking avoids relying on a separately updated shared library, but each executable carries its own copy of library code.
- A static-library bug fix reaches an existing program only after its executable is relinked or recompiled.
- C compilers lay out struct fields in declaration order; a struct containing two four-byte integers occupies eight bytes, with fields at offsets 0 and 4.
- In the point-library example, `struct point` contains `x` and `y`, and the library exposes creation, accessor, and destroy functions.
- Reordering the fields leaves the apparent API unchanged but changes the binary layout expected by compiled code.
- Because a dynamic library and its client executable can be compiled separately, they may interpret the same eight bytes differently.
- The example executable is compiled against version one of `point.h`, then run with a version-two `libpoint.so` whose struct fields are reversed.
- Library accessors agree with the updated library and report the expected `x` and `y` values, while direct struct access in the old executable reads them in reverse.
- `LD_LIBRARY_PATH` can prioritize a directory containing another `libpoint.so`, demonstrating that runtime lookup may select a newer library without recompiling the executable.
- The mismatch shows that changing a public struct layout can break ABI compatibility even when function names and signatures stay the same.
- If clients need only pass a struct by pointer, a header can forward-declare it without exposing its fields.
- Keeping the struct definition inside the library implementation lets the library change its internal layout without changing client code’s assumptions.
- Once a struct’s fields are public in a header, changing their order can break existing binaries and require recompilation.
- Long-lived system interfaces may preserve awkward field layouts to avoid breaking backward compatibility.
- Semantic versioning uses `major.minor.patch` numbers to communicate the impact of library updates.
- A major-version increase signals an API or ABI break that may require clients to recompile.
- A minor-version increase adds backward-compatible functionality, while a patch increase indicates a bug fix without new features.
- A program needing a function introduced in version 1.2 can specify that minimum and accept later compatible 1.x releases.
- `LD_PRELOAD` can load a wrapper library before normal dependencies, allowing it to intercept calls such as `malloc` and `free`.
- A wrapper around a program that allocates four bytes logs the program’s allocation and also reveals that `printf` allocates a 1,024-byte output buffer.
- Valgrind may report no leak for the C library’s retained buffer because it reuses that memory and the Linux kernel reclaims process resources at exit.
- AddressSanitizer instruments a recompiled executable to identify leaks caused by the program, including the allocation site.
- Most application code reaches the kernel through library functions; C library routines such as `printf`, `malloc`, and `exit` ultimately use system calls such as `write`, `brk`, and `exit_group`.
- C library functions can add error handling and buffering, so several `printf` calls may be combined before a `write` system call occurs.
- Buffered output explains why a program may not display a character immediately after calling `printf` without a newline.
- System calls can also be invoked directly, but ordinary C programs generally use library wrappers.
- The C standard library’s `atexit` registers cleanup functions that run when `main` returns or the program calls `exit`.
- Jon Eyolfson demonstrates that registered handlers run in reverse registration order, after the main function’s output.
- A C++ standard-library struct-layout change about a decade earlier reportedly forced Linux distributions to rebuild many programs, illustrating the cost of an ABI break.
- Dynamic libraries distribute fixes without rebuilding clients but risk breaking them; static libraries insulate clients from later library changes but require relinking to receive fixes.
Summary, takeaways, and chapters were generated by AI from the video's transcript and may contain errors. The video belongs to its creator, Jon Eyolfson.