第九节—objc_msgSend(一)方法快速查找流程

2020-10-26  本文已影响0人  L_Ares

本文为L_Ares个人写作,以任何形式转载请表明原文出处。

objc_msgSend可谓是Runtime中的重点,本节重点的重点是探索objc_msgSend的快速发送机制,即通过缓存查找进行消息转发,慢速的查找流程后面再说。

objc_msgSend的快速转发机制是通过汇编来实现的。选择汇编的原因 :

本节又需要使用到objc4-781源码,为什么又要用到它了呢?因为objc_msgSend的源码是在libobjc.A.dyld这个库里面的。

一、找到objc_msgSend

即然objc_msgSendOC方法调用的本质,那么我们就在main.m中调用OC的方法来进入objc_msgSend

main.m中代码 :

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...

        JDPerson *person = [[JDPerson alloc] init];

        [person studyWork];    //挂上断点
        
        NSLog(@"Hello, World!");
        
    }
    return 0;
}

然后打开xcode的汇编查看。打上勾。

图1.png

找到objc_msgSend。并且挂上断点,走到断点上。

图2.png

然后按住control,点击step into,进入objc_msgSend

图3.png 图4.png

现在就找到了objc_msgSend的汇编实现,并且这里也解释了,为什么要用[objc4-781源码],看最上面的libobjc.A.dyld,证明objc_msgSend是在这个库里面的。

即然找到了objc_msgSend的所在库,并且知道objc_msgSend的快速发送机制是汇编,我们就可以全局搜索,去找到它的实现。

图5.png

找我们的arm64架构下的汇编文件。

然后找到objc_msgSend的入口ENTRY,点进去,这里就是objc_msgSend快速转发机制的入口。

二、解析objc_msgSend的汇编

在解析objc_msgSend的汇编之前,我们要明确几个知识点 :

  • 在汇编里面,有个东西叫做寄存器,arm64架构下面有31个通用寄存器的存在。每一个都是64位,它们的标记是x0`x30`,也会看到`w0`w30,这是用来访问寄存器的低32位用的。
  • 寄存器的x0---x7位置存储的是函数入参的前8个参数。
  • 根据上一条可以得知,objc_msgSend传入的(id)self对应着x0,传入的SEL selector对应着x1
  • 寄存器的x0不止是第一个参数的位置,还是返回值在返回后存储的位置。

了解上上面的知识点之后,我们来看汇编。

1. 判断objc_msgSend的接受者是否为空

图6.png

接收者(receiver) :

就是objc_msgSend的第一个参数,还记得objc_msgSend的参数吗?

id selfSEL,这里的接收者就是那个target,一般情况下,我们传入的都是实例,也可以是类。

2. 获取类信息

还是看图1。

接受者也是类,是类就有相应的结构,就有isa,就可以通过isa中的shiftcls获取类的信息,获取到的类信息会被存储到p16寄存器上。

3. 怎么获取到的类信息

到这里,我们可以在本文件下搜索一下GetClassFromIsa_p16,看看它里面怎么从isa把类信息拿到存储到p16寄存器的。

GetClassFromIsa_p16 :

图7.png

特别熟悉的思路吧,在isa的章节,见过这个思路吧。

isa存储类信息的具体位置就是isa中的shiftcls,在isa的章节中,已经介绍过了如何可以取到shiftcls的类信息,可以通过平移地址,更简单的是使用掩码maskshiftcls与类无关的信息遮盖住,仅留出类信息的展示,然后得到类的信息。

然后通过这个isa中存储的类地址和掩码mask我们可以取得父类的信息。

就是isa & mask,具体流程点击上面蓝色的链接可以过去看。

所以现在isa被转移到了p16寄存器上吧,而且是只持有类信息的isa,没有其他杂七杂八的属性了。

然后我们回到主线,继续看。

4. 缓存查找方法实现

类信息获取完成后,就可以去找类中的方法信息。

