objc_msgSend 一些小总结

objc_msgSend

从编译后的CPP文件开始

        Man * msgMan = [[Man alloc]init];
        
xcrun --sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp
===>
        Man *msgMan = ((Man *(*)(id, SEL))(void *)objc_msgSend)((id)
        ((Man *(*)(id, SEL))(void *)objc_msgSend)
        ((id)objc_getClass("Man"), sel_registerName("alloc")), 
        sel_registerName("init"));

简化一下 摸索了一下
===> 其实就是俄罗斯套娃 哈哈 目前使用了 函数指针的形式调用 
    以前是使用objc_msgSend(id,SEL)这样 前提必须设置Enbale Strict Checking of objc_msgSend Calls为NO 
    如果为YES 我们必须使用如下解释的函数指针的方式 直接跳过检查
    Man *msgMan = ((Man *(*)(id, SEL))(void *)objc_msgSend)
    ((id)((Man *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Man"), sel_registerName("alloc")), //id
    sel_registerName("init"));//SEL
再简化一下
===>
    id classMan = ((Man *(*)(id, SEL))(void *)objc_msgSend) //id
    ((id)objc_getClass("Man"), sel_registerName("alloc")) //SEL
    Man *msgMan =  ((Man *(*)(id, SEL))(void *)objc_msgSend)
    (classMan,  //id
     sel_registerName("init")) //SEL
    
最细解释
===>
    void *msgSendIMP = (void *)objc_msgSend;
    Man *(*funcMsgSend)(id,SEL) = msgSendIMP;
    //有这里可知 原代码的地方 使用了匿名函数指针(Man *(*)(id, SEL)) 
    //一开始看懵逼到 恍然大悟 
    Man *msgMan = funcMsgSend((id)objc_getClass("Man"),sel_registerName("alloc"));
    由此递推 alloc init 等其他方法
    
    //看不懂看这里
    //看看C++的函数指针相关的内容
    //IMP 相关 
    执行IMP void (*func)(id, SEL, id) = (void *)imp;  func(self, methodName,param);
    注意分析:如果方法没有传入参数时:void (*func)(id, SEL) = (void *)imp;   func(self, methodName);
    如果方法传入一个参数时:void (*func)(id, SEL,id) = (void *)imp;   func(self, methodName,param);
    如果方法传入俩个参数时:void (*func)(id, SEL,id,id) = (void *)imp;   func(self, methodName,param1,param2);

objc_msgSend

在开始objc_msgSend方法的学习时候,以及对平常使用的oc代码进行 消息发送的本质 底层方法是具体如何去理解,调用的进行总结。

现在开始真正对这个方法进行学习。
在objc/Source里可以看到 objc-msg-XXX.s(),说明这个方法和__dyld_start一样用汇编语言进行实现的。原因:提高消息发送的过程,使用汇编节省高级语言翻译过程,编译成机器码就可以了。
一开始我觉得看不懂汇编,看着里面的内容发现 汇编那么多个内容 你不懂我也不懂 那肯定要有注释 不然谁看懂 整挺好 源码内部真的有注释

objc-msg-simulator-x86_64.s
objc-msg-arm.s
objc-msg-arm64.s
objc-msg-i386.s
objc-msg-simulator-i386.s
objc-msg-x86_64.s

针对看objc-msg-arm64.s 相关的。
找到其入口_objc_msgSend 还有一些别后缀的 直接搜索定位

    ENTRY _objc_msgSend
    UNWIND _objc_msgSend, NoFrame
    //第一个为信息接受者 判断是否为0 如果是直接返回LReturnZero
    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 拿第一个的地址 也就是isa 
    GetClassFromIsa_p16 p13, 1, x0  // p16 = class 获取它的类 存在 p16里面
LGetIsaDone: //名如其目的 get完isa done 往下走
    // calls imp or objc_msgSend_uncached
    //查询缓存 模式?NORMAL Function当前调用的方法 MissLabelDynamic 估计是miss 没命中之后的动态调用?
    CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached 

接下来进入 CacheLookup 关于缓存查找

#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

去看新版的源码的时候 有点吓人 新旧变化那么多?
我删除一些暂为用到的 我考虑的是 arm64的

.macro CacheLookup Mode, Function, MissLabelDynamic, MissLabelConstant
    mov x15, x16            // stash the original isa
LLookupStart\Function:
    // p1 = SEL, p16 = isa
...
#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, LLookupPreopt\Function
    and p10, p11, #0x0000ffffffffffff   // p10 = buckets
#else
    and p10, p11, #0x0000fffffffffffe   // p10 = buckets
    tbnz    p11, #0, LLookupPreopt\Function
#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
...
#else
#error Unsupported cache mask storage for ARM64.
#endif

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

                        // do {
1:  ldp p17, p9, [x13], #-BUCKET_SIZE   //     {imp, sel} = *bucket--
    cmp p9, p1              //     if (sel != _cmd) {
    b.ne    3f              //         scan more
                        //     } else {
2:  CacheHit \Mode              // hit:    call or return imp
                        //     }
3:  cbz p9, \MissLabelDynamic       //     if (sel == 0) goto Miss;
    cmp p13, p10            // } while (bucket >= buckets)
    b.hs    1b

    // wrap-around:
    //   p10 = first bucket
    //   p11 = mask (and maybe other bits on LP64)
    //   p12 = _cmd & mask
    //
    // A full cache can happen with CACHE_ALLOW_FULL_UTILIZATION.
    // So stop when we circle back to the first probed bucket
    // rather than when hitting the first bucket again.
    //
    // Note that we might probe the initial bucket twice
    // when the first probed slot is the last entry.


#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
...
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
    add p13, p10, p11, LSR #(48 - (1+PTRSHIFT))
                        // p13 = buckets + (mask << 1+PTRSHIFT)
                        // see comment about maskZeroBits
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
...
#else
#error Unsupported cache mask storage for ARM64.
#endif
    add p12, p10, p12, LSL #(1+PTRSHIFT)
                        // p12 = first probed bucket

                        // do {
4:  ldp p17, p9, [x13], #-BUCKET_SIZE   //     {imp, sel} = *bucket--
    cmp p9, p1              //     if (sel == _cmd)
    b.eq    2b              //         goto hit
    cmp p9, #0              // } while (sel != 0 &&
    ccmp    p13, p12, #0, ne        //     bucket > first_probed)
    b.hi    4b
//__has_feature(ptrauth_calls): 是判断编译器是否支持指针身份验证功能
//ptrauth_calls 指针身份验证,针对arm64e架构;使用Apple A12或更高版本A系列处理器的设备
//(如iPhone XS、iPhone XS Max和iPhone XR或更新的设备)支持arm64e架构

这一大段 emmm 太难受了 但是通过其他办法来理解 对比新旧代码 以及在cache_t里面的插入缓存方法
在旧版中 还存在 这个find方法 在新版中已经没有了 集合合在insert里了。

    bucket_t *b = buckets();
    mask_t m = capacity - 1;
    mask_t begin = cache_hash(sel, m);
    mask_t i = begin;

    // Scan for the first unused slot and insert there.
    // There is guaranteed to be an empty slot.
    do {
        if (fastpath(b[i].sel() == 0)) {
            incrementOccupied();
            b[i].set<Atomic, Encoded>(b, sel, imp, cls());
            return;
        }
        if (b[i].sel() == sel) {
            // The entry was added to the cache by some other thread
            // before we grabbed the cacheUpdateLock.
            return;
        }
    } while (fastpath((i = cache_next(i, m)) != begin));

对比来学习 也就是说
在此查询的目的就是 在汇编的角度来拿出它的buckets 接着key值为:(mask_t)(value & mask); //value 相当sel 拿到之后进行遍历(mast转一圈又回到原处 来结束)这里查到到直接return 不插入 如果在查找过程 找到了 就应该调用 CacheHit \Mode // hit: call or return imp现在是insert过程 所以是return
找到了是0那就说明是空的插入set 但是在我们查找过程 为0就应该miss cbz p9, \MissLabelDynamic // if (sel == 0) goto Miss;
再回去一字一字的去看 汇编 发现 简单如斯

接着没命中(这才能继续往下走)MissLabelDynamic 还记得当初传进来的是啥? __objc_msgSend_uncached!!! 对的 定位到这个地方(在Mac 上用commonLine 断点调试 也可以看到这个)

STATIC_ENTRY __objc_msgSend_uncached
    UNWIND __objc_msgSend_uncached, FrameWithNoSaves

    // THIS IS NOT A CALLABLE C FUNCTION
    // Out-of-band p15 is the class to search
    
    MethodTableLookup //这个是一个不可调用的?C函数?可能后期改为C?
    TailCallFunctionPointer x17

    END_ENTRY __objc_msgSend_uncached
    
.macro MethodTableLookup
    
    SAVE_REGS MSGSEND

    // lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER)
    // receiver and selector already in x0 and x1
    mov x2, x16 //应该给我们准备 cls 以及后面的传入参数
    mov x3, #3 
    bl  _lookUpImpOrForward //接着调用这个_lookUpImpOrForward

    // IMP in x0
    mov  x17, x0

    RESTORE_REGS MSGSEND

.endmacro

简单看一下上面的源码 其实最终就调用到了.
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
checkIsKnowClass // 防止不是class类的? CFI 控制流完整性(Control Flow Integrity, CFI)的防御机制
cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE);
这就是调用我们的常用的initialize方法之处 盲猜 必须要标志位调一次而已 (cls->isInitialized).
接下来就进入到了 类和父类的方法列表查找了(旧版本很清晰 新版本 维护了一个for 无退出条件的循环 需要break)

  1. 先是判断有没有支持缓存优化 有的话就查询cache中缓存 cache_getImp(curClass, sel) 接着从curClass->cache.preoptFallbackClass再拿缓存。奇怪 如果这里一直拿不到 怎么出来到我的方法中拿呢?猜测是cache.preoptFallbackClass移动回来得到的不会进入这个缓存优化。
  2. Method meth = getMethodNoSuper_nolock(curClass, sel);获取目前class的方法列表 通过SEL查找对应的方法,查询到跳转done
  3. 在2中拿不到就 把curClass 设置为父类 接着查询父类的缓存,接着就是第二个循环里面 查询当前类(第一次的父类)的方法列表 继续循环下去
  4. 知道找到 缓存起来 log_and_fill_cache(cls, imp, sel, inst, curClass); 并且返回imp 或者 没找到break 跳出循环 走动态方法决议。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容