alloc底层探究补充
在上一章 对alloc初探 中我们知道了 alloc实质就是为我们开辟内存空间并且关联我们的类,那我们有没有发现什么问题呢?这中间有没有啥我们还没发现的或者被忽略的呢?我们不妨再来走一遍alloc
先贴上新的alloc流程图
进入正题:
通过debug-->debug workflow-->always show disaseembly查看汇编,
我们看到汇编代码
callq --跳转--> objc_alloc ???
为啥不是alloc呢?
我们给alloc和objc_alloc同时下好断点,看会先调用谁。。。
我们通过断点下一步,发现首先来到的是
objc_alloc,好尴尬😓
alloc---->objc_alloc 什么鬼? 我们都知道sel---->imp,通过方法符号sel找到imp,然后再通过imp去找到相应的函数实现;那这儿到底是什么情况呢,我们可以猜想一下是不是sel对应的imp被修改了呢?
objc源码搜索objc_alloc
带着这个问题我们只有通过objc源码搜索objc_alloc了,一个一个的找
我们通过不断的查找objc_alloc会有个发现
fixupMessageRef 字面意思是修复消息,那意思是当我们发送alloc消息转发时会被修复为objc_alloc?
我们继续跟踪搜索fixupMessageRef方法调用,发现在_read_images方法中会调用
其实我们应该可以在
_read_images方法说明上发现调用_read_images的方法为map_images_nolock,那我们继续跟踪搜索map_images_nolock
继而又发现了
map_images方法,那继续跟踪搜索map_images
我们对这个流程再正向来一次过后,在_read_images方法里去调用fixupMessageRef,而映射镜像的时候是发生在dyld之后的,那么在这么早的时候就开始sel-imp修复,说明在更早之前就开始imp绑定了,也就是LLVM编译阶段。
LLVM探索
我么可以通过上面的链接下载到LLVM源码,通过VSCode打开源码,然后通过搜索objc_alloc
我们通过一个一个的探索,你会发现
if (Sel.isUnarySelector() && Sel.getNameForSlot(0) == "alloc")
return CGF.EmitObjCAlloc(Receiver, CGF.ConvertType(ResultType));
如果sel是alloc然后就调用EmitObjCAlloc
Apple这边会对一些特殊的方法例如alloc init等,像这些对系统内存直接操作的方法Apple是会做监控的,所以直接就hook住,就例如alloc方法,不会直接去找alloc,而是先objc_alloc下好标记,然后再去找alloc:
这就能接着刚开始的时候对alloc和objc_alloc断点时先进入到objc_alloc,我们继续跟进去
来到这边过后,然后跟进去到
callAlloc方法中时,我们下一步发现不会走_objc_rootAllocWithZone这个方法,而是通过下面的objc_msgSend重新对LhkhPerson发送了一个alloc消息
再次下一步过后则来到了
alloc这个方法
为什么会是上面这种情况?
因为在第一次调用alloc的时候,LLVM会调用tryGenerateSpecializedMessageSend方法给alloc方法接收者标记,而当objc_alloc-->callAlloc-->objc_msgSend-->alloc这是后再来了一次alloc,LLVM已经对这个接收者标记了alloc方法,所以就会直接走的GenerateMessageSend方法,也就是直接去找alloc方法了。
通过上面的探索发现会出现这种情况,其实是LLVM编译时拦截优化,也就是当调用alloc方法的时候,LLVM会拦截,并通过方法给Receiver进行标记,当objc_alloc-->callAlloc-->objc_msgSend-->alloc后,LLVM发现已经标记了这个Receiver就会直接调用alloc;所以为什么callAlloc会走两次的原因也是这个。
alloc底层探究先到这,后续发现新问题在进行补充,内容有问题或者错误的希望大神能指正,相互学习😜!