一、内存平移
我们先通过下面的例子了解一下内存平移
Copy int nums [ 4 ] = { 1 , 2 , 3 , 4 };
NSLog (@ "数组nums的地址: %p \n" , & nums);
for ( int i = 0 ; i < 4 ; i ++ ) {
NSLog (@ "第 %d 个元素是 %d 地址是 %p " , i + 1 , nums [i] , & nums [i]);
}
NSLog (@ "\n" );
int * numsPointer = nums;
NSLog (@ "数组nums的指针: %p \n" , numsPointer);
for ( int i = 0 ; i < 4 ; i ++ ) {
NSLog (@ "偏移 %d ,取值 %d ,地址是 %p " , i , * (numsPointer + i) , (numsPointer + i));
}
输出:
Copy 2021 -0 6 - 22 23 : 27 : 02.344397 + 0800 Class [ 15488 : 5517067 ] 数组nums的地址: 0x 16f00d3a0
2021 -0 6 - 22 23 : 27 : 02.344734 + 0800 Class [ 15488 : 5517067 ] 第1个元素是11地址是0x16f00d3a0
2021 -0 6 - 22 23 : 27 : 02.344910 + 0800 Class [ 15488 : 5517067 ] 第2个元素是12地址是0x16f00d3a4
2021 -0 6 - 22 23 : 27 : 02.345776 + 0800 Class [ 15488 : 5517067 ] 第3个元素是13地址是0x16f00d3a8
2021 -0 6 - 22 23 : 27 : 02.346025 + 0800 Class [ 15488 : 5517067 ] 第4个元素是14地址是0x16f00d3ac
2021 -0 6 - 22 23 : 27 : 02.346173 + 0800 Class [ 15488 : 5517067 ]
2021 -0 6 - 22 23 : 27 : 02.346328 + 0800 Class [ 15488 : 5517067 ] 数组nums的指针: 0x 16f00d3a0
2021 -0 6 - 22 23 : 27 : 02.347933 + 0800 Class [ 15488 : 5517067 ] 偏移0,取值11,地址是0x16f00d3a0
2021 -0 6 - 22 23 : 27 : 02.348115 + 0800 Class [ 15488 : 5517067 ] 偏移1,取值12,地址是0x16f00d3a4 // 4字节
2021 -0 6 - 22 23 : 27 : 02.348425 + 0800 Class [ 15488 : 5517067 ] 偏移2,取值13,地址是0x16f00d3a8 // 4字节
2021 -0 6 - 22 23 : 27 : 02.349788 + 0800 Class [ 15488 : 5517067 ] 偏移3,取值14,地址是0x16f00d3ac // 4字节
nums
一个元素4字节,指针偏移1次4字节。所以这里可以通过从数组首地址开始偏移的方式进行取值操作。
同理对Class
的内存结构我们是否也能进行内存平移
来取值呢?
二、Class的结构内存计算
在Class的源码中我们发现核心的数据结构是这样的,暂时忽略其中的方法,因为不影响Class的内存结构。
方法都存在方法区
Copy struct objc_class : objc_object {
...
// Class ISA; objc_object 中的 ISA
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
...
}
三、objc_class: cache_t 的内存结构
我们忽略掉方法,剩下的核心数据结构如下
Copy 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;
};
}
3.1 小补充:LP64数据模型
在里面我们可以看到 __LP64__
,这里代表的是LP64数据模型
。现今所有64位的类Unix平台均使用LP64数据模型。
3.2 内部联合体的内存结构
联合体包含了两部分:结构体、指针。我们已知arm64下指针占用8字节。我们来看下结构体需要占用多少
结构体大小 8 字节,联合体大小为8字节
3.3 结构总结
cache_t大小为16字节
四、objc_class : class_data_bits_t 的内存结构
核心数据结构如下
Copy struct class_data_bits_t {
...
// Values are the FAST_ flags above.
uintptr_t bits;
...
}
我们发现内部只有一个指针bits
,那么如何通过它获取到类的详细信息呢?
我们发现这样一个函数,通过bits
与上FAST_DATA_MASK
可以获得class_rw_t
。和我们找ISA的过程比较类似。
那么它是做什么的呢?
Copy class_rw_t * data () const {
return ( class_rw_t * )(bits & FAST_DATA_MASK);
}
FAST_DATA_MASK在LP64
下的定义
Copy #if __LP64__
...
// data pointer
#define FAST_DATA_MASK 0x 00007ffffffffff8 UL
五、class_data_bits_t : class_rw_t
rw
: read-write
5.1 class_rw_t的内存结构
Copy struct class_rw_t {
// Be warned that Symbolication knows the layout of this structure.
// 标志位,如是否是元类,是否实现了等
uint32_t flags;
uint16_t witness;
explicit_atomic <uintptr_t> ro_or_rw_ext; //
Class firstSubclass;
Class nextSiblingClass;
}
5.2 一些重要函数
这里我们会看到一些眼熟的函数,在methods()
properties()
protocols()
中我们发现了另一个重要的类型class_rw_ext_t
Copy class_rw_ext_t * deepCopy ( const class_ro_t * ro) {
return extAlloc (ro , true );
}
const method_array_t methods () 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) -> methods;
} else {
return method_array_t { v . get <const class_ro_t *> ( & ro_or_rw_ext) -> baseMethods ()};
}
}
const property_array_t properties () const {
...
}
const protocol_array_t protocols () const {
...
}
六、class_data_bits_t : class_rw_t : class_rw_ext_t
Copy struct class_rw_ext_t {
DECLARE_AUTHED_PTR_TEMPLATE ( class_ro_t )
class_ro_t_authed_ptr <const class_ro_t > ro; // 成员变量
method_array_t methods;
property_array_t properties;
protocol_array_t protocols;
char * demangledName;
uint32_t version;
};
6.1 list_array_tt
method_array_t
/property_array_t
/protocol_array_t
都是继承于 list_array_tt
的,通过泛型来完成不同的定义。
源码核心部分:
Copy template < typename Element , typename List , template < typename > class Ptr >
class list_array_tt {
...
private :
union {
Ptr < List > list;
uintptr_t arrayAndFlag;
};
...
}
Copy class method_array_t :
public list_array_tt < method_t , method_list_t , method_list_t_authed_ptr > {...}
Copy class property_array_t :
public list_array_tt < property_t , property_list_t , RawPtr > {...}
Copy class protocol_array_t :
public list_array_tt < protocol_ref_t , protocol_list_t , RawPtr > {...}
6.2 class_ro_t
源码核心部分:
Copy struct class_ro_t {
...
const ivar_list_t * ivars; // 成员变量
...
}
七、LLDB调试Class内存结构
结构图
有这样的一个类
Copy @interface RYModel : NSObject
@ property (nonatomic , copy) NSString * name;
@ property (nonatomic , assign) NSInteger age;
@ property (nonatomic , assign) float height;
@ property (nonatomic , strong) RYModel * father;
@ property (nonatomic , assign) BOOL isBoy;
- ( void )dosomething;
- ( void )dosomethingWith:(NSString * )title;
+ ( void )classDoSomething;
- (NSString * )sayMyName;
@end
7.1 成员变量
获取class_data_bits_t
这里通过内存偏移的方式找到class_data_bits_t
,如前文所述:bits
所在位置的偏移为8+8+16=32字节。
那么我们理论上可以通过对象地址+0x20
计算出它的位置。
Copy (lldb) p / x RYModel . class
(Class) $ 0 = 0x 00000001000083e0 RYModel
(lldb) x / 4 gx 0x 00000001000083e0
0x 1000083e0 : 0x 00000001000083b8 0x 000000010036a140
0x 1000083f0 : 0x 000000010073c2d0 0x 0004803400000007
// 计算bits
(lldb) p / x 0x 1000083e0 + 0x 20
( long ) $ 1 = 0x 0000000100008400
// 这里转换一下类型便于后面继续调试
(lldb) p ( class_data_bits_t * ) 0x 0000000100008400
( class_data_bits_t * ) $ 2 = 0x 0000000100008400
通过 data()
方法获取 class_rw_t
Copy (lldb) p ( class_data_bits_t * ) 0x 0000000100008400
( class_data_bits_t * ) $ 4 = 0x 0000000100008400
(lldb) p $ 4 -> data ()
( class_rw_t * ) $ 5 = 0x 000000010073c040
通过 properties
获取属性列表容器
Copy (lldb) p $ 5 -> properties
( const property_array_t ) $ 6 = {
list_array_tt < property_t , property_list_t , RawPtr > = {
= {
list = {
ptr = 0x 0000000100008280
}
arrayAndFlag = 4295000704
}
}
}
Fix - it applied , fixed expression was:
$ 5 -> properties ()
取出 list
Copy (lldb) p $ 6. list
( const RawPtr < property_list_t > ) $ 7 = {
ptr = 0x 0000000100008280
}
取出 ptr
Copy (lldb) p $ 7. ptr
( property_list_t *const ) $ 8 = 0x 0000000100008280
取出 ptr
内的值
Copy (lldb) p * $ 8
( property_list_t ) $ 9 = {
entsize_list_tt < property_t , property_list_t , 0 , PointerModifierNop > = (entsizeAndFlags = 16 , count = 5 )
}
通过下标取出属性
Copy (lldb) p $ 9. get ( 0 )
( property_t ) $ 10 = (name = "name" , attributes = "T@\"NSString\",C,N,V_name" )
(lldb) p $ 9. get ( 1 )
( property_t ) $ 11 = (name = "age" , attributes = "Tq,N,V_age" )
(lldb) p $ 9. get ( 2 )
( property_t ) $ 12 = (name = "height" , attributes = "Tf,N,V_height" )
(lldb) p $ 9. get ( 3 )
( property_t ) $ 13 = (name = "father" , attributes = "T@\"RYModel\",&,N,V_father" )
(lldb) p $ 9. get ( 4 )
( property_t ) $ 14 = (name = "isBoy" , attributes = "Tc,N,V_isBoy" )
7.2 实例方法列表
前面一些相似的步骤我们省略掉
从 class_rw_t -> methods
获取 方法列表容器
Copy (lldb) p $ 5 -> methods
( const method_array_t ) $ 6 = {
list_array_tt < method_t , method_list_t , method_list_t_authed_ptr > = {
= {
list = {
ptr = 0x 00000001000080e8
}
arrayAndFlag = 4295000296
}
}
}
Fix - it applied , fixed expression was:
$ 5 -> methods ()
取出 list
Copy (lldb) p $ 6. list
( const method_list_t_authed_ptr < method_list_t > ) $ 14 = {
ptr = 0x 00000001000080e8
}
取出 ptr
Copy (lldb) p $ 14. ptr
( method_list_t *const ) $ 15 = 0x 00000001000080e8
取出 ptr
中的数据 method_list_t
Copy (lldb) p * $ 15
( method_list_t ) $ 16 = {
entsize_list_tt < method_t , method_list_t , 4294901763 , method_t ::pointer_modifier > = (entsizeAndFlags = 27 , count = 14 )
}
获取方法数量
Copy (lldb) p $ 16. count
( uint32_t ) $ 29 = 14
通过c++函数get()
与big()
单个获取类的实例方法
这里除了我们自己定义的一些实例方法,还看到了一些列属性自动生成的set、get
方法,以及析构函数
Copy (lldb) p $ 16. get ( 0 ) . big ()
( method_t ::big) $ 19 = {
name = "setFather:"
types = 0x 0000000100003f60 "v24@0:8@16"
imp = 0x 0000000100003cc0 (ObjcBuild` - [RYModel setFather:])
}
(lldb) p $ 16. get ( 1 ) . big ()
( method_t ::big) $ 20 = {
name = "father"
types = 0x 0000000100003f6b "@16@0:8"
imp = 0x 0000000100003ca0 (ObjcBuild` - [RYModel father])
}
... // 省略一些Set、get
(lldb) p $ 16. get ( 3 ) . big ()
( method_t ::big) $ 22 = {
name = "dosomething"
types = 0x 0000000100003f3b "v16@0:8"
imp = 0x 0000000100003b30 (ObjcBuild` - [RYModel dosomething])
}
(lldb) p $ 16. get ( 4 ) . big ()
( method_t ::big) $ 23 = {
name = "dosomethingWith:"
types = 0x 0000000100003f60 "v24@0:8@16"
imp = 0x 0000000100003b40 (ObjcBuild` - [RYModel dosomethingWith:])
}
...
(lldb) p $ 16. get ( 8 ) . big ()
( method_t ::big) $ 27 = {
name = ".cxx_destruct"
types = 0x 0000000100003f3b "v16@0:8"
imp = 0x 0000000100003d30 (ObjcBuild` - [ RYModel . cxx_destruct])
}
... // 省略一些Set、get
7.3 协议列表
前面一些相似的步骤我们省略掉
从 class_rw_t -> protocols
获取 协议列表容器
Copy (lldb) p $ 2 -> protocols ()
( const protocol_array_t ) $ 3 = {
list_array_tt <unsigned long , protocol_list_t , RawPtr > = {
= {
list = {
ptr = 0x 00000001000083b8
}
arrayAndFlag = 4295001016
}
}
}
取出 list
Copy (lldb) p $ 3. list
( const RawPtr < protocol_list_t > ) $ 4 = {
ptr = 0x 00000001000083b8
}
取出 ptr
Copy (lldb) p $ 4. ptr
( protocol_list_t *const ) $ 5 = 0x 00000001000083b8
取出 ptr
中的数据 protocol_list_t
Copy (lldb) p * $ 5
( protocol_list_t ) $ 6 = (count = 1 , list = protocol_ref_t [] @ 0x 00007f9a62cb8778 )
这里可以发现,里面的list中存了一个指针,并不是完整的一个数据结构。我们找到了这样的定义:
typedef uintptr_t protocol_ref_t; // protocol_t *, but unremapped
获取 protocol_t *
Copy (lldb) p ( protocol_t * )$ 6. list [ 0 ]
( protocol_t * ) $ 7 = 0x 00000001000088a0
读取数据
Copy (lldb) p * $ 7
( protocol_t ) $ 8 = {
objc_object = {
isa = {
bits = 4298547400
cls = Protocol
= {
nonpointer = 0
has_assoc = 0
has_cxx_dtor = 0
shiftcls = 537318425
magic = 0
weakly_referenced = 0
unused = 0
has_sidetable_rc = 0
extra_rc = 0
}
}
}
mangledName = 0x 0000000100003e71 "RYProtocol"
protocols = 0x 0000000100008310
instanceMethods = 0x 0000000100008328
classMethods = nil
optionalInstanceMethods = nil
optionalClassMethods = nil
instanceProperties = nil
size = 96
flags = 0
_extendedMethodTypes = 0x 0000000100008348
_demangledName = 0x 0000000000000000
_classProperties = nil
}
7.4 成员变量
前面一些相似的步骤我们省略掉
从 class_rw_t -> ro()
获取 成员变量列表容器
Copy (lldb) p $ 4 -> ro ()
( const class_ro_t * ) $ 18 = 0x 00000001000083d0
class_ro_t -> ivars
获取成员变量列表
Copy (lldb) p $ 18 -> ivars
( const ivar_list_t *const ) $ 19 = 0x 0000000100008570
(lldb) p * $ 19
( const ivar_list_t ) $ 20 = {
entsize_list_tt < ivar_t , ivar_list_t , 0 , PointerModifierNop > = (entsizeAndFlags = 32 , count = 5 )
}
从成员变量列表中获取成员变量
Copy (lldb) p $ 20. count
( const uint32_t ) $ 21 = 5
(lldb) p $ 20. get ( 0 )
( ivar_t ) $ 22 = {
offset = 0x 0000000100008770
name = 0x 0000000100003de7 "_isBoy"
type = 0x 0000000100003f47 "c"
alignment_raw = 0
size = 1
}
(lldb) p $ 20. get ( 1 )
( ivar_t ) $ 23 = {
offset = 0x 0000000100008778
name = 0x 0000000100003dee "_height"
type = 0x 0000000100003f49 "f"
alignment_raw = 2
size = 4
}
(lldb) p $ 20. get ( 2 )
( ivar_t ) $ 24 = {
offset = 0x 0000000100008780
name = 0x 0000000100003df6 "_name"
type = 0x 0000000100003f4b "@\"NSString\""
alignment_raw = 3
size = 8
}
(lldb) p $ 20. get ( 3 )
( ivar_t ) $ 25 = {
offset = 0x 0000000100008788
name = 0x 0000000100003dfc "_age"
type = 0x 0000000100003f57 "q"
alignment_raw = 3
size = 8
}
(lldb) p $ 20. get ( 4 )
( ivar_t ) $ 26 = {
offset = 0x 0000000100008790
name = 0x 0000000100003e01 "_father"
type = 0x 0000000100003f59 "@\"RYModel\""
alignment_raw = 3
size = 8
}
7.5 类方法
类方法我们需要到元类中进行查找
获取元类类信息
Copy (lldb) p / x RYModel . class
(Class) $ 67 = 0x 00000001000087c0 RYModel
(lldb) x / 4 gx 0x 00000001000087c0
0x 1000087c0 : 0x 0000000100008798 0x 000000010036a140
0x 1000087d0 : 0x 00000001010695c0 0x 0004803400000007
(lldb) p / x 0x 0000000100008798 & 0x 00007ffffffffff8 ULL
( unsigned long long ) $ 68 = 0x 0000000100008798
(lldb) p 0x 0000000100008798 + 0x 20
( long ) $ 70 = 4295002040
(lldb) p / x 0x 0000000100008798 + 0x 20
( long ) $ 71 = 0x 00000001000087b8
(lldb) p ( class_data_bits_t * ) 0x 00000001000087b8
( class_data_bits_t * ) $ 72 = 0x 00000001000087b8
(lldb) p $ 72 -> data ()
( class_rw_t * ) $ 73 = 0x 0000000101069510
(lldb) p $ 73 -> methods ()
( const method_array_t ) $ 74 = {
list_array_tt < method_t , method_list_t , method_list_t_authed_ptr > = {
= {
list = {
ptr = 0x 0000000100008398
}
arrayAndFlag = 4295000984
}
}
}
获取元类方法列表(类方法)
Copy (lldb) p $ 74. list
( const method_list_t_authed_ptr < method_list_t > ) $ 75 = {
ptr = 0x 0000000100008398
}
(lldb) p $ 75. ptr
( method_list_t *const ) $ 76 = 0x 0000000100008398
(lldb) p * $ 76
( method_list_t ) $ 77 = {
entsize_list_tt < method_t , method_list_t , 4294901763 , method_t ::pointer_modifier > = (entsizeAndFlags = 27 , count = 1 )
}
输出方法信息
Copy (lldb) p $ 77. get ( 0 ) . big ()
( method_t ::big) $ 78 = {
name = "classDoSomething"
types = 0x 0000000100003f3f "v16@0:8"
imp = 0x 0000000100003920 (ObjcBuild` + [RYModel classDoSomething])
}