美文网首页
dyld和objc的关联

dyld和objc的关联

作者: 大橘猪猪侠 | 来源:发表于2020-10-13 18:05 被阅读0次

    在描述dyld和objc的关联之前,我们需要先了解什么是dyld,在APP启动过程中,会将程序中的静态库动态库编译打包成可执行文件Mach-o文件,而被编译的文件就由dyld负责连接、加载进入主程序。那么关于dyld的详细流程介绍,可以参考这个大神的博客

    其实也就是dyld将库、代码进行编译,加载到内存中。

    探究dyldobjc的关联,首先需要准备两份资料,分别是objc4-781源码dyld源码。他们都可以在苹果官方文档中下载,其中objc4-781源码是经过调试可编译的代码

    既然dyld会将库进行加载,那么dyld也会将objc加载到内存中,而objc代码中主要由objc_init进行初始化。

    先来看看objc_init方法的源码:

    iShot2020-10-13 15.30.00.png

    在上图中,是objc_init的方法实现,在里面调用了很多其他的方法。

    我们来看一下第一个函数environ_init();的代码实现,里面有很多环境变量的设置;
    看下图所示(部分代码):

    iShot2020-10-13 15.37.33.png

    可以看到红色方框中的代码,我们对里面的代码稍微进行修改:


    iShot2020-10-13 15.39.51.png

    将下面的for循环代码复制到上面,并将判断去掉,运行程序:

    iShot2020-10-13 15.41.53.png

    上图是打印的所有环境变量的信息(一部分)。

    下面来进行一个小测试,将OBJC_DISABLE_NONPOINTER_ISA的值设为YES,也就是说,这个值的设置,就杜绝生成NONPOINTER_ISA,就意味着都是为普通的isa

    iShot2020-10-13 15.44.00.png

    在设置OBJC_DISABLE_NONPOINTER_ISA环境变量之后,通过对类信息进行打印,得到的$1末尾为0:

    iShot2020-10-13 15.54.09.png

    下面去掉设置的环境变量,对类信息打印:


    iShot2020-10-13 15.54.48.png

    $1的末尾为1,也就是说去掉了环境变量,就直接影响到了NONPOINTER_ISA,让他不会生成NONPOINTER_ISA的结构.

    获取你还不理解上面的意思,现在换OBJC_PRINT_LOAD_METHODS环境变量设置输出,同时将上面设置的打印环境变量代码注释掉,运行程序:

    iShot2020-10-13 16.01.57.png

    看上图,可以看到系统的很多load方法名被打印了出来(只截取一部分输出结果)。
    而当我们自己在类中写load方法,它同样也会打印出来,那么这样设置的好处就显而易见,通过控制环境变量,来获取我们想要的信息。

    介绍完environ_init();函数之后,下面的一些部分函数我这边就不去一一探索了,跟dyldobjc的关联关系都不大。

    直接跳转到_dyld_objc_notify_register(&map_images, load_images, unmap_image)函数。这就是dyld注册的地方,也是重点中的重点。

    当我们想要点击这个函数的实现时,在objc源码中却只有声明,没有实现。
    &map_images引用函数类型,当map_images的内容发生改变,传入的值也会发生改变。
    images是镜像文件,不是图片的意思。
    map_images是映射镜像文件
    load_images是加载镜像文件

    下面我们就需要去dyld的源码中去继续探索,通过全局搜索_dyld_objc_notify_register,就可以找到这个方法的实现,如下图所示:

    iShot2020-10-13 16.32.52.png

    其中,mapped=map_imagesinit = load_imagesunmapped=unmap_image

    iShot2020-10-13 16.35.16.png

    因此,我们就可以去查看在objc中的_dyld_objc_notify_register方法中的参数实现是来源于dyld中的。

    sNotifyObjCMapped的实现:

    iShot2020-10-13 16.39.12.png

    sNotifyObjCInit的实现

    iShot2020-10-13 16.38.07.png
    因此,得到一个结论,首先dyld_start进行一系列初始化,弱引用,连接等等一系列操作之后开始主程序初始化,初始化所有库,之后就进入objc_init函数,写入注册函数,告诉dyld可以继续执行其他流程。

    下面我们来探索一下map_imagesload_images做了些什么:

    下图是map_images的源码实现,其中红色方框中是重点:

    iShot2020-10-13 16.48.23.png

    _read_images函数中的代码很多,有很多判断,都是对一些事情的处理:
    (部分代码)

    iShot2020-10-13 16.59.30.png iShot2020-10-13 16.59.53.png
    函数中代码实现的作用:

    1: 条件控制进行一次的加载
    2: 修复预编译阶段的 @selector 的混乱问题
    3: 错误混乱的类处理
    4:修复重映射一些没有被镜像文件加载进来的类
    5: 修复一些消息!
    6: 当我们类里面有协议的时候 : readProtocol
    7: 修复没有被加载的协议
    8: 分类处理
    9: 类的加载处理
    10 : 没有被处理的类 优化那些被侵犯的类

    下面选几个代码块进行探索,如下图所示,此函数的作用是修复预编译阶段的 @selector 的混乱问题,它不是简单的字符串匹配,而是带地址的字符串匹配,从图片中可以看到,sels[i]sel对于匹配字符串是一摸一样的,但是对地址来说,他们是不一样的。

    iShot2020-10-13 17.15.06.png

    下图是对预编译过程中类信息混乱进行处理,从lldb调试过程中,if (newCls != cls && newCls)判断并没有进入,因此,这是一个处理过程,当没有出现混乱时,就不会进入此判断。

    iShot2020-10-13 17.20.42.png 而有一个更有意思的东西,如下图所示 iShot2020-10-13 17.23.15.png

    当我们在newCls处打断点,在控制台打印cls的名字时,它只输出了地址,而执行完readClass之后,才可以打印cls的名字,可以看出,readClass是读取类信息。

    继续执行程序,当进入if (!noClassesRemapped())判断时,里面的代码并没有执行,因此,我们可以忽略这处代码块。

    之后的很多判断,里面的功能在上面都已经详细介绍了,当程序中没有涉及到相应的内容时,在_read_images并不会去执行相应的代码块。

    下面我们去探索一下readClass是如何去读取类信息的:

    iShot2020-10-13 17.38.13.png

    从上图可以看到,我们可以用readClass打印所有的方法名;在我们实际开发中,我们就可以利用这个判断某一个方法是否存在,如下图所示:

    iShot2020-10-13 17.41.16.png

    mangledName源码实现:

    iShot2020-10-13 17.48.04.png

    从上图可以得出,当类已经初始化完毕之后,才会从ro里面获取,不然就只能从Mach-o中的内存中去读取信息。

    当函数继续向下执行过程中,可以看到下图3228-3234行代码中,可以看到类信息的赋值,而当我们程序执行时,却并不会执行这一块区域的代码,直接跳转到3242行代码处,这是一个很神奇的地方,如果代码不能调试,你或许会认为这里会对读取类信息。


    iShot2020-10-13 17.43.46.png

    继续执行程序:


    iShot2020-10-13 17.52.49.png

    addClassTableEntry源码:

    iShot2020-10-13 17.52.22.png

    从上面两图得到的结论是:当所有数据还在Mach-o中,从Mach-o中读取信息到ClassTable表当中,这样在内存当中,就会读取到这个类。

    相关文章

      网友评论

          本文标题:dyld和objc的关联

          本文链接:https://www.haomeiwen.com/subject/oazvpktx.html