iOS底层原理之类,元类,根元类探索(上)
Posted WeaterMr
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了iOS底层原理之类,元类,根元类探索(上)相关的知识,希望对你有一定的参考价值。
ios底层原理之类,元类,根元类探索(上)
一,通过地址探索isa
中用来储存类信息的空间
// 0x00007ffffffffff8 对应的掩码,用来取出isa中关于类信息。
//生成一个简单的对象。
GoodOne *p = [GoodOne alloc];
NSLog(@"%@",p);
(lldb) x/4gx p
0x103864410: 0x011d800100008365 0x0000000000000000
0x103864420: 0x0000000000000000 0x0000000000000000
(lldb) p/x 0x011d800100008365 & 0x00007ffffffffff8
(long) $6 = 0x0000000100008360
(lldb) po $6
GoodOne
0x011d800100008365
对应isa 的存储空间
0x011d800100008365 & 0x00007ffffffffff8
通过isa
与 掩码
的 &
操作得到对应的类的储存空间。
这是第一层,我们通过对象取地址,得到成员变量 isa ,通过掩码 & 操作 得到,对象中关于类信息的存储空间。
(lldb) x/4gx 0x0000000100008360
0x100008360: 0x0000000100008338 0x00007fff80822008
0x100008370: 0x0000000100668bf0 0x0002802c00000003
(lldb) po 0x0000000100008338
GoodOne
(lldb) p/x 0x0000000100008338 & 0x00007ffffffffff8
(long) $13 = 0x0000000100008338
(lldb) po 0x0000000100008338
GoodOne
这是第二层,我们通过第一层得到了对应的类信息存储空间,那么我们继续通过 x/4gx 对类的内存空间进行分析。
因为我们并不知道类里面都有哪些东西,所以我们先po 0x0000000100008338
结果发现 是GoodOne
,什么情况?在第一层中我们po 0x0000000100008360
也是 GoodOne
我们发现两个完全不同的内存空间有两个 GoodOne
首先我们可以确认一点,两个GoodOne
肯定不是同一个东西。
(lldb) x/4gx 0x0000000100008338
0x100008338: 0x00007fff80821fe0 0x00007fff80821fe0
0x100008348: 0x0000000100504990 0x0002e03500000003
(lldb) po 0x00007fff80821fe0
NSObject
(lldb) p/x 0x00007fff80821fe0 & 0x00007ffffffffff8
(long) $16 = 0x00007fff80821fe0
这是第三层,即然是类的内存空间那么我们继续分析。我们重复同样的动作但是,结果发生了变化。
这时第二层类的ias 中关于类信息发生了变化, po 0x00007fff80821fe0 ---> NSObject
这里的类信息变成了NSObject
(lldb) x/4gx 0x00007fff80821fe0
0x7fff80821fe0: 0x00007fff80821fe0 0x00007fff80822008
0x7fff80821ff0: 0x00000001005046c0 0x0003e03100000007
(lldb) po 0x00007fff80821fe0
NSObject
(lldb) x/4gx 0x00007fff80821fe0
0x7fff80821fe0: 0x00007fff80821fe0 0x00007fff80822008
0x7fff80821ff0: 0x00000001005046c0 0x0003e03100000007
(lldb) po 0x00007fff80821fe0
NSObject
这是第四层,我们又连续按照同样的分析做了两次操作,结果都是NSObject。
第一个发现:
第一层的 第一个内存块为 : 0x011d800100008365
第二层的 第一个内存块为 : 0x0000000100008338
第三层的 第一个内存块为 : 0x00007fff80821fe0
第四层的 第一个内存块为 : 0x00007fff80821fe0
我们可以发现第三层以后的内存空间不变了,所以,无论你如何通过x/4gx
命令,他都是同一个东西,NSObject
第二个发现:
第一层的&操作:(lldb) p/x 0x011d800100008365 & 0x00007ffffffffff8 (long) $6 = 0x0000000100008360
第二层的&操作:(lldb) p/x 0x0000000100008338 & 0x00007ffffffffff8 (long) $13 = 0x0000000100008338
第三层的&操作:(lldb) p/x 0x00007fff80821fe0 & 0x00007ffffffffff8 (long) $16 = 0x00007fff80821fe0
第四层的&操作:(lldb) p/x 0x00007fff80821fe0 & 0x00007ffffffffff8 (long) $16 = 0x00007fff80821fe0
我们发现从第二层开始通过&
上掩码,内存空间并没有发生变化。
我们尝试多种对象类型进行分析:
NSObject *obg=[NSObject alloc];
NSLog(@"%@",obg);
(lldb) p obg
(NSObject *) $0 = 0x0000000103851db0
(lldb) x/4gx 0x0000000103851db0
0x103851db0: 0x011dffff80822009 0x0000000000000000
0x103851dc0: 0x0000000000000000 0x0000000000000000
(lldb) p/x 0x011dffff80822009 & 0x00007ffffffffff8
(long) $1 = 0x00007fff80822008
(lldb) po 0x00007fff80822008
NSObject //这是第一层
(lldb) x/4gx 0x00007fff80822008
0x7fff80822008: 0x00007fff80821fe0 0x0000000000000000
0x7fff80822018: 0x00000001007124e0 0x0002801000000003
(lldb) p/x 0x00007fff80821fe0 & 0x00007ffffffffff8
(long) $3 = 0x00007fff80821fe0
(lldb) po 0x00007fff80821fe0
NSObject //这是第二层
(lldb) x/4gx 0x00007fff80821fe0
0x7fff80821fe0: 0x00007fff80821fe0 0x00007fff80822008
0x7fff80821ff0: 0x000000010062a3d0 0x0003e03100000007
GoodTwo *p1 = [GoodTwo alloc];
NSLog(@"%@",p1);
(lldb) x/4gx 0x0000000103807f60
0x103807f60: 0x011d800100008315 0x0000000000000000
0x103807f70: 0x0000000000000000 0x0000000000000000
(lldb) p/x 0x011d800100008315 & 0x00007ffffffffff8
(long) $0 = 0x0000000100008310
(lldb) po 0x0000000100008310
第一层
GoodTwo
(lldb) x/4gx 0x0000000100008310
0x100008310: 0x00000001000082e8 0x0000000100008360
0x100008320: 0x00007fff20413aa0 0x0000802c00000000
(lldb) p/x 0x00000001000082e8 & 0x00007ffffffffff8
(long) $2 = 0x00000001000082e8
(lldb) po 0x00000001000082e8
第二层
GoodTwo
(lldb) x/4gx 0x00000001000082e8
0x1000082e8: 0x00007fff80821fe0 0x0000000100008338
0x1000082f8: 0x000000010074b210 0x0002e03500000003
(lldb) p/x 0x00007fff80821fe0 & 0x00007ffffffffff8
(long) $4 = 0x00007fff80821fe0
(lldb) po 0x00007fff80821fe0
第三层
NSObject
因为GoodTwo 是继承GoodOne ,但是你有没有发现上面的打印并没有GoodOne的身影,也就是父类。因为内存大小是有限的,所以第一块内存只发了当前的类信息,至于父类在紧接者的一块内存中。
(lldb) p/x 0x0000000100008360 & 0x00007ffffffffff8
(long) $6 = 0x0000000100008360
(lldb) po 0x0000000100008360
第一层
GoodOne
(lldb) x/4gx 0x0000000100008360
0x100008360: 0x0000000100008338 0x00007fff80822008
0x100008370: 0x000000010064e0b0 0x0001802c0000000f
(lldb) p/x 0x0000000100008338 & 0x00007ffffffffff8
(long) $8 = 0x0000000100008338
(lldb) po 0x0000000100008338
第二层
GoodOne
(lldb) x/4gx 0x0000000100008338
0x100008338: 0x00007fff80821fe0 0x00007fff80821fe0
0x100008348: 0x000000010064e3a0 0x0001e03500000007
(lldb) p/x 0x00007fff80821fe0 & 0x00007ffffffffff8
(long) $10 = 0x00007fff80821fe0
(lldb) po 0x00007fff80821fe0
第三层
NSObject
上面主要是分析isa 的工作流程。
二,通过偏移量取出内存空间的值
int c[4] = {1,2,3,4};
int *d = c;
NSLog(@"%p - %p - %p ",&c,&c[0],&c[1]);
NSLog(@"%p - %p - %p ",d,d+1,d+2);
for (int i = 0; i<4; i++) {
//(d+i) 取地址里面的值
int value = *(d+i);
NSLog(@"value = %d",value);
}
021-06-18 15:40:27.716313+0800 002-isa分析[85677:763618] 0x7ffeefbff470 - 0x7ffeefbff470 - 0x7ffeefbff474
2021-06-18 15:40:27.716616+0800 002-isa分析[85677:763618] 0x7ffeefbff470 - 0x7ffeefbff474 - 0x7ffeefbff478
2021-06-18 15:40:27.716648+0800 002-isa分析[85677:763618] value = 1
2021-06-18 15:40:27.716684+0800 002-isa分析[85677:763618] value = 2
2021-06-18 15:40:27.716718+0800 002-isa分析[85677:763618] value = 3
2021-06-18 15:40:27.716740+0800 002-isa分析[85677:763618] value = 4
先看看c中的数组数据通过内存偏移读取对应的内存数据。int *d表示的是一级指针,表示p所指向的地址里面存放的是一个int类型的值
从打印可以分析出,我们取数组中的值,本质上是通过首地址加上对应的偏移量,然后取出对应内存中的数据。
通过上面分析我们可以得到这样一个isa和对象与类的关系图:
苹果官方给出的流程图如下:
三,读取类中存储的数据信息
typedef struct objc_class *Class;
struct objc_class : objc_object {
objc_class(const objc_class&) = delete;
objc_class(objc_class&&) = delete;
void operator=(const objc_class&) = delete;
void operator=(objc_class&&) = delete;
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
Class getSuperclass() const {
我们可以看到 Class
是一个结构体指针
,在底层的实现是结构体 。如果我们想知道类的内存中的存储信息,那么我们必须要了解当前结构体的成员变量以及对应的大小
,这样才有利于我们进行内存平移取值。
第一个成员isa:8字节。
第二个成员superclass:结构体指针 8字节。
下面主要分析第三个成员cache:的大小。
struct cache_t {
private:
explicit_atomic<uintptr_t> _bucketsAndMaybeMask;
union {
struct {
explicit_atomic<mask_t> _maybeMask;
#if __LP64__
uint16_t _flags;
#endif
uint16_t _occupied;
};
explicit_atomic<preopt_cache_t *> _originalPreoptCache;
};
我们可以看到 cache_t
是一个结构体,两个成员变量, explicit_atomic<uintptr_t> _bucketsAndMaybeMask
对应8字节
; 和一个联合体,联合体互斥
,又因为explicit_atomic<preopt_cache_t *> _originalPreoptCache; 是一个 指针
所以联合体的大小为8字节,所以cache_t的大小为16字节
我们查看源码可以发现cache_t中有很多方法和 static constexpr
,因为方法和static不再堆区
所以不作为cache_t
的内存计算
第三个成员cache_t
:为16字节。
因而我们可以得出内存平移的大小 :8+8+16=32即 0x20
class_rw_t *data() const {
return bits.data();
}
(lldb) p/x GoodTwo.class
(Class) $3 = 0x00000001000083e8 GoodTwo
(lldb) p 0x00000001000083e8 + 0x20
(long) $4 = 4295001096
(lldb) p/x 0x00000001000083e8 + 0x20
(long) $5 = 0x0000000100008408
(lldb) po (class_data_bits_t*)0x0000000100008408
0x0000000100008408
(lldb) p (class_data_bits_t*)0x0000000100008408
(class_data_bits_t *) $7 = 0x0000000100008408
第一种方式通过点语法去取对应的class_rw_t list我们会发现最先面会有一个 Fix-it 让我们用
*$7->data() 箭头去取,当是指针的时候我们就用 ->去取对应的地址 。
如果是结构体 我们可以用点语法去取值 : $7->data()是获取对应的 data()的地址,*是取对应地址中的值。
(lldb) p *$7.data()
(class_rw_t) $8 = {
flags = 2148007936
witness = 0
ro_or_rw_ext = {
std::__1::atomic<unsigned long> = {
Value = 4295000544
}
}
firstSubclass = nil
nextSiblingClass = NSUUID
}
Fix-it applied, fixed expression was:
*$7->data()
第二种方式根据第一种方式的提示。
(lldb) p *$7->data()
(class_rw_t) $9 = {
flags = 2148007936
witness = 0
ro_or_rw_ext = {
std::__1::atomic<unsigned long> = {
Value = 4295000544
}
}
firstSubclass = nil
nextSiblingClass = NSUUID
}
第三种方式分布走,先拿内存地址,在取地址。
(lldb) p $7 ->data()
(class_rw_t *) $10 = 0x0000000100658610
(lldb) p *$10
(class_rw_t) $11 = {
flags = 2148007936
witness = 0
ro_or_rw_ext = {
std::__1::atomic<unsigned long> = {
Value = 4295000544
}
}
firstSubclass = nil
nextSiblingClass = NSUUID
}
(lldb)
struct class_rw_t {
// Be warned that Symbolication knows the layout of this structure.
uint32_t flags;
uint16_t witness;
#if SUPPORT_INDEXED_ISA
uint16_t index;
#endif
explicit_atomic<uintptr_t> ro_or_rw_ext;
Class firstSubclass;
Class nextSiblingClass;
private:
using ro_or_rw_ext_t = objc::PointerUnion<const class_ro_t, class_rw_ext_t, PTRAUTH_STR("class_ro_t"), PTRAUTH_STR("class_rw_ext_t")>;
const ro_or_rw_ext_t get_ro_or_rwe() const {
return ro_or_rw_ext_t{ro_or_rw_ext};
}
void set_ro_or_rwe(const class_ro_t *ro) {
ro_or_rw_ext_t{ro, &ro_or_rw_ext}.storeAt(ro_or_rw_ext, memory_order_relaxed);
}
我们可以发现 struct class_rw_t
是一个结构体,我们发现成员变量中并没有我们想要的信息,那么我们可以通过调用方法,这个结构体中除了成员就是方法。我们要找关于properties
字段相关的方法。
const property_array_t properties() const {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->properties;
} else {
return property_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProperties};
}
}
(lldb) p *$4->data()
(class_rw_t) $6 = {
flags = 2148007936
witness = 0
ro_or_rw_ext = {
std::__1::atomic<unsigned long> = {
Value = 4295000544
}
}
firstSubclass = nil
nextSiblingClass = NSUUID
}
(lldb) p $6.properties()
(const property_array_t) $7 = {
list_array_tt<property_t, property_list_t, RawPtr> = {
= {
list = {
ptr = 0x0000000100008310
}
arrayAndFlag = 4295000848
}
}
}
(lldb) p $7.list
(const RawPtr<property_list_t>) $8 = {
ptr = 0x0000000100008310
}
(lldb) p $8.ptr
(property_list_t *const) $9 = 0x0000000100008310
(lldb) p *$9
(property_list_t) $10 = {
entsize_list_tt<property_t, property_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 16, count = 2)
}
(lldb) p $10.get(0)
(property_t) $11 = (name = "name", attributes = "T@\\"NSString\\",C,N,V_name")
(lldb) p $10.get(1)
(property_t) $12 = (name = "number", attributes = "T@\\"NSString\\",&,N,V_number")
通过层层打印我们得到了对应的属性存放位置。但是我们并没有发现成员变量的身影。下面我们探索对应的方法又放在那里呢?
(lldb) p/x GoodTwo.class
(Class) $0 = 0x00000001000083e8 GoodTwo
(lldb) p (class_data_bits_t *)0x00000001000083e8 + 0x20
(class_data_bits_t *) $1 = 0x00000001000084e8
(lldb) p *$1->data()
error: Couldn't apply expression side effects : Couldn't dematerialize a result variable: couldn't read its memory
(lldb) p $1->data()
(class_rw_t *) $3 = nil
(lldb) p/x 0x00000001000083e8 + 0x20
(long) $4 = 0x0000000100008408
(lldb) p (class_data_bits_t *)0x0000000100008408
(class_data_bits_t *) $5 = 0x0000000100008408
(lldb) p *$5->data()
(class_rw_t) $6 = {
flags = 2148007936
witness = 0
ro_or_rw_ext = {
std::__1::atomic<unsigned long> = {
Value = 4295000544
}
}
firstSubclass = nil
nextSiblingClass = NSUUID
}
(lldb) p $6.methods()
(const method_array_t) $7 = {
list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
= {
list = {
ptr iOS底层原理之类,元类数据结构探索(下)