iOS-OC底层10:dyld加载流程分析
前沿
我们实现ViewController的+(void)load方法,在main函数中添加c++方法
//ViewController的load方法
+ (void)load{
NSLog(@"%s",__func__);
}
//程序入口
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
NSLog(@"1223333");
@autoreleasepool {
// Setup code that might create autoreleased objects goes here.
appDelegateClassName = NSStringFromClass([AppDelegate class]);
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
//c++方法
__attribute__((constructor)) void kcFunc(){
printf("来了 : %s \n",__func__);
}
打印结果
2020-09-29 15:13:42.259577+0800 002-应用程加载分析[81605:302871] +[ViewController load]
来了 : kcFunc
2020-09-29 15:13:42.268476+0800 002-应用程加载分析[81605:302871] 1223333
我们可以看出先load然后c++,最后main方法,接下来我们围绕这个打印顺序就行分析。
我们在c++方法处打断点,然后查看堆栈信息如下
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 2.1
* frame #0: 0x0000000102858174 002-应用程加载分析`kcFunc at main.m:30:5
frame #1: 0x00000001028786d9 dyld_sim`ImageLoaderMachO::doModInitFunctions(ImageLoader::LinkContext const&) + 513
frame #2: 0x0000000102878ace dyld_sim`ImageLoaderMachO::doInitialization(ImageLoader::LinkContext const&) + 40
frame #3: 0x0000000102873868 dyld_sim`ImageLoader::recursiveInitialization(ImageLoader::LinkContext const&, unsigned int, char const*, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 456
frame #4: 0x0000000102871d2c dyld_sim`ImageLoader::processInitializers(ImageLoader::LinkContext const&, unsigned int, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 188
frame #5: 0x0000000102871dcc dyld_sim`ImageLoader::runInitializers(ImageLoader::LinkContext const&, ImageLoader::InitializerTimingList&) + 82
frame #6: 0x0000000102866270 dyld_sim`dyld::initializeMainExecutable() + 199
frame #7: 0x000000010286a1bb dyld_sim`dyld::_main(macho_header const*, unsigned long, int, char const**, char const**, char const**, unsigned long*) + 3662
frame #8: 0x00000001028651cd dyld_sim`start_sim + 122
frame #9: 0x0000000108e4e85c dyld`dyld::useSimulatorDyld(int, macho_header const*, char const*, int, char const**, char const**, char const**, unsigned long*, unsigned long*) + 2308
frame #10: 0x0000000108e4c4f4 dyld`dyld::_main(macho_header const*, unsigned long, int, char const**, char const**, char const**, unsigned long*) + 837
frame #11: 0x0000000108e47227 dyld`dyldbootstrap::start(dyld3::MachOLoaded const*, int, char const**, dyld3::MachOLoaded const*, unsigned long*) + 453
frame #12: 0x0000000108e47025 dyld`_dyld_start + 37
编译过程
我们写的oc代码怎么能显示到手机上呢?
image.png
我们写的hm文件先要经过预编译,预编译主要是语法分析,语义分析,编译是生成中间代码然后汇编链接到可执行文件
iOS的库
静态库通常以.a,.lib或者.framework结尾,动态库以.tbd,.so,.framework结尾
1.静态库:链接时,静态库会被完整的复制到可执行文件中,被多次使用就会有多份冗余拷贝
2.动态库:链接时不复制,程序运行时由系统动态加载到内存,供程序调用,系统只加载一次,多个程序公用,节省内存
image.png
我们通过一个图来区分静态库和动态库
在静态库中B和D分别被完整的复制到可执行文件,而动态库中则由系统动态加载到内存。
dyld加载流程
我们要先在官网上下载一份dyld源码
概念什么是dyld
dyld 是英文 the dynamic link editor 的简写,翻译过来就是动态链接器,是苹果操作系统的一个重要的组成部分。在 iOS/Mac OSX 系统中,仅有很少量的进程只需要内核就能完成加载,基本上所有的进程都是动态链接的,所以 Mach-O 镜像文件中会有很多对外部的库和符号的引用,但是这些引用并不能直接用,在启动时还必须要通过这些引用进行内容的填补,这个填补工作就是由 动态链接器dyld 来完成的,也就是符号绑定。动态链接器dyld 在系统中以一个用户态的可执行文件形式存在,一般应用程序会在 Mach-O 文件部分指定一个 LC_LOAD_DYLINKER 的加载命令,此加载命令指定了 dyld 的路径,通常它的默认值是 /usr/lib/dyld 。系统内核在加载 Mach-O 文件时,都需要用 dyld(位于 /usr/lib/dyld )程序进行链接。
总括加载流程
image.png_dyld_start源码
__dyld_start:
...
//调用dyldbootstrap:start函数
# call dyldbootstrap::start(app_mh, argc, argv, dyld_mh, &startGlue)
subl $L__dyld_start_picbase-__dyld_start, %ebx # ebx = &__dyld_start
subl $0x1000, %ebx # ebx = load address of dyld
movl %edx,(%esp) # param1 = app_mh
movl 4(%ebp),%eax
movl %eax,4(%esp) # param2 = argc
lea 8(%ebp),%eax
movl %eax,8(%esp) # param3 = argv
movl %ebx,12(%esp) # param4 = dyld load address
lea 28(%esp),%eax
movl %eax,16(%esp) # param5 = &startGlue
call __ZN13dyldbootstrap5startEPKN5dyld311MachOLoadedEiPPKcS3_Pm
movl 28(%esp),%edx
cmpl $0,%edx
jne Lnew
...
//调用main函数
# LC_MAIN case, set up stack for call to main()
Lnew: movl 4(%ebp),%ebx
movl %ebx,(%esp) # main param1 = argc
leal 8(%ebp),%ecx
movl %ecx,4(%esp) # main param2 = argv
leal 0x4(%ecx,%ebx,4),%ebx
movl %ebx,8(%esp) # main param3 = env
...
_dyld_start=====>dyldbootstrap::start======>main
dyldbootstrap::start最主要的就是调用dyld::_main
dyldbootstrap::start===>dyld::_main
dyld::_main流程
Dyld.png我们可以根据注释去了解大致流程,我们最主要研究的是initializeMainExecutable函数
initializeMainExecutable
initializeMainExecutable=====>runInitializers=====>processInitializers=======>recursiveInitialization
recursiveInitialization
context.notifySingle(dyld_image_state_dependents_initialized, this, &timingInfo);
// initialize this image
bool hasInitializers = this->doInitialization(context);
context.notifySingle中
(*sNotifyObjCInit)(image->getRealPath(), image->machHeader());
我们追踪sNotifyObjCInit发现赋值
_dyld_objc_notify_register====> dyld::registerObjCNotifiers(mapped, init, unmapped);==>(sNotifyObjCInit = init;)
在objc源码中搜索_dyld_objc_notify_register,发现此方法是在_objc_init中调用的
并且sNotifyObjCInit的回调实现为
调用load方法
void
load_images(const char *path __unused, const struct mach_header *mh)
{
if (!didInitialAttachCategories && didCallDyldNotifyRegister) {
didInitialAttachCategories = true;
loadAllCategories();
}
// Return without taking locks if there are no +load methods here.
if (!hasLoadMethods((const headerType *)mh)) return;
recursive_mutex_locker_t lock(loadMethodLock);
// Discover load methods
{
mutex_locker_t lock2(runtimeLock);
prepare_load_methods((const headerType *)mh);
}
// Call +load methods (without runtimeLock - re-entrant)
call_load_methods();
}
我们在_objc_init打断点
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 2.1
* frame #0: 0x00000001002d461d libobjc.A.dylib`_objc_init at objc-os.mm:920:5
frame #1: 0x000000010042b0bc libdispatch.dylib`_os_object_init + 13
frame #2: 0x000000010043bafc libdispatch.dylib`libdispatch_init + 282
frame #3: 0x00007fff6ad08791 libSystem.B.dylib`libSystem_initializer + 220
frame #4: 0x000000010002f1d3 dyld`ImageLoaderMachO::doModInitFunctions(ImageLoader::LinkContext const&) + 535
frame #5: 0x000000010002f5de dyld`ImageLoaderMachO::doInitialization(ImageLoader::LinkContext const&) + 40
frame #6: 0x0000000100029ffb dyld`ImageLoader::recursiveInitialization(ImageLoader::LinkContext const&, unsigned int, char const*, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 493
frame #7: 0x0000000100029f66 dyld`ImageLoader::recursiveInitialization(ImageLoader::LinkContext const&, unsigned int, char const*, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 344
frame #8: 0x00000001000280b4 dyld`ImageLoader::processInitializers(ImageLoader::LinkContext const&, unsigned int, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 188
frame #9: 0x0000000100028154 dyld`ImageLoader::runInitializers(ImageLoader::LinkContext const&, ImageLoader::InitializerTimingList&) + 82
frame #10: 0x0000000100016662 dyld`dyld::initializeMainExecutable() + 129
frame #11: 0x000000010001bbba dyld`dyld::_main(macho_header const*, unsigned long, int, char const**, char const**, char const**, unsigned long*) + 6667
frame #12: 0x0000000100015227 dyld`dyldbootstrap::start(dyld3::MachOLoaded const*, int, char const**, dyld3::MachOLoaded const*, unsigned long*) + 453
frame #13: 0x0000000100015025 dyld`_dyld_start + 37
系统会优先初始化libSystem,libdispatch,libdispatch libobjc,然后注册一个通知sNotifyObjCInit,当我们初始化主程序或者我们写的库时,会发通知让先加载该哭的load方法,然后doInitialization进行调用c++方法
dyld流程分析图.png