App build step
- 预处理,将宏替换,生成ASCIi中间文件
- 运行编译器,将中间文件生成汇编中间文件
- 运行汇编器,将汇编文件翻译生成目标文件 .o
- 运行链接器,将目标文件组合成一个可执行文件
Mach-O Terminology
File Types:
- Executable – Main binary for application
- Dylib – Dynamic library (.dylib, .dll, .so)
- bundle – Dylib that cannot be linked, only
dlopen()
, e.g. plug-ins
Image – An executable, dylib or Bundle
Framework – Dylib with directories for resources and headers
Segments
File divided into segments:__TEXT
, __DATA
, __LINEEDIT
All segments are multiples of page size. 16KB on arm64, 4KB else where.
Sections are a subrange of a segment
Common segments:
__TEXT
has header, code, and read-only constants(c strings)__DATA
has all read-write content: globals, static variables, etc.__LINKEDIT
hasmeta data
about how to load the program.
Mach-O Universal Files
Fat Header
- One Page in size
- Lists architectures and offsets
Tools and runtime support fat march-O files
Mach-O Image Loading
From exec()
to main()
exec()
It’s a system call. Kernel maps maps your application into new address space.
Start of your app is random.
Low memory is marked inaccessible
- 4KB+ for 32bit process
- 4GB+ for 64-bit process
- Catches NULL pointer usage
- Catches pointer truncation errors.
What about Dylibs
Kernel loads helper program.
- Dyld (dynamic loader)
- Executions starts in
dyld
Dyld runs in-process - Loads dependent dylibs
- Has same permissions as app
Dyld Steps
- Map all dylibs, recurse
- Rebase all Images
- Bind all Images
- ObjC prepare images
- Run initializers
Loading Dylibs
- parse list of depentdent dylibs. (they are listed in the executable file header)
- find requested mach-o file
- open and read start of file
- validate mach-o
- register code signature (
Code signatur
means instructions can not be altered) - call
mmap()
for each segment
Recursive Loading
- All your indepedent dylibs are loaded.
- Plus any dylib’s needed by those dylibs
- Rinse and repeat
Apps typically load 100 to 400 dylibs!
- Most are OS dylibs (they are always pre-cashed, and loaded very quickly)
Rebase and Bindings
Rebase : Ajusting pointers to within an image
binding: Setting pointers to outside image
1 | //show rebase, bind information |
大致理解是每个动态库中,指针地址的起始位置都是相对于内部基于0开始的,但是将库加载到App进程的内存空间后,整个库的指针起始位置是变了的。每个库都有自己的起始位置。所以有两个方面要调整:
- 动态库内部的地址引用需要调整, rebase
- 动态库之间的地址引用需要调整。binding
显然是与库中类的大小,个数都是相关的
Notify ObjC Runtime
- Most ObjC set up done via rebasing and binding
- All ObjC class definitions are registered
- Non-fragile ivars offsets updated
- Categories are inserted into method lists
- Selectors are uniqued
Initializers
- C++ generates initializers for statically allocated objects
- ObjC +load Method
- Run
bottom up
so each initializer can call dylibs below it - Lastly, Dyld calls
main()
in executable
Improve launch time
Goals
- Duration varies on devices.
- 400ms is a good target
- don’t ever take longer than 20 seconds, or App will be killed by system
Test on the lowest supported device
Launch steps
parse images -> map images -> rebase images -> bind images -> run image initializers -> Call main()
-> Call UIApplicationMain()
-> Call applicationWillFinishLaunching
warm vs cold launch
warm launch
App and data are already in memory
cold launch
App is not in kernal buffer cache
Warm and cold launch times will be different.
Cold launch times are important.Measure cold launch time by rebooting.
measure pre-main
Measure before main()
is difficult.
Dyld has built in mesurements
DYLD_PRINT_STATISTICS
environment variable- Debugger pauses every dylib load. Dyld subtracts out debugger time
Dylib Loading
Embedded dylibs are expensive
Use fewer dylibs
- Merge existing dylibs
- Use static archives
dlopen()
can cause issues, so it’s not recommended.
rebasing/binding
Reduce __DATA pointers
Reduce Objective C metadata
- Classes, selectors, categories
Reduce C++ virtual
Use Swift structs
Examine machine generated codes
- use offsets instead of pointers
- Mark readonly
ObjC setup
- Class registration
- Non-fragile ivars offsets updated
- Category registration
- Selector uniquing
the same way as rebasing/binding.
Initializers -explicit
ObjC +load()
method
- Relplace with
+initialize
C/C++ attribute((constructor))
Replace with call site initializers
dispatch_once()
pthread_once()
std::once()
Initializers -implicit
C++ statics with non-trivial constructors
rewrite with Swift…
Do not cal dlopen()
in initializers
Do not create thread in initializers