Skip to content
macOSDeep Dive Published Updated 5 min readViews unavailable

How dyld and the Shared Cache Actually Load macOS Applications

Why you can't find most of macOS's own system libraries as individual files on disk anymore, and how the shared cache changed launch performance.

Every macOS application that isn’t entirely statically linked depends on dyld, Apple’s dynamic linker, to resolve and load shared libraries at launch time — but since macOS Big Sur, a curious change means you can no longer find most of the system’s own frameworks and libraries as individual files on disk at all, because they’ve been merged into a single, massive shared cache file instead.

What dynamic linking normally looks like

On a traditional Unix-like system, a dynamically-linked executable references shared libraries by path, and the dynamic linker (ld.so on Linux, dyld on macOS) locates each one on disk at launch time, maps it into the process’s address space, and resolves symbol references between the executable and each loaded library. This is flexible — libraries can be updated independently of the executables that use them — but it comes with real launch-time cost: locating each library file, opening it, parsing its structure, and resolving symbols across potentially dozens of interdependent libraries, every single time an application launches.

What changed with the dyld shared cache

Apple had used a shared cache mechanism in a more limited form for years, but with Big Sur, virtually the entire set of system frameworks and libraries got merged into one large, pre-linked shared cache file (found under /System/Library/dyld/ on Intel Macs, or, on Apple Silicon, actually only reconstructable in memory rather than existing as a single visible file, precisely because Apple no longer wants developers relying on its exact internal layout). This is why running ls /usr/lib/ or checking for a specific system framework’s .dylib file directly often turns up nothing on modern macOS — the individual files genuinely aren’t there anymore, having been consolidated into the cache at build time.

Why Apple did this

Building the shared cache is essentially doing a huge portion of the dynamic linking work once, at OS build time, rather than redoing equivalent work every time any application launches. Symbol resolution between interdependent system libraries, which would otherwise need to happen fresh at every launch of every application using those libraries, is instead resolved once when the cache itself is built — the practical result is a measurable, systemic improvement in application launch time across the entire OS, since essentially every non-trivial macOS application depends on at least some system frameworks.

What this means for tools that expect individual library files

This change broke a real category of existing tooling and workflows that expected to find and directly inspect individual system .dylib files on disk — debugging tools, certain reverse-engineering workflows, and some third-party software that shipped its own bundled copies of what it assumed were still discoverable system libraries. Apple’s answer for legitimate cases (debugging, security research) is a set of dedicated tools built specifically to work with the cache format rather than raw files — dyld_shared_cache_util can extract individual libraries back out of the cache when genuinely needed, and system-level debugging tools were updated to understand the cache format directly rather than expecting standalone files.

How this interacts with code signing

The shared cache’s contents are built from Apple’s own signed system libraries, and the cache itself is protected as part of the broader signed system volume (the read-only APFS volume introduced alongside System Integrity Protection hardening in more recent macOS versions) — meaning tampering with the shared cache isn’t simply a matter of editing files on disk the way it might have been with individual library files in older macOS versions. This ties the shared cache’s integrity directly into the same cryptographic verification chain that protects the rest of the system volume, rather than being a separate, independently-securable component.

The practical takeaway for anyone debugging load-time behavior

If you’re trying to trace which library a symbol actually resolved from, or diagnose a dynamic-linking-related crash on modern macOS, the mental model of “just go find the .dylib file and inspect it directly” no longer applies for anything that’s part of the system cache — the actual library content exists, but it’s living inside the consolidated cache rather than as a standalone file, and the debugging tools and workflow need to account for that directly rather than assuming a traditional per-library file layout that hasn’t reflected reality on modern macOS in years.

The shared cache existed in a more limited form long before Big Sur

Apple didn’t invent the shared-cache concept for Big Sur — a more limited version dates back to Mac OS X Leopard (10.5) in 2007, initially covering only a smaller, curated set of the most frequently used system frameworks rather than virtually the entire system library set. What changed with Big Sur wasn’t the underlying mechanism so much as its scope: extending the same pre-linking approach to nearly every system framework and library, rather than a curated subset, which is exactly why the practical effect — individual .dylib files disappearing from disk — became so much more noticeable and comprehensive starting with that specific release, even though the technique itself had over a decade of prior, more limited production use behind it.

How ASLR interacts with a monolithic shared cache

Address Space Layout Randomization (ASLR) — loading libraries at a randomized base address on each launch, specifically to make memory-corruption exploits harder to reliably weaponize — still applies to the shared cache, but at the level of the cache as a single unit rather than per individual library. The entire cache is relocated as one contiguous block to a randomized base address at load time, rather than each of the hundreds of libraries it contains being independently randomized relative to one another — a meaningfully different randomization granularity than a traditional per-library dynamic linking setup provides, since the relative offsets between libraries within the cache stay fixed across every launch even though the cache’s overall base address changes. This is a deliberate tradeoff: computing the cache’s own internal relative layout once, at build time, rather than re-randomizing internal relationships on every single launch, is part of what keeps the performance benefit intact — full independent per-library randomization would reintroduce some of the very per-launch resolution cost the whole shared-cache mechanism exists to eliminate.

Related:

Sources:

Comments