AI智能
改变未来

iOS 应用程序加载

iOS 应用程序加载

  • 1. APP 加载分析
  • 1.1 动静态库
  • 1.2 加载过程
  • 2.`_dyld_start` 分析
    • 2.1 `reloadAllImages` 分析
    • 2.2 `initializeMainExecutable` 运行所有初始化程序
  • 总结
  • 1. APP 加载分析

    1.1 动静态库

    1. app依赖很多底层库,底层库是很什么?

      可执行的代码的二进制,可以被操作系统写入到内存

    2. 库分为几种?
      静态库: .a .lib,动态库: framework .so .dll

    3. 动静态库的区别?

      静态库:在链接阶段,会将汇编生成的目标文件与引用的库一起链接打包到可执行文件中,可能会重复编译多次

      动态库:程序编译并不会链接到目标代码中,而是程序运行时才被载入。
      优势:减少打包之后APP的大小,共享内容,节约资源,通过更新动态库,达到更新程序的目的。
      常见动态库:UIKit,libdispatch、libobj.dyld

    编译过程:

    动静态库示例:

    1.2 加载过程

    2.

    _dyld_start

    分析

    通过在

    + (void)load

    方法中添断点,查看调用堆栈,通过汇编查看,在程序启动的时,调用

    dyldbootstrap::start(dyld3::MachOLoaded const*, int, char const**, dyld3::MachOLoaded const*, unsigned long*)

    ,那么在这之前还有一系列操作,


    通过

    bt

    ,查看调用堆栈

    接下来我们分析一下

    _dyld_start

    ,查看

    dyld

    源码,全局搜索

    _dyld_start

    ,发现会跳转

    dyldbootstrap::start

    方法,为

    c++

    方法,


    全局搜索

    start(

    ,找到

    start

    方法,

    uintptr_t start(const struct macho_header* appsMachHeader, int argc, const char* argv[],intptr_t slide, const struct macho_header* dyldsMachHeader,uintptr_t* startGlue){// if kernel had to slide dyld, we need to fix up load sensitive locations// we have to do this before using any global variablesslide = slideOfMainExecutable(dyldsMachHeader);bool shouldRebase = slide != 0;#if __has_feature(ptrauth_calls)shouldRebase = true;#endifif ( shouldRebase ) {rebaseDyld(dyldsMachHeader, slide);}// allow dyld to use mach messagingmach_init();// kernel sets up env pointer to be just past end of agv arrayconst char** envp = &argv[argc+1];// kernel sets up apple pointer to be just past end of envp arrayconst 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 dyldrunDyldInitializers(dyldsMachHeader, slide, argc, argv, envp, apple);#endif// now that we are done bootstrapping dyld, call dyld\'s mainuintptr_t appsSlide = slideOfMainExecutable(appsMachHeader);return dyld::_main(appsMachHeader, appsSlide, argc, argv, envp, apple, startGlue);}

    进入

    dyld::_main()

    ,

    1. 环境变量相关处理,先 从环境中获取主可执行文件的

      cdHash

      ,

      checkEnvironmentVariables(envp);

      ,然后

      defaultUninitializedFallbackPaths(envp);

    2. 加载共享资源

      checkSharedRegionDisable((dyld3::MachOLoaded*)mainExecutableMH, mainExecutableSlide);

    3. dyld

      本身,添加到

      UUID

      列表

    1. reloadAllImages

    2. 运行所有初始化程序

      initializeMainExecutable()

    3. 通知监听

      dyld

      main

      ,然后进入

      main

      函数。

    2.1

    reloadAllImages

    分析

    1.实例化主程序

    CRSetCrashLogMessage(sLoadingCrashMessage);// instantiate ImageLoader for main executablesMainExecutable = instantiateFromLoadedImage(mainExecutableMH, mainExecutableSlide, sExecPath);gLinkContext.mainExecutable = sMainExecutable;gLinkContext.mainExecutableCodeSigned = hasCodeSignatureLoadCommand(mainExecutableMH);

    instantiateFromLoadedImage

    方法中

    先读取

    image

    ,

    addImage()

    读取加载镜像文件。

    1. 加载任何插入动态库
      loadInsertedDylib(*lib)

      ,读取为

      image

    1. 链接库,遍历代码
      iamge

      ,然后

      link

    link

    的过程中,会递归,插入任何动态加载的镜像文件

    2.2

    initializeMainExecutable

    运行所有初始化程序

    initializeMainExecutable

    方法:


    initializeMainExecutable

    方法中,

    runInitializers

    运行主程序的可执行文件,在

    runInitializers

    方法中,代码如下:

    processInitializers

    方法中,进行初始化准备,遍历

    iamge.count

    ,递归一个个开始初始化条件

    images[i]->recursiveInitialization

    ,代码如下:

    recursiveInitialization

    中,通过上下文的

    notifySingle

    方法,通知要进行单个镜像的初始化。

    notifySingle

    方法是怎么进行通知呢?

    通过查看

    notifySingle()

    方法,在此方法中,通过

    (*sNotifyObjCInit)(image->getRealPath(), image->machHeader());

    获取镜像文件的真实地址,如下图:

    那么

    sNotifyObjCInit

    指针地址又是什么时候传进来的呢?
    通过搜索发现

    sNotifyObjCInit

    是在

    registerObjCNotifiers()

    方法中进行赋值,而

    registerObjCNotifiers()

    方法又是在

    _dyld_objc_notify_register()

    方法中调用,通过传进来的

    init

    地址,回调函数,最终加载镜像文件

    那么

    _dyld_objc_notify_register

    方法是在什么时候调用,给

    sNotifyObjCInit

    赋值,让

    (*sNotifyObjCInit)(image->getRealPath(), image->machHeader());

    回调函数有意义,将镜像文件传递回去呢?接下来我们查看一下

    libobjc

    源码

    由此猜测:是在

    _objc_init()

    方法中,注册通知,传入函数地址,然后最终回调镜像文件的。那么这个回调地址又是怎样在

    dyld

    库和

    libobjc

    之间传递的呢?

    _objc_init()

    方法中断点,然后

    bt

    ,打印堆栈信息如下:

    这也从另一个方面进一步验证了上面所说的启动流程,当在

    recursiveInitialization

    方法中通过

    notifySingle

    进行通知要初始化镜像文件,此时,

    sNotifyObjCInit

    nil

    ,则调用无意义,无法完成通知。

    那么在

    notifySingle

    之后,调用了

    this->doInitialization(context)

    ,也验证了调用堆栈,

    doInitialization

    方法中,

    bool ImageLoaderMachO::doInitialization(const LinkContext& context){CRSetCrashLogMessage2(this->getPath());// mach-o has -init and static initializersdoImageInit(context);doModInitFunctions(context);CRSetCrashLogMessage2(NULL);return (fHasDashInit || fHasInitializers);}

    先调用

    doImageInit(context)

    ,确保

    libSystem

    库必须提前初始化完成。

    再调用

    doModInitFunctions()

    方法,必然会调用

    libSystem

    库中的

    libSystem_initializer

    方法。

    libSystem_initializer

    方法中调用

    libdispatch_init

    ,

    然后在

    libdispatch

    库中,调用

    libdispatch_init

    函数。

    libdispatch_init

    部分代码:

    _os_object_init

    代码:

    最终调用到

    libobjc

    库中的

    _objc_init

    的方法,调用

    _dyld_objc_notify_register

    ,传入地址给

    sNotifyObjCInit

    ,然后回调镜像文件。

    而先调用

    notifySingle

    方法,在调用

    doInitialization

    方法,而

    notifySingle

    方法中的

    sNotifyObjCInit

    指针是从

    _objc_init

    方法中

    _dyld_objc_notify_register(&map_images, load_images, unmap_image)

    load_images

    传递过去的,这也解释了C++函数和构造函数的调用在

    load

    方法之后的原因。

    总结

    1. APP加载过程:程序启动依次加载

      dyld

      libSystem

      libdispathc.dyld

      libobjc

      动态库,最终调用

      _objc_init()

      方法,在此方法中

      Runtime

      dyld

      注册回调函数,加载新的

      image

      ,执行

      map_images

      load_images

      imageLoader

      加载

      image

      ,调用

      main

      函数

    2. dyld

      中,

      __dyld_start

      链接开始,调用

      start()

      方法,调用

      dyld::_main()

      方法。在此方法中,

        环境变量相关处理,先获取可执行文件的

        cdHash

        checkEnvironmentVariables(envp)

        defaultUninitializedFallbackPaths(envp)
      • 加载共享缓存,通过
        checkSharedRegionDisable()

        验证共享缓存路径,然后

        mapSharedCache()

        ,加载共享缓存。

      • dyld

        本身,添加到

        UUID

        列表,

        addDyldImageToUUIDList()

      • 然后加载所有的镜像文件,
        reloadAllImages

      • 运行所有初始化程序
        initializeMainExecutable()
      • 通知监听
        dyld

        main

        ,然后进入

        main

        函数,

        notifyMonitoringDyldMain()

    3. reloadAllImages

      加载镜像文件的步骤:

        实例化主程序

        instantiateFromLoadedImage()

        ,内核会映射到主要可执行文件中,我们需要为映射到主可执行文件的文件,创建

        ImageLoader

        。在此方法中,然后读取

        image

        ,然后

        addImage()

        读取加载镜像文件。会先在

        instantiateMainExecutable()

        中,会确认此mach-o文件中是否具有压缩的

        LINKEDIT

        以及段数。

      • 加载插入任何动态库
        loadInsertedDylib(*lib)

        ,将其读取为镜像文件

        iamge

      • 链接库。先遍历,读取
        image

        ,然后

        link

        。在

        link

        中,递归插入动态加载的镜像文件。

    4. initializeMainExecutable()

      运行所有初始化程序步骤:

        runInitializers()
      • processInitializers

        初始化准备。

      • processInitializers

        中,遍历

        iamge.count

        ,递归一个个开始初始化条件

        images[i]->recursiveInitialization

      • 在递归开始初始化条件中
        recursiveInitialization

        ,通过

        notifySingle

        方法,对单个镜像通知开始初始化。获取镜像文件的真实地址

        (*sNotifyObjCInit)(image->getRealPath(), image->machHeader())

        , 而

        notifySingle

        中的

        sNotifyObjCInit

        是在

        objc_init()

        中注册传递过来的,所以只有当

        objc_init()

        调用时,重新加载

        image

      • notifySingle

        方法之后,遍历初始化

        this->doInitialization(context)
      • doInitialization

        方法中,先调用

        doImageInit(context)

        ,确保

        libSystem

        库必须提前初始化完成。再调用

        doModInitFunctions()

        方法,对 C++和构造函数处理,然后调用

        libSystem_initializer

        方法,调用

        libdispatch_init

        ,调用

        _os_object_init

        ,最终调用

        _objc_init

        方法。

      • _objc_init

        方法来注册回调函数,重新加载

        images

        ,执行

        map_images

        load_images

        imageLoader

        加载

        image

        ,调用

        main

        函数。

    • 点赞1
    • 收藏
    • 分享
    • 文章举报

    亮亮不想说话发布了18 篇原创文章 · 获赞 10 · 访问量 263私信关注

    赞(0) 打赏
    未经允许不得转载:爱站程序员基地 » iOS 应用程序加载