How iOS 15 makes your app launch faster

Read the full version of this post on the Emerge Tools Blog

--

The most intriguing feature from WWDC21 was buried deep in the Xcode 13 release notes:

All programs and dylibs built with a deployment target of macOS 12 or iOS 15 or later now use the chained fixups format. This uses different load commands and LINKEDIT data, and won’t run or load on older OS versions.

There isn’t any documentation or sessions to learn more about this change, but we can reverse engineer it to see what Apple is doing differently on the new OSes and if it will help your apps. First, a bit of background on the program that controls app startup.

Meet dyld

The dynamic linker (dyld) is the entry point of every app. It’s responsible for getting your code ready to run, so it would make sense that any improvement to dyld would result in improved app launch time. Before calling main, running static initializers, or setting up the Objective-C runtime, dyld performs fixups. These consist of rebase and bind operations which modify pointers in the app binary to contain addresses that will be valid at runtime. To see what these look like, you can use the dyldinfo command line tool.

% xcrun dyldinfo -rebase -bind Snapchat.app/Snapchat
rebase information (from compressed dyld info):
segment section address type
__DATA __got 0x10748C0C8 pointer
...
bind information:
segment section address type addend dylib symbol
__DATA __const 0x107595A70 pointer 0 libswiftCore _$sSHMp

This means address 0x10748C0C8 is located in __DATA/__got and needs to be shifted by a constant value (known as the slide). While address 0x107595A70 is in __DATA/__const and should point to the protocol descriptor for Hashable[1] found in libswiftCore.dylib

dyld uses the LC_DYLD_INFO load command anddyld_info_command struct to determine the location and size of rebases, binds and exported symbols[2] in a binary. Emerge (disclaimer: I’m the founder 😬), parses this data to let you visualize their contribution to binary size as well as suggest linker flags to make them smaller:

A new format

When I first uploaded an app built for iOS 15 to Emerge there was no visualization of dyld fixups. This was because the LC_DYLD_INFO_ONLY load command was missing, it had been replaced by LC_DYLD_CHAINED_FIXUPS and LC_DYLD_EXPORTS_TRIE.

% otool -l iOS14Example.app/iOS14Example | grep LC_DYLD
cmd LC_DYLD_INFO_ONLY
% otool -l iOS15Example.app/iOS15Example | grep LC_DYLD
cmd LC_DYLD_CHAINED_FIXUPS
cmd LC_DYLD_EXPORTS_TRIE

The export data is exactly the same as before, a trie where each node represents part of a symbol name.

Portion of the exports trie for Wikipedia

The only change in iOS 15 is the data is now referenced by a linkedit_data_command which contains the offset of the first node. To validate this, I wrote a short Swift app to parse the iOS 15 binary and print each symbol:

Chaining

The real change is in LC_DYLD_CHAINED_FIXUPS. Before iOS 15, rebases, binds and lazy binds were each stored in a separate table. Now they have been combined into chains, with pointers to the starts of the chains contained in this new load command…

Read the full version of this post on the Emerge Tools Blog

--

--

Noah Martin
Geek Culture

Co-founder of Emerge Tools, previously software engineer at Airbnb.