DYLD, Symbol not found
Let us revisit dynamic linking and then solve an otherwise cryptic iOS crash.
I have recently come across an unusual bug, whose Xcode crash point output is:
Exception Type: EXC_CRASH (SIGKILL)
Exception Codes: 0x0000000000000000, 0x0000000000000000
Exception Note: EXC_CORPSE_NOTIFY
Termination Description: DYLD, Symbol not found: _objc_opt_new | Referenced from: /private/var/containers/Bundle/Application/MyApp.app/Frameworks/MyFramework.framework/MyFramework | Expected in: dyld shared cacheThread 0 Crashed:
...
4 dyld 0x0000000100f38a14 dyld::fastBindLazySymbol (ImageLoader**, unsigned long) + 284 (dyld.cpp:4112)
5 libdyld.dylib 0x00000001f7634848 dyld_stub_binder + 60
6 MyFramework 0x00000001033721f4 +[MyLibrary myMethod:]
...
Apple crash report points to a missing symbol referenced in some custom framework. Looking at the crashing thread backtrace, we see indeed some familiar method call at line 6. But then the execution moves to a system module called dyld at line 4. It is the same module that “Terminate Description” mentions: DYLD, Symbol not found. DYLD stands for dynamic loader. It is Apple’s macOS and iOS module responsible for dynamically linking libraries.
To decipher our bug, we should first remind ourselves how linking works and how dynamic linking differs from static linking. Hopefully this will help us understand how on earth could some symbol not be found at runtime and cause our app to crash.
The Linking Step
Building an executable involves a series of steps. Programs are translated from human readable code into machine code. The linker is usually the last step of the compilation system. It takes as input multiple object files and concatenates their sections to produce an executable ready to be loaded into memory and executed.
An object file defines and references symbols (a variable or a method name). One of the linker’s jobs is to resolve symbol references. It associates each reference with a definition, possibly across different modules.
Linking can be performed at compile time, right after the assembler outputs an object file containing machine code. This is called static linking. Once the static linker is done, all symbols have been resolved. Text and data sections have been merged into one self-sufficient executable.
Dynamic Linking
Linking can also be performed later. At load time, when the program is copied into memory; or even at run time. This is called dynamic linking.
The idea of dynamic linking is to split the linking into two steps. First, when the executable is created, the linker does some of its job statically. For instance, it resolves symbol references for locally defined symbols. Then, a program called the dynamic linker completes the linking process by relocating text and data sections, updating previously unresolved symbol references with their final memory location.
Linking on iOS
On iOS, frameworks and libraries are two types of modules that can be linked to another module.
Frameworks can be dynamically or statically linked. Custom libraries can only be statically linked (Swift Package Manager recently introduced dynamic linking for Libraries). System libraries however, such as UIKit, are dynamically linked into applications.
Symbol Not Found
Let us now go back to our crash and let me show you once again its backtrace, right before the dylb module is executed:
6 MyFramework 0x00000001033721f4 +[MyLibrary myMethod:]
First I owe you some important precisions:
- MyFramework is a framework, compiled from source code when Xcode builds the application.
- MyFramework is dynamically linked into the application.
- MyLibrary is a pre-compiled library: it’s an archive, .a file, that MyFramework links against.
- MyLibrary is statically linked into MyFramework.
- MyLibrary links against UIKit.
We now have all the information we need. It’s time to piece them together.
The crash happens when we call myMethod of MyLibrary. This method triggers a call to the dyld. It means that myMethod calls another method or references a variable from a library it is dynamically linked against. As listed above, MyLibrary links against UIKit.
How could MyLibrary reference a symbol from UIKit that the dynamic loader can’t find at run time? This scenario would happen if MyLibrary had been pre-compiled against a version of UIKit that is different from the one the dynamic linker finds at run time.
To recap:
- MyLibrary is pre-compiled against a newer version of UIKit.
- MyFramework statically links agaisnt the pre-compiled MyLibrary.
- The iOS application embeds MyFramework — dynamic linking.
- Xcode builds the iOS application against an older version of UIKit
- At some point of its execution, myMethod of MyLibrary references a UIKit symbol. The dynamic loader can’t resolve this symbol because it can’t find its definition in the older UIKit the application links against.
The mix of source and binary frameworks/libraries, along with static and dynamic linking, can lead to runtime bugs Xcode build process can’t help you with.
The funny thing about this bug is how easy it is to fix once you understand it. One just needs to recompile MyLibrary against the same version of UIKit the iOS app uses.