dyld加载流程

1,678 阅读6分钟
  • 准备工作

  • 编译过程

    • 编译步骤详解
      1. 预编译 替换宏,删除注释,展开头文件,产生.i文件
      2. 编译 生成汇编文件,.s文件
      3. 汇编 将汇编文件生成机器码文件 .o文件
      4. 链接 .o文件中有用到的动态库、静态库进行引用链接生成可执行文件
    • 编译过程流程图
  • 静态库和动态库

    • 静态库
      在程序编译时就会将静态库链接(这个链接的过程简单的讲就是合并,并且链接器只会将静态库中被使用的部分合并到可执行文件中去)到目标代码中,这里连接的过程会将静态库拷贝一份,程序在运行阶段,程序运行时将不再需要改静态库
    • 动态库
      动态库和静态库类似,但是它并不在链接时将需要的二进制代码都“拷贝”到可执行文件中,而是仅仅“拷贝”一些重定位和符号表信息,这些信息可以在程序运行时完成真正的链接过程
    • 动态库和静态库的区别
      静态库在编译过程中就将静态库的代码拷贝了一份合并到可执行文件中去,动态库在编译的过程仅仅是将一些重定位和符号表信息合并到可执行文件中去,如下图:
      这就引出了一下几种区别点:
      1. 磁盘内存占用情况不一样:静态库会占用更大的磁盘内存,应为静态库的使用是拷贝,而动态库是共享一个
      2. 可执行文件的大小不一样,静态库编译时会把涉及到的方法拷贝到可执行文件中去,而动态库仅仅是一些动态库的信息。
      3. 如果静态库中的代码变化,必须要重新走编译的过程,而动态库应为是运行时链接,所以不需要重新编译程序
      4. 依赖不一样:静态链接的可执行文件不需要依赖任何库(应为会把要用到的函数拷贝到可执行文件中去),动态链接的可执行文件必须依赖动态库(应为可执行文件中存储的仅仅是动态库的一些位置信息并不是动态库中的代码)
  • dyld的加载流程

    1. dyld简介
      dyld(the dynamic link editor)是苹果的动态链接器,是苹果操作系统一个重要组成部分,在系统内核做好程序准备工作之后,交由dyld负责余下的工作,作用其实就是用来连接动态库的
    2. 共享缓存机制
      系统的动态库基本上每个程序都会用到,如果没有共享缓存,每次程序启动都会将动态库加载到内存中去,会增加程序启动的时间,同时系统的运行也会变得缓慢。而共享缓存机制会在进程启动时被dyld映射到内存中,之后,当任何Mach-O映像加载时,dyld首先会检查该Mach-O映像与所需的动态库是否在共享缓存中,如果存在,则直接将它在共享内存中的内存地址映射到进程的内存地址空间。
    3. dyld的加载流程
      1. 通过打印堆栈信息查看load方法执行之前执行的方法,也就是找出程序启动的起始点如下图:可发现程序的起始是
      2. 再看_dyld_start方法
        打开dyld源码程序搜索_dyld_start找到如下代码
      3. dyldbootstrap::start方法源码探索
      4. dyld::_main方法源码探索
        1. 设置运行环境、环境变量
        2. 设置共享缓存
        3. 实例化主程序
          这一步主要是将Mach-O文件加载到内存中并实例化ImageLoader。最后将image加载到内存中去
          ImageLoaderImageLoader是抽象类,其子类负责把Mach-O文件实例化为image,当sniffLoadCommands()解析完以后,根据compressed的值来决定调用哪个子类进行实例化
          • compressed 是否是压缩类型的Mach-O文件如果Mach-O文件中由上图三个加载指令说明是压缩型的
          • segCount:根据 LC_SEGMENT_COMMAND 加载命令来统计段数量
            段数量不能超过255个
          • libCount 库的数量通过LC_LOAD_UPWARD_DYLIB指令来统计库的数量,库的数量不能超过4096
          • codeSigCmd 代码签名加载指令,通过LC_CODE_SIGNATURE指令获取
          • encryptCmd 段加密信息通过LC_ENCRYPTION_INFO指令和LC_ENCRYPTION_INFO_64指令获取
        4. 加载插入动态库
          这一步是加载环境变量DYLD_INSERT_LIBRARIES中配置的动态库,先判断环境变量DYLD_INSERT_LIBRARIES中是否存在要加载的动态库,如果存在则调用loadInsertedDylib()依次加载,代码如下:
        5. 链接主程序
          调用link()函数将实例化后的主程序进行动态修正,让二进制变为可正常执行的状态
        6. 链接插入动态库
        7. 弱符号绑定
        8. 执行初始化方法再看runInitializers方法源码processInitializers方法源码recursiveInitialization方法源码方法中初始化完毕之后会调用notifySingle方法通知,在全局搜索找到notifySingle源码继续搜索sNotifyObjCInit发现只有一处赋值再找registerObjCNotifiers方法调用的地方找到init继续全局搜索_dyld_objc_notify_register找到_dyld_objc_notify_register调用找init方法,发现在dyld源码中找不到_dyld_objc_notify_register方法的调用,可以再xcode下一个符号断点找到_dyld_objc_notify_register方法调用地方可以看到_dyld_objc_notify_register方法是在libdyld.dylib库中调用的,再去objc源码搜索找到这时候就找到了sNotifyObjCInit发现是loadImage方法再找loadImage方法发现在这里调用了load方法从而也验证了上文中的执行顺序,load方法会在c++方法和main函数之前执行冲堆栈信息中可以看出notifySingle()之后就是调用doInitialization()再看doInitialization方法源码doInitialization主要的核心方法有两个doImageInitdoModInitFunctions
          • doImageInit 镜像文件的初始化
          • doModInitFunctions 解析并执行_DATA_,mod_init_func这个section中保存的函数(这里保存的是全局C++对象的构造函数以及所有带__attribute((constructor)的C函数)下图可验证 最后再看_objc_init调用时机添加一个_objc_init的符号断点,然后再打印堆栈看结果发现doModInitFunctions方法执行完成之后还有libSystem_initializerlibdispatch_init_os_object_init,这时候再看libSystem_initializer的源码发现印证了上图的顺序libSystem_initializer执行完之后会执行libdispatch_init,再看libdispatch_init源码实现发现在libdispatch_init方法中调用了_os_object_init方法,再看_os_object_init方法实现最终找到了_objc_init方法的调用
        9. main函数
  • dyld加载流程图

    • 主要流程
    • dyld::_main流程
    • 初始化流程
    • sNotifyObjCInit注册函数流程