详细的思路我写在注释里面了。看图

图8.png

下面我们看一下CacheLookup是怎么查找的。

5. 缓存查找方法的实现

CacheLookup :

图9.png

先看一下一会就要看到的宏定义都代表着什么 。这都是一会儿要用到的宏,先记住。

图9.1.png 图10.png 图11.png

然后继续看CacheLookup的汇编

先看官方给的注释 :

图12.png

一段一段的说明 :

1. 获取cache

ldr p11, [x16, #CACHE]              // p11 = mask|buckets

2. 拆分maskbuckets

#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16

and p10, p11, #0x0000ffffffffffff   // p10 = buckets

and p12, p1, p11, LSR #48       // x12 = _cmd & mask

#endif

3. 拿到一个bucket

add p12, p10, p12, LSL #(1+PTRSHIFT)
                     // p12 = buckets + ((_cmd & mask) << (1+PTRSHIFT))

就是p12 = p10 + p12 << (1 + PTRSHIFT)

我们拆开看

4. 把拿到的bucketimpsel放入寄存器

ldp p17, p9, [x12]      // {imp, sel} = *bucket

p17 = impp9 = sel

5. 判断_cmdbucket中的sel是否相等

1:  cmp p9, p1          // if (bucket->sel != _cmd)

6. _cmdsel不一样

    b.ne    2f          //     scan more

不一样的话就跳到2f这个函数中,2f会在下面写出来,前面带有2 :的就是。

7. _cmdsel一样

CacheHit $0         // call or return imp

_cmdsel一样,那么就命中了缓存,直接返回p17寄存器里面的imp就可以。

8. 2f函数

2:  // not hit: p12 = not-hit bucket

CheckMiss $0            // miss if bucket->sel == 0

cmp p12, p10        // wrap if bucket == buckets

b.eq    3f

ldp p17, p9, [x12, #-BUCKET_SIZE]!  // {imp, sel} = *--bucket

b   1b          // loop

这里就是2f_cmdsel不一样的时候跳进来了。

9. 3f函数

这里就是3f,走到这里就证明bucket已经是buckets的第一个元素了。

3:  // wrap: p12 = first bucket, w11 = mask
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
add p12, p12, p11, LSR #(48 - (1+PTRSHIFT))
                    // p12 = buckets + (mask << 1+PTRSHIFT)
#endif

官方给了注释,p12 = buckets + (mask << 1+PTRSHIFT),这就很明显了。

maskcache_t的那节说过,mask = buckets的大小 - 1,相当于buckets的最后一个元素的索引,那这就是将buckets首地址偏移到最后一个bucket上面。

10. 第二次查找

    ldp p17, p9, [x12]      // {imp, sel} = *bucket

1:  cmp p9, p1          // if (bucket->sel != _cmd)

    b.ne    2f          //     scan more

    CacheHit $0         // call or return imp

2:  // not hit: p12 = not-hit bucket
    CheckMiss $0            // miss if bucket->sel == 0

    cmp p12, p10        // wrap if bucket == buckets

    b.eq    3f

    ldp p17, p9, [x12, #-BUCKET_SIZE]!  // {imp, sel} = *--bucket

这里的步骤我就不说了吧,和上面的4,5,6,7,8步骤一模一样的吧,逐步的往上找,一直找一圈,直到再次走到3f,又把p12寄存器指向了最后一个bucket的位置上结束。

11. 结束

LLookupEnd$1:
LLookupRecover$1:
3:  // double wrap
    JumpMiss $0

.endmacro

这里也说明了,double wrap,做两次这样的循环。还能走到这里,证明还是没找到_cmd == sel吧,那就JumpMiss

放张图,把上面的内容串起来。其实是和cache_tinsert非常相似的,只不过这是查询,cache_insert是插入。

三、objc_msgSend快速查找机制流程图

objc_msgSend快速查找机制.png
上一篇 下一篇

猜你喜欢

热点阅读