iOS底层-16:应用程序的加载

今天我们研究的是应用程序的加载过程,先做一下准备工作。
新建一个iphone工程,添加下面代码:

  • ViewController.m
+ (void)load{
    NSLog(@"%s",__func__);
}
  • main.m
int main(int argc, char * argv[]) {
    NSString * appDelegateClassName;
    
    NSLog(@"333333");
    
    @autoreleasepool {
        // Setup code that might create autoreleased objects goes here.
        appDelegateClassName = NSStringFromClass([AppDelegate class]);
    }
    return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}

__attribute__((constructor)) void lyFunc(){
    printf("来了 : %s \n",__func__);
}

运行查看这三个打印的顺序:

+[ViewController load]
来了 : lyFunc 
333333

程序加载过程这些方法的调用顺序是:
load ——> C++ ——> main
main函数作为程序的入口,为什确实最后执行的,我们需要研究的是在main函数之前,程序到底做了什么?

程序编译过程

编译过程

预编译:预编译又称为预处理,主要做些代码文本的替换工作,处理#开头的指令。比如拷贝#include、#import包含的文件代码,#define宏定义的替换,条件编译等,就是为编译做预备工作的阶段,主要处理#开始的预编译指令。生成.i文件

编译:将高级语言转换为机器能识别的汇编语言。生成.s文件

汇编:将汇编文件转换成机器码文件。生成.o文件

链接:对.o文件中引用的其他的库进行引入,生成可执行文件。

动态库 和 静态库

动静态库链接
  • 静态库:在链接阶段,将可汇编生成目标程序与它所引用的库一起链接打包到可执行文件中。此时静态库就不会改变了,静态库是直接拷贝,复制到目标程序。

    • 优点:编译完成,库文件就没有作用了,运行时可以直接使用
    • 缺点:不同的静态库中引用了相同的文件,如上图中的B、D,他就会拷贝两份相同的库文件,导致目标程序体积增大,对性能、内存会有一定的影响。
  • 动态库:编译时相同的库并不会拷贝到目标程序,而是在目标程序载入的时候,把那些相同的库用一份共享实例加载进来。

    • 优点
      • 减少打包的体积:共享内存,节约资源
      • 更新动态库可以直接更新程序:由于运行时才载入的特性,因此可以随时对下层库进行更换,而我们的代码却不用改变
    • 缺点:动态载入会带来一部分性能损失,如果当前环境缺少动态库,或者版本不正确,会导致程序无法运行

dyld加载流程

dyld是苹果的动态连接器,加载到程序中的动态库,主要靠dyld来链接管理。

下面我们通过源码分析dyld的加载流程,首先下载一份dyld源码,这篇文章使用的是750.6

拿到了源码,那么我们从哪里开始分析呢?借助一下上面的工程,在load方法处加上断点,打印堆栈信息:

  • bt

image.png

我们可以看到堆栈的第一个信息是_dyld_start,也可以直接在左边窗口查看。

  • 打开dyld源码,搜索_dyld_start找到__arm64__环境下的源码

往下查找发现它调起了dyldbootstrap::start方法

  • 搜索dyldbootstrap找到start方法
uintptr_t start(const dyld3::MachOLoaded* appsMachHeader, int argc, const char* argv[],
                const dyld3::MachOLoaded* dyldsMachHeader, uintptr_t* startGlue)
{

    // Emit kdebug tracepoint to indicate dyld bootstrap has started 
    dyld3::kdebug_trace_dyld_marker(DBG_DYLD_TIMING_BOOTSTRAP_START, 0, 0, 0, 0);

    // if kernel had to slide dyld, we need to fix up load sensitive locations
    // we have to do this before using any global variables
    rebaseDyld(dyldsMachHeader);

    // kernel sets up env pointer to be just past end of agv array
    const char** envp = &argv[argc+1];
    
    // kernel sets up apple pointer to be just past end of envp array
    const char** apple = envp;
    while(*apple != NULL) { ++apple; }
    ++apple;

    // set up random value for stack canary
    __guard_setup(apple);

#if DYLD_INITIALIZER_SUPPORT
    // run all C++ initializers inside dyld
    runDyldInitializers(argc, argv, envp, apple);
#endif

    // now that we are done bootstrapping dyld, call dyld's main
    uintptr_t appsSlide = appsMachHeader->getSlide();
    return dyld::_main((macho_header*)appsMachHeader, appsSlide, argc, argv, envp, apple, startGlue);
}

