站在巨人的肩膀,乘后人之凉
分析OC代码,通用手段是用C++ rewrite,通过分析C++代码来了解OC compile time
先来份完整片段
如下OC代码 Alan.m
1 | #import <UIKit/UIKit.h> |
rewrite xcrun -sdk iphoneos clang -rewrite-objc -F UIKit -fobjc-arc -arch arm64 Alan.m
1 |
|
可以搜索关键字AlanWang来定位代码,当然往往都在最下面
不难发现c++重写后OC类被加了前缀
OBJC_CLASS_REF_$_
OBJC_CLASS_$_
OBJC_METACLASS_$_
查阅Swift源码可见的确是这些前缀。可见在rewrite过程中总会被加一些标识前缀,所以阅读时自行转化前缀
1 | // An Objective-C class reference reference. The symbol is private, so |
先来看两个类的初始化方法
extern “C” 的意思是把它当做C语言编译
比如C++支持重载,而C不支持
1 | extern "C" __declspec(dllexport) struct _class_t OBJC_CLASS_$_Human; |
1 | extern "C" __declspec(dllexport) struct _class_t OBJC_CLASS_$_AlanWang __attribute__ ((used, section ("__DATA,__objc_data"))) = { |
AlanWang 这个类被存在"__DATA, __objc_classlist,regular,no_dead_strip"
section中
_class_t
1 | struct _class_t { |
_class_t 的 isa 和 superclass 指向 _class_t
SETUP AlanWang
1 | static void OBJC_CLASS_SETUP_$_AlanWang(void ) { |
SETUP Human
1 | static void OBJC_CLASS_SETUP_$_Human(void ) { |
一图胜千言。
_class_ro_t
1 | struct _class_ro_t { |
ro readonly
1 | struct _objc_method { |
通观全文,不难发现并未找到alloc
方法的声明与实现
main函数中的实现
1 | int main(int argc, char * argv[]) { |
到运行时dyld通知回调时执行map_images时才会注册alloc方法
运行时体系结构(Runtime Architecture)
具有关键的一些方面
- 提供了用于启动和执行程序的工具
- 指定了代码和数据怎样驻留在磁盘上—-也就是说,它制定了二进制格式。它还指定了编译器和相关工具必须怎样生成代码和数据
- 指定了怎样将代码和数据加载进内存中
- 指定了怎样解析指向外部库的引用
Mac OS X & iOS 运行时环境: Mach-O
对象的诞生
objc_getClass
1 | /*********************************************************************** |
提供类名到类映射的哈希表
1 | // Maps class name to Class, for in-use classes only. NXStrValueMapPrototype. |
1 | static Class getClass_impl(const char *name) |
1 | /*********************************************************************** |
_objc_init–>map_images –> map_images_nolock –> _read_images
objc_getClass:通过key= className 在哈希表(gdb_objc_realized_classes)找到 Class
sel_registerName:通过key= selectorName 在哈希表(namedSelectors )找到SEL
objc_msgSend: 参数为Class 与 SEL
objc_msgSend伪代码如下
1 |
|
在cache中找
在类的方法列表中找
消息转发
终于找到alloc方法了
运行时类的结构
目前最新的是objc4-779.1
objc.h
1 | /* |
Class 意为一个不透明的类型(指针) 代表一个Objective-C的类
id 意为类的实例的指针
SEL 意为一个不透明的类型 代表一个方法选择器
IMP 意为方法实现的函数指针
1 | /* |
如果有swift的代码。则可变的IMP是不透明指针,不可变则不是。
OBJC_TYPES_DEFINED` 宏定义是因为在另一处
objc-private.h
中也有声明 避免重复定义
1 | /* |
Meta 元类
宏
objc-config.h中的宏
__LP64__
字长为64位的操作系统
在 XL C/C++ V10.1 中添加了四个新宏:
_ILP32
__ILP32__
仅当对一个目标进行编译时,才可定义为 1,其中该目标的 long int,int 和指针使用的都是 32 位。否则, 不能对它定义。
_LP64
__LP64__
仅当对一个目标进行编译时,才可定义为 1,其中该目标的 long int 和指针使用的都是 64 位而 int 使用的是 32 位。否则, 不能对它定义。
与编译器运行的平台有关,利用clang可以查看对应的宏定义
1 | ➜ / clang -dM -E -x c /dev/null |
__arm__
32-bit ARM
1 |
|
SUPPORT_PACKED_ISA
isa 视为被数据包裹的可以掩码化的指针。
1 | // Define SUPPORT_PACKED_ISA=1 on platforms that store the class in the isa |
iOS中为1
SUPPORT_TAGGED_POINTERS
1 | // Define SUPPORT_TAGGED_POINTERS=1 to enable tagged pointer objects |
iOS中为1
SUPPORT_INDEXED_ISA
1 | // Define SUPPORT_INDEXED_ISA=1 on platforms that store the class in the isa |
iOS中为0
(__arm64__ && !__LP64__)
这个条件为了兼容32位系统?
SUPPORT_PACKED_ISA
1 | // Define SUPPORT_PACKED_ISA=1 on platforms that store the class in the isa |
iOS中为1
SUPPORT_NONPOINTER_ISA
可能在isa中存有额外的数据,支持所有平台
1 | // Define SUPPORT_NONPOINTER_ISA=1 on any platform that may store something |
SUPPORT_FIXUP
1 | // Define SUPPORT_FIXUP=1 to repair calls sites for fixup dispatch. |
SUPPORT_ZEROCOST_EXCEPTIONS
1 | // Define SUPPORT_ZEROCOST_EXCEPTIONS to use "zero-cost" exceptions for OBJC2. |
ISA_BITFIELD, RC_ONE, RC_HALF
前文提到iOS中SUPPORT_PACKED_ISA为1,所以iOS64位机中isa的相关字段定义如下
1 |
|
MSB: most significant bit 最高有效位(最左位)
LSB: least significant bit 最低有效位
ISA_MAGIC_VALUE
0x000001a000000001ULL
TaggedPointer
1 | #if (TARGET_OS_OSX || TARGET_OS_IOSMAC) && __x86_64__ |
只有64位 Mac 上是LSB 也就是看最低位
iOS中看MSB 也就是最高位(第63位)
1 |
流程
libdispatch_init
-> _os_object_init
-> _objc_init
—[_dyld_objc_notify_register(&map_images, load_images, unmap_image)] —>
dyld 通知 -> (map_images,load_images,unmap_image)
map_images->arr_init->
AutoreleasePoolPage::init(); -> 注册析构到对应线程
SideTableInit();->
realizeClassWithoutSwift
addRootClass(cls);
1 | 没有supercls的根类 |
从dyld进入到libSystem
先看ImageLoaderMachO::doModInitFunctions
1 | Initializer* inits = (Initializer*)(sect->addr + fSlide); |
通过这两句得到libSystem.B.dylib__mod_init_funcs
section inits数组,元素类型为Initializer* 函数指针类型
1 | // dyld/src/ImageLoader.h |
otool -l /usr/lib/libSystem.dylib
查看libSystem依赖的库
libSystem.dylib会加载libSystem.B.dylib
1 | ➜ ~ otool -L /usr/lib/libSystem.dylib |
查看load commands
1 | ➜ ~ otool -l /usr/lib/libSystem.B.dylib |
otool -l /usr/lib/libSystem.dylib`
1 | Section |
调用数组里的函数
1 | Initializer func = inits[j]; |
汇编片段
1 | 0x1090833a0 <+510>: movq -0x68(%rbp), %r8 |
此时控制台
1 | (lldb) register read r13 |
如果设置了DYLD_PRINT_INITIALIZERS
变量,还会打印
1 | dyld: calling initializer function 0x7fff4ff16773 in /usr/lib/libSystem.B.dylib |
对应代码
1 | if ( context.verboseInit ) |
小结:ImageLoaderMachO::doModInitFunctions方法中找到__mod_init_funcs section,通过地址加fSlide计算得到inits数组,然后调用libSystem.B.dylib`libSystem_initializer
ImageLoaderMachO::doModInitFunctions相关代码如下
SECTION_TYPE
1 | /* |
S_MOD_INIT_FUNC_POINTERS
1 | // darwin-xnu/EXTERNAL_HEADERS/mach-o/loader.h |
1 | void ImageLoaderMachO::doModInitFunctions(const LinkContext& context) |
ImageLoaderMachO::doModInitFunctions->libSystem.B.dylib`libSystem_initializer
从libSystem到libSystem
1 | libSystem.B.dylib`libSystem_initializer->libdispatch.dylib`libdispatch_init |
1 | void |
libdispatch_init中, dispatch进行一系列的初始化后,调用_os_object_init。
1 | void |
_os_object_init->_objc_init
从libdispatch进入到objc
1 | /*********************************************************************** |
进行一系列初始化
注册三个方法到dyld
registerObjCNotifiers->notifyBatchPartial
objc 接收到通知
1 | /*********************************************************************** |
1 | void |
1 | /*********************************************************************** |
1 | SEL sel_registerNameNoLock(const char *name, bool copy) { |
sel_init中的方法注册到全局静态哈希表namedSelectors,数据结构定义如下
1 | typedef struct { |
建表过程会调用到bootstrap
1 | static void bootstrap (void) { |
arr_init
1 | void arr_init(void) |
初始化AutoreleasePoolPage
初始化SideTable
_read_images
1 | /*********************************************************************** |
releaize
先看主体流程
1 | /*********************************************************************** |
1 | // addRootClass(cls); cls包含的类 |
可以看到,main函数前,有三个cls,NSObject, __NSAtom, NSProxy
1 | static void addRootClass(Class cls) |
addRootClass 根类数据结构的调整,可以发现为单链表,每一次把新结点通过头插法插入到表头(_firstRealizedClass)
1 | /*********************************************************************** |
addSubclass的方法与root类似,链表的表头为supercls中的firstSubclss.
整体结构如图 省略了尾(空)结点
追究细节,先看1处省略的代码
1 | ro = (const class_ro_t *)cls->data(); |
ro的声明const class_ro_t *ro;
ro = (const class_ro_t *)cls->data();
当看到data()的实现不免疑惑 返回rw*
类型,为什么要强转ro*
类型呢? 为什么能转呢?
1 | class_rw_t *data() { |
看下class_ro_t与class_rw_t的定义
1 | struct class_ro_t { |
1 | struct class_rw_t { |
ro 9*
8字节,rw 8*
8字节
这得从编译时说起
编译时还没有rw,看下ro的结构
1 | struct _class_ro_t { |
对比运行时objc_class的数据结构
1 | struct objc_class : objc_object { |
对比数据结构_class_t与objc_class 发现
objc_class的bits对应于_class_t的ro
就此清晰了,编译时确定的ro结构,在运行时初始化类时通过objc_class的data方法获取到有效的地址接下来会赋给新建的rw。
methodizeClass
1 | /*********************************************************************** |
method_list_t
1 | // Two bits of entsize are used for fixup markers. |
1 | /*********************************************************************** |
名词
malloc
c++的atuo 根据表达式推断出数据类型