区块链技术博客
www.b2bchain.cn

图解原理4:一大坨汇编的objc_msgSend原理求职学习资料

本文介绍了图解原理4:一大坨汇编的objc_msgSend原理求职学习资料,有助于帮助完成毕业设计以及求职,是一篇很好的资料。

对技术面试,学习经验等有一些体会,在此分享。

一大坨汇编的objc_msgSend原理

相信打开这篇文章的都是有一定功底的同学,对于objc_msgSend是个啥玩意我就不过多介绍了。毕竟咱们都是高端玩家直接上才艺!
图解原理4:一大坨汇编的objc_msgSend原理

大图图解伺候

图解原理4:一大坨汇编的objc_msgSend原理

以上是整个objc_msgSend的原理大图图解,一下子看不懂没关系,可以结合下文的分析对照着看。有一张大图图解会有很多好处,可以让复杂的逻辑清晰化,可以让原理的记忆结构化,所以这也是作为作为图解的文章笔者觉得必须去做的事。

汇编入口源码图解

是的,你没有看错,我们即将去看objc_msgSend底层的汇编代码,因为整个objc_msgSend底层都是用汇编写的,没想到吧。

图解原理4:一大坨汇编的objc_msgSend原理

但是,不要慌,不要怕。汇编没有那么难理解,看懂后也就那么回事。不过看之前我们得知道objc_msgSend这个方法的目的是啥,其实说白了就是给objc_msgSend一个消息接收者,一个消息selector,一坨参数,然后让他去调用这个消息接收者的这个消息对应的方法。

所以他底层的实现如果让大家去写应该大体思路也是:

  1. 通过消息接收者取得它的isa
  2. 通过isa取得它的class类型
  3. 遍历class中的方法看是否命中
  4. 命中了就调用对应的imp

好,废话不多说了,让我们看看他的实现和我们猜想的是否一致。

    ENTRY _objc_msgSend     UNWIND _objc_msgSend, NoFrame      cmp p0, #0          // nil check and tagged pointer check #if SUPPORT_TAGGED_POINTERS     b.le    LNilOrTagged        //  (MSB tagged pointer looks negative) #else     b.eq    LReturnZero #endif     ldr p13, [x0]       // p13 = isa     GetClassFromIsa_p16 p13, 1, x0  // p16 = class LGetIsaDone:     // calls imp or objc_msgSend_uncached     CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached

上面的代码都在runtime源码的objc-msg-arm64.s中,因为咱们的手机基本都是arm64的架构,所以看这个就可以了。之前没接触过arm64汇编的同学肯定会一脸懵逼,卧槽,这都是啥,没关系,不着急,不懂可以学么,你听我一行一行给你“狡辩”。

图解原理4:一大坨汇编的objc_msgSend原理

ENTRY _objc_msgSend

这个其实就是咱们objc_msgSend在汇编层面的函数入口,没啥难度吧,不过这边要注意的是函数的参数也都会传递过来,只是不会显示的写出来,会通过寄存器一起过来。来,然后我们接着往下看。

UNWIND _objc_msgSend, NoFrame

忽略,无关痛痒,对,就是这么任性!
图解原理4:一大坨汇编的objc_msgSend原理

cmp    p0, #0          // nil check and tagged pointer check

cmp就是compare的意思,也就是比较,这里其实是在比较p0#0是否相等;#0其实就是十进制的整数0,那p0又是什么呢,我们来看下面这张图。

图解原理4:一大坨汇编的objc_msgSend原理

我在viewDidLoad方法里打了个断点,然后从命令行上通过register read把当前寄存器里的内容都读取出来,可以发现寄存器是从x0开始一直递增作为key来存放它的值的。所以在arm64汇编里p0就是第0个位置的寄存器的值。而第0个位置就是objc_msgSend第一个参数的值,而objc_msgSend第一个参数就是消息的接收者。

所以这行代码的意思其实是一个空判断,判断消息接收者和空做大小对比。是不是和咱们平时写其他代码一毛一样。

图解原理4:一大坨汇编的objc_msgSend原理

咱们接着往下看:

#if SUPPORT_TAGGED_POINTERS     b.le    LNilOrTagged        //  (MSB tagged pointer looks negative) #else     b.eq    LReturnZero #endif

