美文网首页
第九节—objc_msgSend(一)方法快速查找流程

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

作者: L_Ares | 来源:发表于2020-10-26 03:16 被阅读0次

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

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

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

  • C语言中不可能写通过写一个函数来保留未知的参数,并且还要跳转到任一函数的指针,因为C语言是静态的。

  • 汇编是更接近机器指令的语言,而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
  • CACHE是16,因为一个__SIZEOF_POINTER__的大小是8位吧。

  • 我们是探索arm64下的objc_msgSend
    所以CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16

  • 所以CACHE_MASK_STORAGE_HIGH_16才是我们会走的arm64的架构宏吧。所以PTRSHIFT = 3

  • 还有要理解buckets是散列表,是一张表,bucket只是buckets中的一个成员,buckets里面可以有很多的bucket就像数组和元素一样。

然后继续看CacheLookup的汇编

先看官方给的注释 :

图12.png

一段一段的说明 :

1. 获取cache

ldr p11, [x16, #CACHE]              // p11 = mask|buckets
  • 这是获取p16寄存器上的cache_t,然后把cache_t中的mask|buckets放到p11寄存器上。

  • cache_t中高16位存mask,低48位存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

  • 第1句汇编的意思是 : p10 = p11 & 0x0000ffffffffffffp11里面不是有maskbuckets吗,我们不要mask,就要buckets,把buckets存放在p10寄存器。

  • 第2句汇编拆开看 :
    (1). p11, LSR #48是把p11也就是mask|buckets右移48位,就是抹掉buckets只留mask,存放在p11,也就是说p11 = mask
    (2). 然后,and p12, p1, p11的意思p12 = p1 & p11
    (3). p1在最开始说过了,是objc_msgSend的第二个参数selector,也可以说是_cmd,为了区分sel,我们就用_cmd来表示传进来的selector
    (4). 所以,p12 = _cmd & mask,也就是cache_t中说的传进来的sel的下标。

3. 拿到一个bucket

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

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

我们拆开看

  • p10bucketsp12_cmd & mask就是下标,PTRSHIFT上面说了是3。

  • (1 + PTRSHIFT)是4,LSL #(1+PTRSHIFT)就是哈希下标向左移4位,即1 << 4 = 16字节,是一个bucket_t结构体的大小吧,之前的章节看bucket_t的时候说过,bucket_t结构体在arm64下,第一个元素是imp,第二个元素是sel,这句话就是获得了一个bucket的大小。

  • p12, LSL #(1+PTRSHIFT),这里就是计算buckets首地址的实际偏移量。

  • add p12, p10, p12就是 p12 = p10 + p12,根据buckets首地址 + 首地址的实际偏移量,我们可以取到这个hash下标对应着的bucket

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不一样的时候跳进来了。

  • CheckMiss $0 :如果一直都找不到, 因为是normal ,跳转至__objc_msgSend_uncached,这个下面我会再重点说,这里先这么记着。

  • 比较p12p10p12是我们缓存中找到的bucketp10buckets,这个比较就是判断bucket是不是已经是buckets里面的第一个元素了。

  • 如果是第一个,跳到下面的3f

  • 如果不是第一个,就从最后一个元素开始往前一个找,p17p9就会存储再往前一个的impsel

  • bucket还不是buckets的第一个元素的情况下,循环的向上一个bucket找,然后继续做这个判断,循环会在p12 == p10的时候停止,跳入3f

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

相关文章

网友评论

      本文标题:第九节—objc_msgSend(一)方法快速查找流程

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