底层

iOS-OC底层10:dyld加载流程分析

2020-10-12  本文已影响0人  MonKey_Money

前沿

我们实现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
上一篇下一篇

猜你喜欢

热点阅读