iOS 27's Reworked Stub Islands

gok1 pts0 comments

Inside iOS 27's Reworked Stub Islands

June 15, 2026Inside iOS 27's Reworked Stub Islands

At WWDC 2026, Apple announced iOS 27 with performance improvements, but of course the keynote didn't cover much of the details.<br>Below are a few observations from the disassembly — some may relate to those performance gains, some may not.<br>This article only focuses on aarch64 implementations.

How Stubs Worked Before iOS 27

In the dyld_shared_cache, external symbols—whether functions or data references from other libraries—are pre-bound<br>during cache optimization. Pointers to other libraries become rebase operations (or relative offsets)<br>because the precise distances between libraries are known and fixed.

A typical example of how dyld resolves dynamic symbol references in standalone binaries outside of the dyld_shared_cache:

__text:

BL _os_unfair_lock_unlock ; __auth_stubs<br>__auth_stubs: _os_unfair_lock_unlock

ADRL X17, _os_unfair_lock_unlock_ptr<br>LDR X16, [X17] ; load from GOT entry<br>BRAA X16, X17 ; jump to the resolved function<br>Where _os_unfair_lock_unlock_ptr is an entry in __auth_got. The linker (dyld) will bind (and sign) the pointer to the actual implementation (__imp__os_unfair_lock_unlock) and store it in __auth_got.

_os_unfair_lock_unlock_ptr DCQ __imp__os_unfair_lock_unlock<br>In dyld_shared_cache, most of __auth_stubs and __auth_got are aggregated into stub island pages, which don't belong to any specific binary.

There is still a per-binary __auth_stubs section in the dyld_shared_cache, however most of the time you won't find the actual cross-references to them, because the branch instructions are replaced to point to the stub island pages.

iOS still makes heavy use of Objective-C, so there is first-class support for method calls (message dispatch).

Most Objective-C method calls are compiled into a branch instruction that points to stubs in a dedicated section __objc_stubs:

__objc_stubs: _objc_msgSend$URLByAppendingPathComponent_<br>ADRP X1, #selRef_URLByAppendingPathComponent_@PAGE ; load from __objc_selrefs<br>LDR X1, [X1,#selRef_URLByAppendingPathComponent_@PAGEOFF] ; load selector<br>ADRL X17, _objc_msgSend_ptr ; GOT entry<br>LDR X16, [X17] ; load _objc_msgSend<br>BRAA X16, X17 ; dispatch message<br>What iOS 27 Changes

Redundant sections are gone

As mentioned, after dyld cache optimization, branch instructions to __objc_stubs are updated to jump to stub island pages, while the unused sections remain in each binary.

On iOS 27 beta, those sections are removed; only the stub island pages in the dyld_shared_cache remain.

Some other sections related to Objective-C are also removed from source binaries, such as __objc_methname, __objc_methtype and __objc_classname. Now the data references (selectors, method types, class names) point to the __OBJC_RO region.

It's worth noting that a while ago, the schema of __objc2_meth_list changed.

__objc2_meth_list contains a list of method information for Objective-C methods, including each method's selector, type encoding, and implementation pointer. The selector field used to be an offset from the field itself. Then the dyld_shared_cache optimizer changed the semantics to use a global base address for selector offsets, which can be found through the cache header:

struct dyld_cache_header {<br>// other fields omitted<br>uint64_t objcOptsOffset; // VM offset from cache_header* to ObjC optimizations header<br>uint64_t objcOptsSize; // size of ObjC optimizations header<br>};<br>This pair points to the ObjCOptimizationHeader struct:

dyld/common<br>/DyldSharedCache.h#L89

struct VIS_HIDDEN ObjCOptimizationHeader<br>uint32_t version;<br>uint32_t flags;<br>uint64_t headerInfoROCacheOffset;<br>uint64_t headerInfoRWCacheOffset;<br>uint64_t selectorHashTableCacheOffset;<br>uint64_t classHashTableCacheOffset;<br>uint64_t protocolHashTableCacheOffset;<br>uint64_t relativeMethodSelectorBaseAddressOffset;

// Added in version 2<br>uint64_t relativeMethodSelectorBufferSize;<br>uint64_t relativeMethodTypesBufferSize; // this buffer starts at the end of the selectors buffer<br>};<br>So on iOS 26, to get the selector string, you need to add the selector offset<br>to relativeMethodSelectorBaseAddressOffset, instead of to the address of that field itself. 🤯

In version 2 of the Objective-C optimizations, dyld also applies this same offset<br>schema to method type encoding strings.

The throughline: Apple is actively removing redundant sections.

Rethinking the stub trampolines

We've mentioned two types of stubs: one for cross-module symbols and one for Objective-C method calls.

On iOS 27, the old __auth_stubs style — ADRL and LDR to load a function pointer<br>from the GOT, then BRAA to branch with pointer validation — still exists. But there are two new variants.

The first has no memory load nor pointer validation:

_stubs: _open<br>ADRL X16, _open<br>BR X16<br>Then here comes an interesting pattern:

ADR X16, 0x188060060<br>MOV X17, #0x9B7<br>ADD X16, X16, X17, LSL #21<br>BR X16<br>If you have no clue what it is supposed to do, try simulating the arithmetic instructions.

> X16...

uint64_t stub from method dyld_shared_cache load

Related Articles