iOS的底层探究--------LLVM优化alloc

390 阅读5分钟

引子

在讲正题之前,先插入个小话题:App加载过程中,会加载dyld。那么dyld是什么了?

dyld就是一个动态链接器:加载所有的库和可执行文件

下面简单说下它的加载流程:

第一步:程序执行从_dyld_start开始

第二步:进入dyld:main函数

第三步:配置一些环境 :rebase_dyld 加载共享缓存

第四步: 实例化主程序 DYLD2 / DYLD3(闭包模式),这一步里面:

1、加载动态库(首先是插入的动态库)(主程序和动态库都会添加到allImages里面:loadAllImages

2、链接主程序、绑定符号(非懒加载、弱符号)等等 最关键的:初始化方法:initializeMainExecutable-> runInitializers -> processInitializers -> recursiveInitialization -> notifySingle

2.1、在notifySingle函数里面,会执行一个回调,此回调是_objc_init初始化时赋值的一个函数Load_images

2.1.1、在Load_images里面执行class_load_methods函数,还有call_class_loads函数,call_class_loads函数是循环调用各个类的load方法

2.1.2、后面再执行doModInitFunction函数,这个函数内部会调用全局C++对象的构造函数 attribute ((constructor))的C函数

第五步:返回主程序的入口函数。开始进入主程序的main函数!

那么这就是dyld的加载流程了。

如有需要dyld源码的童鞋,可以留言哦o( ̄︶ ̄)o

有了这对dyld的初步的了解,就可以进入正题了~ ~ ~ GO GO GO

LLVM优化alloc

在调试objc的源码中,(objc的源码在iOS的底层探究--------alloc 中就有,可以到那里获取) 声明了一个LGPerson类,然后在main.m里面调用。

我们在这里断点,查看alloc的执行 951D10A4-1483-4D69-80BB-3A8CAA81733B.png

紧接着进入到alloc方法里面,调用的是 _objc_rootAlloc方法,打上断点 F6F60AD8-52FE-4CD9-935A-5C06522D5AC3.png

再接着进入 _objc_rootAlloc 方法,在_objc_rootAlloc方法里面,调用的是callAlloc方法,但是在1956行,也是调用的callAlloc方法,打上断点 018E861F-DD6F-4973-A421-3D31E1803E5E.png

嘿嘿,做完准备工作后,就可以享受挖掘的雷区了(乐趣 ^_^)

现在进行断点跟踪咯,得看仔细咯。

A5C6B6FA-301F-4D72-A143-BA3500A48336.png

现在断点刚刚开始断在alloc即将调用的时候。正常情况,是直接进入到alloc的底层实现 也就是下面这个代码处,但实际上是这样子的吗?

+ (id)alloc {
    return _objc_rootAlloc(self);
}

我们接着让断点往下走一步。断点直接到了objc_alloc方法,而不是断在+(id)alloc方法上面。 6F53A239-D185-4C46-89A7-F3BE0FBBF6E5.png

当我们让断点往下再走一步,此时,断点在执行到+(id)alloc方法上 AD321D0D-4E52-4EB7-B1DC-15DB524FBECB.png

对象初始化是,执行alloc,竟然先到objc_alloc方法,再到+(id)alloc,这是为什么了?

既然我们知道allocobjc_alloc进行了编译的调节,但是在什么时候进行的,我们却不得而知。

文章最开始的时候,讲了下dlyd的加载流程。这里,我们先直接定位到类的加载映射里面(映射当前的镜像文件),找到_read_images方法 77CBEAB6-FBC0-48A1-A959-FADA851E8DB6.png

在这个方法里面,有处理映射的方法编号的代码块,就是方法里面的isa指针和imp的关系调整处理 06BCA23F-2052-445F-9325-C0E82AB3B046.png

我们进入_read_images方法里面。在这个方法里面,有段关于size_t修复代码,这个意味着我们的isa指针和imp的绑定,一定发生在这个size_t的修复之前,那么_read_images的执行,是在isa指针和imp的绑定之后,也就是在llvm编译之后。

这个修复的方法,就在同文件(objc-runtime-new.mm)8443行,从这里面,可以看出,当alloc出问题时,作出修复。把走歪的路线掰回正轨来。当msg->imp == &objc_msgSend_fixup时,如果msg->sel == @selector(alloc), msg->imp 就回对 (IMP)&objc_alloc进行一个标记;当然,如果没有出现问题,就直接执行objc_alloc方法,不用再进行修复。在_read_images方法里面,做了一层fixupMessageRef的修复。 906FD532-3AFA-42B7-B580-6746D893CFDE.png

刚刚提到过,_read_images的执行,是在isa指针和imp的绑定之后,也就是在llvm编译之后。

因为苹果对一些方式是做了hook处理的,就比如,alloc方式是对系统内存进行直接操作的,所以会对其做相应的监控,这个就是做了hook的拦截处理(本身也是一个消息机制)。当调用alloc的时候,不是直接走alloc的底层流程,而是先找objc_alloc,找完objc_alloc之后,才开始对下层进行标记;标记完成之后,才开始找alloc方法。

来,回到objc_alloc方法的断点处

B745FD78-43C2-4B2E-B155-ED972D85C63B.png

然后进入callAlloc方法,就来到callAlloc的实现,当再往下执行一步,就直接到了1940行,中间,并没有进入1932行代码,也没进入1938行代码。 148C7C23-F161-469A-93EE-B557ED4B796F.png 5323F1C6-E849-4DB8-BEDD-46A3CBE1A81A.png

在这里,又是往cls (LGPerson)objc_msgSend(发送)一个alloc消息,那么LGPerson类接收到消息之后,就会执行alloc方法,但是在前面执行objc_alloc方法时,已经标记了,所以这次接收到发送的alloc消息,就直接进入到正常的轨道中,直接调用alloc底层方法流程。

接着往下执行一步断点,那么就进入到了+ (id)alloc 方法里面,开始执行底层的流程了

总结一个流程图 (画得有点菜哈 😄)

0A982842-91A7-47A1-9CCE-356E51207160.png

这也就是callalloc方法,为什么会执行次的原因 A9396427-813C-4EC9-955C-3F14AB980647.png

到了此处,欧耶,大功告成,llvm优化alloc的底层探究,就完成了,有木有点收获啊,(不许没有啊<( ̄▽ ̄)/)

最后再附上一个llvmgithub地址:github.com/apple/llvm-…

感谢各位的光临~ ~ ~ ~ ~ ~ 71623057628_.pic.jpg