其核心就是dyld::_main,返回调用了dyldmain函数。从左边的堆栈,也可以清晰的看到。其中macho_header就是Mach-o的头部。

  • 点击跳转到dyld::_main函数
    我们发现这个main函数有600多行代码,在这里就不详细解释,感兴趣的同学可以自行研究。_main函数主要做了以下事情:

1.环境变量配置
2.共享缓存:(UIKit、CoreFoundation等)
3.主程序的初始化
4.加入动态库
5.link主程序
6.link动态库
7.绑定弱引用
8.initialize初始化
9.main()

-环境变量配置

  • 共享缓存

  • 主程序初始化

  • 加载动态库

  • link主程序

  • link动态库

  • 弱引用绑定主程序

  • run initialize初始化方法

    image.png

  • 进入main()函数

    image.png

initialize

我们主要分析第八步,看看run all initializers里面做了什么?

  • 点击进入initializeMainExecutable源码

  • 点击runInitializers

    image.png

    主要是其中的processInitializers方法

  • 点击进入processInitializers

  • 进入recursiveInitialization源码


    重点查看notifySingledoInitialization方法

notifySingle
  • 进入notifySingle源码
    根据传入参数dyld_image_state_dependents_initializeddyld_image_state_initialized找到下段代码

  • sNotifyObjCInit
    直接搜索sNotifyObjCInit没有找到相应的源码,只找到一段赋值代码。我们继续跟registerObjCNotifiers流程

  • 搜索_dyld_objc_notify_register
    只找到一处调用的地方,接着我们搜索查看_dyld_objc_notify_register,并没有发现调用的地方。_dyld_objc_notify_register实际上是在objc源码中调用的.

  • objc-781源码中搜索_dyld_objc_notify_register


    _objc_init中找到,从这可知赋给sNotifyObjCInit的值其实就是load_images

  • 接着查看load_images源码


    主要放大是call_load_methods,调用所有+load方法

  • 点击call_load_methods

void call_load_methods(void)
{
    static bool loading = NO;
    bool more_categories;

    loadMethodLock.assertLocked();

    // Re-entrant calls do nothing; the outermost call will finish the job.
    if (loading) return;
    loading = YES;

    void *pool = objc_autoreleasePoolPush();

    do {
        //遍历所有的类,调用 + load 方法
        // 1. Repeatedly call class +loads until there aren't any more
        while (loadable_classes_used > 0) {
            call_class_loads();
        }
        //加载所有的category
        // 2. Call category +loads ONCE
        more_categories = call_category_loads();

        // 3. Run more +loads if there are classes OR more untried categories
    } while (loadable_classes_used > 0  ||  more_categories);

    objc_autoreleasePoolPop(pool);

    loading = NO;
}

看到这里想必大家都已经明白了,sNotifyObjCInit实际上调用的是libobjc里的load_images方法,作用是调用所有类的+load方法和加载category

doInitialization
  • 点击进入doInitialization

这里的doImageInitdoModInitFunctions这两个方法是关键。我们先看doImageInit

  • 点击进入doImageInit


    这里主要进行动态库的初始化工作,值得注意的是libSystem.dylib必须第一个初始化。

  • 点击进入doModInitFunctions


    这里主要是加载C++方法,我们可以在上面的demo中打一个断点进行验证。

    C++方法调用之前,确实调用了doModInitFunctions

至此我们整个的runInitializers执行完毕了。大致流程是initializeMainExecutable ——> runInitializers ——> processInitializers ——> processInitializers ——> recursiveInitialization ——> notifySingle ——> load_images ——> call_load_methods ——> call_class_loads ——> call_category_loads ——> doInitialization ——> doImageInit ——> doModInitFunctions ——> main()

不知道大家有没有注意到notifySingle调用load_images时,必须先由_objc_init ——> _dyld_objc_notify_register ——> registerObjCNotifiers中才给了notifySingle赋值load_images

这个赋值必须要在notifySingle调用之前,那么_objc_init在什么时候调用了呢?这个我们并没有找到。

探索_objc_init调用时机

我们在objc-781源码中_objc_init方法打上断点。查看堆栈情况


我们发现在_objc_init之前,调用了libSystem_initializerlibdispatch_initializer方法。


接下来我们要分别查看libSystemlibdispatch的源码。开源库地址我下载的是Libsystem-1281.100.1libdispatch-1173.100.2

libSystem

  • 打开libSystem源码,搜索libSystem_initializer