在arm64下 SUPPORT_TAGGED_POINTERS被定义为1,因此这边会执行 b.le LNilOrTagged。那么b.le是啥意思呢,google后你可能会得到如下一个结果小于或等于跳转(Branch if Less than or Equal).跳转咱们能理解,就是跳转到LNilOrTagged方法中,这个小于或等于是谁和谁比较的结果,对,就是上面cmp p0, #0比较的结果,所以这几行代码咱们结合arm64编译可以这样看:

cmp    p0, #0 b.le    LNilOrTagged

翻译成伪代码语言就是:

if (p0 <= 0){   LNilOrTagged() }

如果要把编译宏以及函数入口加上就是:

function _objc_msgSend(p0,p1,p2,...){   #if SUPPORT_TAGGED_POINTERS         if (p0 <= 0){       LNilOrTagged()     }     #else       if (p0 == 0) {       LReturnZero()     }     #endif   ... //后续代码 }

怎么样,汇编是不是没有那么难。行,咱们先没必要去看LNilOrTaggedLReturnZero在干啥,因为不是咱们的主干分支,我们继续往下看,先把_objc_msgSend入口函数的代码分析完。

 ldr    p13, [x0]        // p13 = isa

这个很明显了(看注释~),就是咱们猜测的1.通过消息接收者取得它的isa.

GetClassFromIsa_p16 p13, 1, x0    // p16 = class

这个更明显了(继续看注释~),还是咱们猜测的2.通过isa取得它的class类型,是不是很简单。好,我们继续:

LGetIsaDone:     // calls imp or objc_msgSend_uncached     CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached``

嗯,还是我们猜测的遍历class中的方法看是否命中命中了就调用对应的imp,不过看名字,我们没有猜中的是这里肯定有缓存。

好,这个时候我们可以画出一张图了,毕竟是图解嘛,画图也是最好的总结方式:

图解原理4:一大坨汇编的objc_msgSend原理

相信看到这张图应该能把入口函数的逻辑理清楚了,后续咱们要重点展开的就是CacheLoopUp函数,这玩意也是整个objc_msgSend的核心和重点。

图解原理4:一大坨汇编的objc_msgSend原理

缓存查找源码图解

所谓缓存查找的源码就是CacheLoopUp函数的源码,这里我就不一把梭把代码都先copy上来了,占篇幅还没这个必要,我们一行一行去看。

.macro CacheLookup Mode, Function, MissLabelDynamic, MissLabelConstant

CacheLookup的方法定义,结合上面的分析,这边Mode传递的是NORMAL,FUNCTION 传递的是_objc_msgSend,MissLabelDynamic传递的是__objc_msgSend_uncached,暂时不用管他是干嘛的,先往下看。

mov    x15, x16            // stash the original isa

x16是什么,就是我们上文中的p16,也就是消息接收者的class,这里相当于 x15 = x16,把我们的class给保存起来。接着往下看

LLookupStartFunction:

方法位置标记位,暂时忽略。

看后续代码前我们先来看一段宏定义(后面要用到)

#define CACHE_MASK_STORAGE_OUTLINED 1 #define CACHE_MASK_STORAGE_HIGH_16 2 #define CACHE_MASK_STORAGE_LOW_4 3 #define CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS 4  #if defined(__arm64__) && __LP64__ #if TARGET_OS_OSX || TARGET_OS_SIMULATOR #define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS #else #define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_HIGH_16 #endif #elif defined(__arm64__) && !__LP64__ #define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_LOW_4 #else #define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_OUTLINED #endif

defined(__arm64__)是用来判断设备是否为arm64架构的设备,__LP64__是用来判断运行时是否为arm64的运行时。TARGET_OS_OSX只有macos下会有这个定义,TARGET_OS_SIMULATOR只有在模拟器下会有这个定义。因此我们可以看出我们最关心的iOS系统在arm64架构和运行包中CACHE_MASK_STORAGE的值是CACHE_MASK_STORAGE_HIGH_16.

好,我们继续回到CacheLoopup中的代码:

    // p1 = SEL, p16 = isa #if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS     ldr p10, [x16, #CACHE]              // p10 = mask|buckets     lsr p11, p10, #48           // p11 = mask     and p10, p10, #0xffffffffffff   // p10 = buckets     and w12, w1, w11            // x12 = _cmd & mask #elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16     ldr p11, [x16, #CACHE]          // p11 = mask|buckets #if CONFIG_USE_PREOPT_CACHES #if __has_feature(ptrauth_calls)     tbnz    p11, #0, LLookupPreoptFunction     and p10, p11, #0x0000ffffffffffff   // p10 = buckets #else     and p10, p11, #0x0000fffffffffffe   // p10 = buckets     tbnz    p11, #0, LLookupPreoptFunction #endif     eor p12, p1, p1, LSR #7     and p12, p12, p11, LSR #48      // x12 = (_cmd ^ (_cmd >> 7)) & mask #else     and p10, p11, #0x0000ffffffffffff   // p10 = buckets     and p12, p1, p11, LSR #48       // x12 = _cmd & mask #endif // CONFIG_USE_PREOPT_CACHES #elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4     ldr p11, [x16, #CACHE]              // p11 = mask|buckets     and p10, p11, #~0xf         // p10 = buckets     and p11, p11, #0xf          // p11 = maskShift     mov p12, #0xffff     lsr p11, p12, p11           // p11 = mask = 0xffff >> p11     and p12, p1, p11            // x12 = _cmd & mask #else #error Unsupported cache mask storage for ARM64. #endif

按照CACHE_MASK_STORAGE的值是CACHE_MASK_STORAGE_HIGH_16,我们需要关心的代码就精简成了:

    // p1 = SEL, p16 = isa     ldr p11, [x16, #CACHE]          // p11 = mask|buckets #if CONFIG_USE_PREOPT_CACHES #if __has_feature(ptrauth_calls)     tbnz    p11, #0, LLookupPreoptFunction     and p10, p11, #0x0000ffffffffffff   // p10 = buckets #else     and p10, p11, #0x0000fffffffffffe   // p10 = buckets     tbnz    p11, #0, LLookupPreoptFunction #endif     eor p12, p1, p1, LSR #7     and p12, p12, p11, LSR #48      // x12 = (_cmd ^ (_cmd >> 7)) & mask #else     and p10, p11, #0x0000ffffffffffff   // p10 = buckets     and p12, p1, p11, LSR #48       // x12 = _cmd & mask #endif // CONFIG_USE_PREOPT_CACHES

是不是简单很多了,我们接下来就只需要分析这边的逻辑即可。

// p1 = SEL, p16 = isa ldr    p11, [x16, #CACHE]          // p11 = mask|buckets

首先,可能细心的同学会发现,p16放的不是class么,怎么变成isa了,其实isa不就是class么,所以大家可以回去看下GetClassFromIsa_p16方法,可以看到mov p16, src,src就是p13,也就是isa。

OK,解释完p16=isa后我们来看ldr p11, [x16, #CACHE]这个是在干啥。首先,字面意思很好理解,对isa平移CACHE个字节,把结果保存在p11中。那么#CACHE是啥呢?

一大坨汇编的objc_msgSend原理

相信打开这篇文章的都是有一定功底的同学,对于objc_msgSend是个啥玩意我就不过多介绍了。毕竟咱们都是高端玩家直接上才艺!
图解原理4:一大坨汇编的objc_msgSend原理

大图图解伺候

图解原理4:一大坨汇编的objc_msgSend原理

以上是整个objc_msgSend的原理大图图解,一下子看不懂没关系,可以结合下文的分析对照着看。有一张大图图解会有很多好处,可以让复杂的逻辑清晰化,可以让原理的记忆结构化,所以这也是作为作为图解的文章笔者觉得必须去做的事。

汇编入口源码图解

是的,你没有看错,我们即将去看objc_msgSend底层的汇编代码,因为整个objc_msgSend底层都是用汇编写的,没想到吧。

图解原理4:一大坨汇编的objc_msgSend原理

但是,不要慌,不要怕。汇编没有那么难理解,看懂后也就那么回事。不过看之前我们得知道objc_msgSend这个方法的目的是啥,其实说白了就是给objc_msgSend一个消息接收者,一个消息selector,一坨参数,然后让他去调用这个消息接收者的这个消息对应的方法。

所以他底层的实现如果让大家去写应该大体思路也是:

  1. 通过消息接收者取得它的isa
  2. 通过isa取得它的class类型
  3. 遍历class中的方法看是否命中
  4. 命中了就调用对应的imp

好,废话不多说了,让我们看看他的实现和我们猜想的是否一致。

    ENTRY _objc_msgSend     UNWIND _objc_msgSend, NoFrame      cmp p0, #0          // nil check and tagged pointer check #if SUPPORT_TAGGED_POINTERS     b.le    LNilOrTagged        //  (MSB tagged pointer looks negative) #else     b.eq    LReturnZero #endif     ldr p13, [x0]       // p13 = isa     GetClassFromIsa_p16 p13, 1, x0  // p16 = class LGetIsaDone:     // calls imp or objc_msgSend_uncached     CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached

上面的代码都在runtime源码的objc-msg-arm64.s中,因为咱们的手机基本都是arm64的架构,所以看这个就可以了。之前没接触过arm64汇编的同学肯定会一脸懵逼,卧槽,这都是啥,没关系,不着急,不懂可以学么,你听我一行一行给你“狡辩”。

图解原理4:一大坨汇编的objc_msgSend原理

ENTRY _objc_msgSend

这个其实就是咱们objc_msgSend在汇编层面的函数入口,没啥难度吧,不过这边要注意的是函数的参数也都会传递过来,只是不会显示的写出来,会通过寄存器一起过来。来,然后我们接着往下看。

UNWIND _objc_msgSend, NoFrame

忽略,无关痛痒,对,就是这么任性!
图解原理4:一大坨汇编的objc_msgSend原理

cmp    p0, #0          // nil check and tagged pointer check

cmp就是compare的意思,也就是比较,这里其实是在比较p0#0是否相等;#0其实就是十进制的整数0,那p0又是什么呢,我们来看下面这张图。

图解原理4:一大坨汇编的objc_msgSend原理

我在viewDidLoad方法里打了个断点,然后从命令行上通过register read把当前寄存器里的内容都读取出来,可以发现寄存器是从x0开始一直递增作为key来存放它的值的。所以在arm64汇编里p0就是第0个位置的寄存器的值。而第0个位置就是objc_msgSend第一个参数的值,而objc_msgSend第一个参数就是消息的接收者。

所以这行代码的意思其实是一个空判断,判断消息接收者和空做大小对比。是不是和咱们平时写其他代码一毛一样。

图解原理4:一大坨汇编的objc_msgSend原理

咱们接着往下看:

#if SUPPORT_TAGGED_POINTERS     b.le    LNilOrTagged        //  (MSB tagged pointer looks negative) #else     b.eq    LReturnZero #endif

在arm64下 SUPPORT_TAGGED_POINTERS被定义为1,因此这边会执行 b.le LNilOrTagged。那么b.le是啥意思呢,google后你可能会得到如下一个结果小于或等于跳转(Branch if Less than or Equal).跳转咱们能理解,就是跳转到LNilOrTagged方法中,这个小于或等于是谁和谁比较的结果,对,就是上面cmp p0, #0比较的结果,所以这几行代码咱们结合arm64编译可以这样看:

cmp    p0, #0 b.le    LNilOrTagged

翻译成伪代码语言就是:

if (p0 <= 0){   LNilOrTagged() }

如果要把编译宏以及函数入口加上就是:

function _objc_msgSend(p0,p1,p2,...){   #if SUPPORT_TAGGED_POINTERS         if (p0 <= 0){       LNilOrTagged()     }     #else       if (p0 == 0) {       LReturnZero()     }     #endif   ... //后续代码 }

怎么样,汇编是不是没有那么难。行,咱们先没必要去看LNilOrTaggedLReturnZero在干啥,因为不是咱们的主干分支,我们继续往下看,先把_objc_msgSend入口函数的代码分析完。

 ldr    p13, [x0]        // p13 = isa

这个很明显了(看注释~),就是咱们猜测的1.通过消息接收者取得它的isa.

GetClassFromIsa_p16 p13, 1, x0    // p16 = class

这个更明显了(继续看注释~),还是咱们猜测的2.通过isa取得它的class类型,是不是很简单。好,我们继续:

LGetIsaDone:     // calls imp or objc_msgSend_uncached     CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached``

嗯,还是我们猜测的遍历class中的方法看是否命中命中了就调用对应的imp,不过看名字,我们没有猜中的是这里肯定有缓存。

好,这个时候我们可以画出一张图了,毕竟是图解嘛,画图也是最好的总结方式:

图解原理4:一大坨汇编的objc_msgSend原理

相信看到这张图应该能把入口函数的逻辑理清楚了,后续咱们要重点展开的就是CacheLoopUp函数,这玩意也是整个objc_msgSend的核心和重点。

图解原理4:一大坨汇编的objc_msgSend原理

缓存查找源码图解

所谓缓存查找的源码就是CacheLoopUp函数的源码,这里我就不一把梭把代码都先copy上来了,占篇幅还没这个必要,我们一行一行去看。

.macro CacheLookup Mode, Function, MissLabelDynamic, MissLabelConstant

CacheLookup的方法定义,结合上面的分析,这边Mode传递的是NORMAL,FUNCTION 传递的是_objc_msgSend,MissLabelDynamic传递的是__objc_msgSend_uncached,暂时不用管他是干嘛的,先往下看。

mov    x15, x16            // stash the original isa

x16是什么,就是我们上文中的p16,也就是消息接收者的class,这里相当于 x15 = x16,把我们的class给保存起来。接着往下看

LLookupStartFunction:

方法位置标记位,暂时忽略。

看后续代码前我们先来看一段宏定义(后面要用到)

#define CACHE_MASK_STORAGE_OUTLINED 1 #define CACHE_MASK_STORAGE_HIGH_16 2 #define CACHE_MASK_STORAGE_LOW_4 3 #define CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS 4  #if defined(__arm64__) && __LP64__ #if TARGET_OS_OSX || TARGET_OS_SIMULATOR #define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS #else #define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_HIGH_16 #endif #elif defined(__arm64__) && !__LP64__ #define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_LOW_4 #else #define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_OUTLINED #endif

defined(__arm64__)是用来判断设备是否为arm64架构的设备,__LP64__是用来判断运行时是否为arm64的运行时。TARGET_OS_OSX只有macos下会有这个定义,TARGET_OS_SIMULATOR只有在模拟器下会有这个定义。因此我们可以看出我们最关心的iOS系统在arm64架构和运行包中CACHE_MASK_STORAGE的值是CACHE_MASK_STORAGE_HIGH_16.

好,我们继续回到CacheLoopup中的代码:

    // p1 = SEL, p16 = isa #if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS     ldr p10, [x16, #CACHE]              // p10 = mask|buckets     lsr p11, p10, #48           // p11 = mask     and p10, p10, #0xffffffffffff   // p10 = buckets     and w12, w1, w11            // x12 = _cmd & mask #elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16     ldr p11, [x16, #CACHE]          // p11 = mask|buckets #if CONFIG_USE_PREOPT_CACHES #if __has_feature(ptrauth_calls)     tbnz    p11, #0, LLookupPreoptFunction     and p10, p11, #0x0000ffffffffffff   // p10 = buckets #else     and p10, p11, #0x0000fffffffffffe   // p10 = buckets     tbnz    p11, #0, LLookupPreoptFunction #endif     eor p12, p1, p1, LSR #7     and p12, p12, p11, LSR #48      // x12 = (_cmd ^ (_cmd >> 7)) & mask #else     and p10, p11, #0x0000ffffffffffff   // p10 = buckets     and p12, p1, p11, LSR #48       // x12 = _cmd & mask #endif // CONFIG_USE_PREOPT_CACHES #elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4     ldr p11, [x16, #CACHE]              // p11 = mask|buckets     and p10, p11, #~0xf         // p10 = buckets     and p11, p11, #0xf          // p11 = maskShift     mov p12, #0xffff     lsr p11, p12, p11           // p11 = mask = 0xffff >> p11     and p12, p1, p11            // x12 = _cmd & mask #else #error Unsupported cache mask storage for ARM64. #endif

按照CACHE_MASK_STORAGE的值是CACHE_MASK_STORAGE_HIGH_16,我们需要关心的代码就精简成了:

    // p1 = SEL, p16 = isa     ldr p11, [x16, #CACHE]          // p11 = mask|buckets #if CONFIG_USE_PREOPT_CACHES #if __has_feature(ptrauth_calls)     tbnz    p11, #0, LLookupPreoptFunction     and p10, p11, #0x0000ffffffffffff   // p10 = buckets #else     and p10, p11, #0x0000fffffffffffe   // p10 = buckets     tbnz    p11, #0, LLookupPreoptFunction #endif     eor p12, p1, p1, LSR #7     and p12, p12, p11, LSR #48      // x12 = (_cmd ^ (_cmd >> 7)) & mask #else     and p10, p11, #0x0000ffffffffffff   // p10 = buckets     and p12, p1, p11, LSR #48       // x12 = _cmd & mask #endif // CONFIG_USE_PREOPT_CACHES

是不是简单很多了,我们接下来就只需要分析这边的逻辑即可。

// p1 = SEL, p16 = isa ldr    p11, [x16, #CACHE]          // p11 = mask|buckets

首先,可能细心的同学会发现,p16放的不是class么,怎么变成isa了,其实isa不就是class么,所以大家可以回去看下GetClassFromIsa_p16方法,可以看到mov p16, src,src就是p13,也就是isa。

OK,解释完p16=isa后我们来看ldr p11, [x16, #CACHE]这个是在干啥。首先,字面意思很好理解,对isa平移CACHE个字节,把结果保存在p11中。那么#CACHE是啥呢?

一大坨汇编的objc_msgSend原理

相信打开这篇文章的都是有一定功底的同学,对于objc_msgSend是个啥玩意我就不过多介绍了。毕竟咱们都是高端玩家直接上才艺!
图解原理4:一大坨汇编的objc_msgSend原理

大图图解伺候

图解原理4:一大坨汇编的objc_msgSend原理

以上是整个objc_msgSend的原理大图图解,一下子看不懂没关系,可以结合下文的分析对照着看。有一张大图图解会有很多好处,可以让复杂的逻辑清晰化,可以让原理的记忆结构化,所以这也是作为作为图解的文章笔者觉得必须去做的事。

汇编入口源码图解

是的,你没有看错,我们即将去看objc_msgSend底层的汇编代码,因为整个objc_msgSend底层都是用汇编写的,没想到吧。

图解原理4:一大坨汇编的objc_msgSend原理

但是,不要慌,不要怕。汇编没有那么难理解,看懂后也就那么回事。不过看之前我们得知道objc_msgSend这个方法的目的是啥,其实说白了就是给objc_msgSend一个消息接收者,一个消息selector,一坨参数,然后让他去调用这个消息接收者的这个消息对应的方法。

所以他底层的实现如果让大家去写应该大体思路也是:

  1. 通过消息接收者取得它的isa
  2. 通过isa取得它的class类型
  3. 遍历class中的方法看是否命中
  4. 命中了就调用对应的imp

好,废话不多说了,让我们看看他的实现和我们猜想的是否一致。

    ENTRY _objc_msgSend     UNWIND _objc_msgSend, NoFrame      cmp p0, #0          // nil check and tagged pointer check #if SUPPORT_TAGGED_POINTERS     b.le    LNilOrTagged        //  (MSB tagged pointer looks negative) #else     b.eq    LReturnZero #endif     ldr p13, [x0]       // p13 = isa     GetClassFromIsa_p16 p13, 1, x0  // p16 = class LGetIsaDone:     // calls imp or objc_msgSend_uncached     CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached

上面的代码都在runtime源码的objc-msg-arm64.s中,因为咱们的手机基本都是arm64的架构,所以看这个就可以了。之前没接触过arm64汇编的同学肯定会一脸懵逼,卧槽,这都是啥,没关系,不着急,不懂可以学么,你听我一行一行给你“狡辩”。

图解原理4:一大坨汇编的objc_msgSend原理

ENTRY _objc_msgSend

这个其实就是咱们objc_msgSend在汇编层面的函数入口,没啥难度吧,不过这边要注意的是函数的参数也都会传递过来,只是不会显示的写出来,会通过寄存器一起过来。来,然后我们接着往下看。

UNWIND _objc_msgSend, NoFrame

忽略,无关痛痒,对,就是这么任性!
图解原理4:一大坨汇编的objc_msgSend原理

cmp    p0, #0          // nil check and tagged pointer check

cmp就是compare的意思,也就是比较,这里其实是在比较p0#0是否相等;#0其实就是十进制的整数0,那p0又是什么呢,我们来看下面这张图。

图解原理4:一大坨汇编的objc_msgSend原理

我在viewDidLoad方法里打了个断点,然后从命令行上通过register read把当前寄存器里的内容都读取出来,可以发现寄存器是从x0开始一直递增作为key来存放它的值的。所以在arm64汇编里p0就是第0个位置的寄存器的值。而第0个位置就是objc_msgSend第一个参数的值,而objc_msgSend第一个参数就是消息的接收者。

所以这行代码的意思其实是一个空判断,判断消息接收者和空做大小对比。是不是和咱们平时写其他代码一毛一样。

图解原理4:一大坨汇编的objc_msgSend原理

咱们接着往下看:

#if SUPPORT_TAGGED_POINTERS     b.le    LNilOrTagged        //  (MSB tagged pointer looks negative) #else     b.eq    LReturnZero #endif

在arm64下 SUPPORT_TAGGED_POINTERS被定义为1,因此这边会执行 b.le LNilOrTagged。那么b.le是啥意思呢,google后你可能会得到如下一个结果小于或等于跳转(Branch if Less than or Equal).跳转咱们能理解,就是跳转到LNilOrTagged方法中,这个小于或等于是谁和谁比较的结果,对,就是上面cmp p0, #0比较的结果,所以这几行代码咱们结合arm64编译可以这样看:

cmp    p0, #0 b.le    LNilOrTagged

翻译成伪代码语言就是:

if (p0 <= 0){   LNilOrTagged() }

如果要把编译宏以及函数入口加上就是:

function _objc_msgSend(p0,p1,p2,...){   #if SUPPORT_TAGGED_POINTERS         if (p0 <= 0){       LNilOrTagged()     }     #else       if (p0 == 0) {       LReturnZero()     }     #endif   ... //后续代码 }

怎么样,汇编是不是没有那么难。行,咱们先没必要去看LNilOrTaggedLReturnZero在干啥,因为不是咱们的主干分支,我们继续往下看,先把_objc_msgSend入口函数的代码分析完。

 ldr    p13, [x0]        // p13 = isa

这个很明显了(看注释~),就是咱们猜测的1.通过消息接收者取得它的isa.

GetClassFromIsa_p16 p13, 1, x0    // p16 = class

这个更明显了(继续看注释~),还是咱们猜测的2.通过isa取得它的class类型,是不是很简单。好,我们继续:

LGetIsaDone:     // calls imp or objc_msgSend_uncached     CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached``

嗯,还是我们猜测的遍历class中的方法看是否命中命中了就调用对应的imp,不过看名字,我们没有猜中的是这里肯定有缓存。

好,这个时候我们可以画出一张图了,毕竟是图解嘛,画图也是最好的总结方式:

图解原理4:一大坨汇编的objc_msgSend原理

相信看到这张图应该能把入口函数的逻辑理清楚了,后续咱们要重点展开的就是CacheLoopUp函数,这玩意也是整个objc_msgSend的核心和重点。

图解原理4:一大坨汇编的objc_msgSend原理

缓存查找源码图解

所谓缓存查找的源码就是CacheLoopUp函数的源码,这里我就不一把梭把代码都先copy上来了,占篇幅还没这个必要,我们一行一行去看。

.macro CacheLookup Mode, Function, MissLabelDynamic, MissLabelConstant

CacheLookup的方法定义,结合上面的分析,这边Mode传递的是NORMAL,FUNCTION 传递的是_objc_msgSend,MissLabelDynamic传递的是__objc_msgSend_uncached,暂时不用管他是干嘛的,先往下看。

mov    x15, x16            // stash the original isa

x16是什么,就是我们上文中的p16,也就是消息接收者的class,这里相当于 x15 = x16,把我们的class给保存起来。接着往下看

LLookupStartFunction:

方法位置标记位,暂时忽略。

看后续代码前我们先来看一段宏定义(后面要用到)

#define CACHE_MASK_STORAGE_OUTLINED 1 #define CACHE_MASK_STORAGE_HIGH_16 2 #define CACHE_MASK_STORAGE_LOW_4 3 #define CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS 4  #if defined(__arm64__) && __LP64__ #if TARGET_OS_OSX || TARGET_OS_SIMULATOR #define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS #else #define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_HIGH_16 #endif #elif defined(__arm64__) && !__LP64__ #define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_LOW_4 #else #define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_OUTLINED #endif

defined(__arm64__)是用来判断设备是否为arm64架构的设备,__LP64__是用来判断运行时是否为arm64的运行时。TARGET_OS_OSX只有macos下会有这个定义,TARGET_OS_SIMULATOR只有在模拟器下会有这个定义。因此我们可以看出我们最关心的iOS系统在arm64架构和运行包中CACHE_MASK_STORAGE的值是CACHE_MASK_STORAGE_HIGH_16.

好,我们继续回到CacheLoopup中的代码:

    // p1 = SEL, p16 = isa #if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS     ldr p10, [x16, #CACHE]              // p10 = mask|buckets     lsr p11, p10, #48           // p11 = mask     and p10, p10, #0xffffffffffff   // p10 = buckets     and w12, w1, w11            // x12 = _cmd & mask #elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16     ldr p11, [x16, #CACHE]          // p11 = mask|buckets #if CONFIG_USE_PREOPT_CACHES #if __has_feature(ptrauth_calls)     tbnz    p11, #0, LLookupPreoptFunction     and p10, p11, #0x0000ffffffffffff   // p10 = buckets #else     and p10, p11, #0x0000fffffffffffe   // p10 = buckets     tbnz    p11, #0, LLookupPreoptFunction #endif     eor p12, p1, p1, LSR #7     and p12, p12, p11, LSR #48      // x12 = (_cmd ^ (_cmd >> 7)) & mask #else     and p10, p11, #0x0000ffffffffffff   // p10 = buckets     and p12, p1, p11, LSR #48       // x12 = _cmd & mask #endif // CONFIG_USE_PREOPT_CACHES #elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4     ldr p11, [x16, #CACHE]              // p11 = mask|buckets     and p10, p11, #~0xf         // p10 = buckets     and p11, p11, #0xf          // p11 = maskShift     mov p12, #0xffff     lsr p11, p12, p11           // p11 = mask = 0xffff >> p11     and p12, p1, p11            // x12 = _cmd & mask #else #error Unsupported cache mask storage for ARM64. #endif

按照CACHE_MASK_STORAGE的值是CACHE_MASK_STORAGE_HIGH_16,我们需要关心的代码就精简成了:

    // p1 = SEL, p16 = isa     ldr p11, [x16, #CACHE]          // p11 = mask|buckets #if CONFIG_USE_PREOPT_CACHES #if __has_feature(ptrauth_calls)     tbnz    p11, #0, LLookupPreoptFunction     and p10, p11, #0x0000ffffffffffff   // p10 = buckets #else     and p10, p11, #0x0000fffffffffffe   // p10 = buckets     tbnz    p11, #0, LLookupPreoptFunction #endif     eor p12, p1, p1, LSR #7     and p12, p12, p11, LSR #48      // x12 = (_cmd ^ (_cmd >> 7)) & mask #else     and p10, p11, #0x0000ffffffffffff   // p10 = buckets     and p12, p1, p11, LSR #48       // x12 = _cmd & mask #endif // CONFIG_USE_PREOPT_CACHES

是不是简单很多了,我们接下来就只需要分析这边的逻辑即可。

// p1 = SEL, p16 = isa ldr    p11, [x16, #CACHE]          // p11 = mask|buckets

首先,可能细心的同学会发现,p16放的不是class么,怎么变成isa了,其实isa不就是class么,所以大家可以回去看下GetClassFromIsa_p16方法,可以看到mov p16, src,src就是p13,也就是isa。

OK,解释完p16=isa后我们来看ldr p11, [x16, #CACHE]这个是在干啥。首先,字面意思很好理解,对isa平移CACHE个字节,把结果保存在p11中。那么#CACHE是啥呢?

部分转自互联网,侵权删除联系

赞(0) 打赏
部分文章转自网络,侵权联系删除b2bchain区块链学习技术社区 » 图解原理4:一大坨汇编的objc_msgSend原理求职学习资料
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

b2b链

联系我们联系我们