static void
libSystem_initializer(int argc,
              const char* argv[],
              const char* envp[],
              const char* apple[],
              const struct ProgramVars* vars)
{

    _libSystem_ktrace0(ARIADNE_LIFECYCLE_libsystem_init | DBG_FUNC_START);

    __libkernel_init(&libkernel_funcs, envp, apple, vars);
    _libSystem_ktrace_init_func(KERNEL);

    __libplatform_init(NULL, envp, apple, vars);
    _libSystem_ktrace_init_func(PLATFORM);

    __pthread_init(&libpthread_funcs, envp, apple, vars);
    _libSystem_ktrace_init_func(PTHREAD);

    _libc_initializer(&libc_funcs, envp, apple, vars);
    _libSystem_ktrace_init_func(LIBC);

    // TODO: Move __malloc_init before __libc_init after breaking malloc's upward link to Libc
    __malloc_init(apple);
    _libSystem_ktrace_init_func(MALLOC);

#if TARGET_OS_OSX
    /*  */
    __keymgr_initializer();
    _libSystem_ktrace_init_func(KEYMGR);
#endif

    // No ASan interceptors are invoked before this point. ASan is normally initialized via the malloc interceptor:
    // _dyld_initializer() -> tlv_load_notification -> wrap_malloc -> ASanInitInternal

    _dyld_initializer();
    _libSystem_ktrace_init_func(DYLD);

    libdispatch_init();
    _libSystem_ktrace_init_func(LIBDISPATCH);

       ....已省略部分代码.....
}

发现在其中调用了__malloc_init、 _dyld_initializer、 libdispatch_init

libdispatch

  • 打开libdispatch源码,搜索libdispatch_init
void
libdispatch_init(void)
{
      ...已省略部分代码....
    dispatch_assert(sizeof(struct dispatch_apply_s) <=
            DISPATCH_CONTINUATION_SIZE);

    if (_dispatch_getenv_bool("LIBDISPATCH_STRICT", false)) {
        _dispatch_mode |= DISPATCH_MODE_STRICT;
    }

#if DISPATCH_USE_THREAD_LOCAL_STORAGE
    _dispatch_thread_key_create(&__dispatch_tsd_key, _libdispatch_tsd_cleanup);
#else
    _dispatch_thread_key_create(&dispatch_priority_key, NULL);
    _dispatch_thread_key_create(&dispatch_r2k_key, NULL);
    _dispatch_thread_key_create(&dispatch_queue_key, _dispatch_queue_cleanup);
    _dispatch_thread_key_create(&dispatch_frame_key, _dispatch_frame_cleanup);
    _dispatch_thread_key_create(&dispatch_cache_key, _dispatch_cache_cleanup);
    _dispatch_thread_key_create(&dispatch_context_key, _dispatch_context_cleanup);
    _dispatch_thread_key_create(&dispatch_pthread_root_queue_observer_hooks_key,
            NULL);
    _dispatch_thread_key_create(&dispatch_basepri_key, NULL);
#if DISPATCH_INTROSPECTION
    _dispatch_thread_key_create(&dispatch_introspection_key , NULL);
#elif DISPATCH_PERF_MON
    _dispatch_thread_key_create(&dispatch_bcounter_key, NULL);
#endif
    _dispatch_thread_key_create(&dispatch_wlh_key, _dispatch_wlh_cleanup);
    _dispatch_thread_key_create(&dispatch_voucher_key, _voucher_thread_cleanup);
    _dispatch_thread_key_create(&dispatch_deferred_items_key,
            _dispatch_deferred_items_cleanup);
#endif

#if DISPATCH_USE_RESOLVERS // rdar://problem/8541707
    _dispatch_main_q.do_targetq = _dispatch_get_default_queue(true);
#endif

    _dispatch_queue_set_current(&_dispatch_main_q);
    _dispatch_queue_set_bound_thread(&_dispatch_main_q);

#if DISPATCH_USE_PTHREAD_ATFORK
    (void)dispatch_assume_zero(pthread_atfork(dispatch_atfork_prepare,
            dispatch_atfork_parent, dispatch_atfork_child));
#endif
    _dispatch_hw_config_init();
    _dispatch_time_init();
    _dispatch_vtable_init();
    _os_object_init();
    _voucher_init();
    _dispatch_introspection_init();
}

最后我们看到libdispatch调用了_os_object_init

  • 继续搜索_os_object_init

    果然_objc_init就在其中,所有的猜想都得到了验证。

你可能感兴趣的:(iOS底层-16:应用程序的加载)