isa分析到元类(metalClass)
用来获取类信息的掩码,将isa的地址&
ISA_MASK
,可以解析得到类的信息,ISA_MASK
是系统定义的,不同架构不同,我在objc4源码内拿到的
x86_64与模拟器: define ISA_MASK0x00007ffffffffff8ULL
arm64: define ISA_MASK0x0000000ffffffff8ULL
新建一个LGPerson对象的实例p,通过lldb动态调试探索isa指向关系:
对象(object) – > 类(class) – > 元类(metaClass)
代码:
@interface LGPerson : NSObject
@end
@implementation LGPerson
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
// nonpointerIsa掩码0x00007ffffffffff8
LGPerson *p = [LGPerson alloc];
NSLog(@"%@",p);
// 0x0000000100008360 VS 0x0000000100008338
// LGPerson : 猜想 类会和我们的对象 无限开辟 内存不止有一个类
// 0x0000000100008338 不是类 它是什么
// 新的东西
// isa 的走位
// 对象 isa -> 类 isa -> 元类 isa -> 根元类 isa -> 根元类
// 根类 isa -> 根元类 isa
// 继承链
}
return 0;
}
复制代码
断点调试图:
调试打印:
(lldb) x p
0x1005b3870: 65 83 00 00 01 80 1d 01 00 00 00 00 00 00 00 00 e...............
0x1005b3880: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
(lldb) p/x p
(LGPerson *) $1 = 0x00000001005b3870
(lldb) x/4gx 0x00000001005b3870
0x1005b3870: 0x011d800100008365 0x0000000000000000
0x1005b3880: 0x0000000000000000 0x0000000000000000
(lldb) 0x011d800100008365 是 我们对象的 isa
error: '0x011d800100008365' is not a valid command.
(lldb) p 0x011d800100008365 & 0x00007ffffffffff8
(long) $2 = 4295000928
(lldb) p/x 0x011d800100008365 & 0x00007ffffffffff8
(long) $3 = 0x0000000100008360
(lldb) po 0x0000000100008360
LGPerson
(lldb) x/4gx 0x0000000100008360
0x100008360: 0x0000000100008338 0x00007fff808be008
0x100008370: 0x00007fff204afaa0 0x0000802c00000000
(lldb) po 0x0000000100008338 & 0x00007ffffffffff8
LGPerson
(lldb) p/x 0x0000000100008338 & 0x00007ffffffffff8
(long) $6 = 0x0000000100008338
(lldb) $6 != $3
复制代码
结论:
p/x p
打印LGPerson
的实例对象p的内存地址
x/4gx 0x00000001005b3870
格式化打印0x00000001005b3870
地址下的连续地址空间内存储的数据,拿到了LGPerson对象
的首地址
,也就是isa指针地址p/x
0x011d800100008365
&0x00007ffffffffff8
通过isa指针和ISA_MASK的与操作,解析了LGPerson类对象
的首地址
po 0x0000000100008360
打印这个地址数据,得到了LGPerson
x/4gx 0x0000000100008360
通过实例对象的isa指向类对象,我拿到了类对象内存地址0x0000000100008360
,格式化输出类对象
的内存地址p/x 0x0000000100008338 & 0x00007ffffffffff8
将类对象的首地址(isa指针地址)0x0000000100008338
和ISA_MASK
做与操作,得到了元类·的内存地址0x0000000100008338
0x0000000100008360
VS0x0000000100008338
虽然地址不同,但是打印都是LGPerson
,是类地址
与元类地址
的区别- LGPerson: 猜想 类会和我们的对象 无限开辟 内存不止有一个类
验证类与元类是否会无限开辟内存
在上面代码添加方法,在其中声明多个对象进行测试,看类地址会不会变化
代码:
//MARK: - 分析类对象内存存在个数
void lgTestClassNum(void){
Class class1 = [LGPerson class];
Class class2 = [LGPerson alloc].class;
Class class3 = object_getClass([LGPerson alloc]);
Class class4 = [LGPerson alloc].class;
NSLog(@"\n%p-\n%p-\n%p-\n%p",class1,class2,class3,class4);
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
// nonpointerIsa掩码0x00007ffffffffff8
LGPerson *p = [LGPerson alloc];
NSLog(@"%@",p);
// 0x0000000100008360 VS 0x0000000100008338
// LGPerson : 猜想 类会和我们的对象 无限开辟 内存不止有一个类
// 0x0000000100008338 不是类 它是元类
// 新的东西
// isa 的走位
// 对象 isa -> 类 isa -> 元类 isa -> 根元类 isa -> 根元类
// 根类 isa -> 根元类 isa
// 继承链
}
return 0;
}
复制代码
lldb调试:
2021-06-23 16:52:21.495202+0800 002-isa分析[12154:882912] <LGPerson: 0x1005b3870>
2021-06-23 16:52:21.495968+0800 002-isa分析[12154:882912]
0x100008360-
0x100008360-
0x100008360-
0x100008360
复制代码
结论:
LGPerson
的类对象
在内存只有一份,跟对象不一样!
烂苹果分析类与元类内存
进一步使用MatchOview烂苹果分析002-isa分析.exec
结论:
LGPerson的
类对象
与元类
在exec
中只有一份
元类(metaClass)的isa->根元类(rootMetaClass)
由上面一节可知道类的isa是元类,那元类的isa就是根元类
调试打印:
(lldb) x/4gx p
0x10064e270: 0x011d800100008365 0x0000000000000000
0x10064e280: 0x0000000000000000 0x0000000000000000
(lldb) p/x 0x011d800100008365 & 0x00007ffffffffff8
(long) $1 = 0x0000000100008360
(lldb) x/4gx 0x0000000100008360
0x100008360: 0x0000000100008338 0x00007fff808be008
0x100008370: 0x00007fff204afaa0 0x0000802c00000000
(lldb) p/x 0x0000000100008338 & 0x00007ffffffffff8
(long) $2 = 0x0000000100008338
(lldb) x/4gx 0x0000000100008338
0x100008338: 0x00007fff808bdfe0 0x00007fff808bdfe0
0x100008348: 0x00007fff204afaa0 0x0000e03500000000
(lldb) p/x 0x00007fff808bdfe0 & 0x00007ffffffffff8
(long) $3 = 0x00007fff808bdfe0
(lldb) po 0x00007fff808bdfe0
NSObject
(lldb)
复制代码
调试图:
结论:
x/4gx 0x0000000100008338
通过类对象
的isa指针
找到了元类对象地址
,格式化输出元类对象地址,得到了元类对象
的首地址,即isa指针0x0000000100008338
p/x 0x0000000100008338 & 0x00007ffffffffff8
同样,将isa指针地址和ISA_MASK做与操作,得到了一个地址0x00007fff808bdfe0
po 0x00007fff808bdfe0
输出地址0x00007fff808bdfe0
,打印了NSObject对象。- 根元类(
rootMetaClass
)就是NSObject
NSObject的根元类是
(lldb) p/x NSObject.class
(Class) $5 = 0x00007fff808be008 NSObject
(lldb) x/4gx 0x00007fff808be008
0x7fff808be008: 0x00007fff808bdfe0 0x0000000000000000
0x7fff808be018: 0x0000000100648050 0x0001801000000003
(lldb) p/x 0x00007fff808bdfe0 & 0x00007ffffffffff8
(long) $6 = 0x00007fff808bdfe0
(lldb) po 0x00007fff808bdfe0
NSObject
复制代码
继承链
验证自定义类与子类的superClass与isa
新建LGTeacher类继承于LGPerson类,通过方法验证类的继承关系跟上图一致:
subClass类
的根元类
与superClass类
的根元类
一致是NSObject类
subClass类
的父类
是superClass类
,而superClass类
的父类
是NSObject类
(superClass类继承与NSObject类)NSObject类
的父类是nil
,但是NSObject类
的元类
是NSObject根元类
;而NSObject根元类
的父类
是NSObject类
,它的根根元类就是自身!
代码:
@interface LGPerson : NSObject
@end
@implementation LGPerson
@end
@interface LGTeacher : LGPerson
@end
@implementation LGTeacher
@end
void lgTestNSObject(void){
// NSObject实例对象
NSObject *object1 = [NSObject alloc];
// NSObject类
Class class = object_getClass(object1);
// NSObject元类
Class metaClass = object_getClass(class);
// NSObject根元类
Class rootMetaClass = object_getClass(metaClass);
// NSObject根根元类
Class rootRootMetaClass = object_getClass(rootMetaClass);
NSLog(@"\n%p 实例对象\n%p 类\n%p 元类\n%p 根元类\n%p 根根元类",object1,class,metaClass,rootMetaClass,rootRootMetaClass);
// LGPerson元类
Class pMetaClass = object_getClass(LGPerson.class);
Class psuperClass = class_getSuperclass(pMetaClass);
NSLog(@"LGPerson的元类的父类: %@ - %p",psuperClass,psuperClass);
// LGTeacher -> LGPerson -> NSObject
// 元类也有一条继承链
Class tMetaClass = object_getClass(LGTeacher.class);
Class tsuperClass = class_getSuperclass(tMetaClass);
NSLog(@"LGTeacher的元类的父类: %@ - %p",tsuperClass,tsuperClass);
// NSObject 根类特殊情况
Class nsuperClass = class_getSuperclass(NSObject.class);
NSLog(@"NSObject的元类的父类: %@ - %p",nsuperClass,nsuperClass);
// 根元类 -> NSObject
Class rnsuperClass = class_getSuperclass(metaClass);
NSLog(@"NSObject根元类的父类: %@ - %p",rnsuperClass,rnsuperClass);
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 0x00007ffffffffff8
LGPerson *p = [LGPerson alloc];
NSLog(@"%@",p);
// 0x0000000100008360 VS 0x0000000100008338
// LGPerson : 猜想 类会和我们的对象 无限开辟 内存不止有一个类
// 0x0000000100008338 不是类 它是元类
// 新的东西
// isa 的走位
// 对象 isa -> 类 isa -> 元类 isa -> 根元类 isa -> 根元类
// 根类 isa -> 根元类 isa
// 继承链
lgTestNSObject();
}
return 0;
}
复制代码
2021-06-25 23:07:44.993380+0800 002-isa分析[33538:2348495]
0x1007042a0 实例对象
0x7fff808be008 类
0x7fff808bdfe0 元类
0x7fff808bdfe0 根元类
0x7fff808bdfe0 根根元类
2021-06-25 23:07:44.994396+0800 002-isa分析[33538:2348495] LGPerson的元类的父类: NSObject - 0x7fff808bdfe0
2021-06-25 23:07:44.994518+0800 002-isa分析[33538:2348495] LGTeacher的元类的父类: LGPerson - 0x100008338
2021-06-25 23:07:44.994579+0800 002-isa分析[33538:2348495] NSObject的元类的父类: (null) - 0x0
2021-06-25 23:07:44.994680+0800 002-isa分析[33538:2348495] NSObject根元类的父类: NSObject - 0x7fff808be008
(lldb)
复制代码
结论:
NSOject
对象的元类与根元类
是同一个类- 元类间也存在着继承的关系,跟类是一样的,通过打印
LGTeacher
的元类
的superClass
与LGPerson
的元类
都得到了一样的地址0x100008338
- 根元类的父类指向了
NSObject
,通过打印NSObject
的superClass
与NSObject类得到了0x7fff808be008
地址- NSObject的父类是(null),地址为0x0,即NSObject没有父类
类的结构分析
新旧版本的objc_class源码
老版本1.0的objc_class
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */
复制代码
新版本2.0的objc_class(基于objc4(818版本)的源码)
可以省略,因为里面大部分方法现在不做讲解,只用注意它的3大变量
Class superclass
保存父类内存地址,占8字节内存
cache_t cache
结构体占16字节内存,具体内容下一章说明
class_data_bits_t bits
管理有类属性property、方法 method等等的结构体,内存大小由定义property、method等等多少定
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的内存结构
代码:
#import <Foundation/Foundation.h>
@interface LGPerson
@end
@implementation LGPerson
@end
#ifdef DEBUG
#define LGNSLog(format, ...) printf("KC打印: %s\n", [[NSString stringWithFormat:format, ## __VA_ARGS__] UTF8String]);
#else
#define LGNSLog(format, ...);
#endif
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
// 普通指针
int a = 10; //
int b = 10; //
LGNSLog(@"%d -- %p",a,&a);
LGNSLog(@"%d -- %p",b,&b);
// 对象 -
LGPerson *p1 = [LGPerson alloc];
LGPerson *p2 = [LGPerson alloc];
LGNSLog(@"%@ -- %p",p1,&p1);
LGNSLog(@"%@ -- %p",p2,&p2);
// 数组指针
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++) {
int value = *(d+i);
NSLog(@"%d",value);
}
// OC 类 结构 首地址 - 平移一些大小 -> 内容
// LGPerson.class地址 - 平移 所有的值
NSLog(@"指针 - 内存偏移");
}
return 0;
}
复制代码
调试打印:
KC打印: 10 -- 0x7ffeefbff36c
KC打印: 10 -- 0x7ffeefbff368
KC打印: <LGPerson: 0x105c040c0> -- 0x7ffeefbff360
KC打印: <LGPerson: 0x105c040d0> -- 0x7ffeefbff358
2021-06-24 16:44:03.478075+0800 002-内存偏移[23027:1912018] 0x7ffeefbff380 - 0x7ffeefbff380 - 0x7ffeefbff384
2021-06-24 16:44:03.478816+0800 002-内存偏移[23027:1912018] 0x7ffeefbff380 - 0x7ffeefbff384 - 0x7ffeefbff388
2021-06-24 16:50:34.623442+0800 002-内存偏移[23027:1912018] 1
2021-06-24 16:50:34.623577+0800 002-内存偏移[23027:1912018] 2
2021-06-24 16:50:34.623656+0800 002-内存偏移[23027:1912018] 3
2021-06-24 16:50:34.623712+0800 002-内存偏移[23027:1912018] 4
2021-06-24 16:50:34.624118+0800 002-内存偏移[23027:1912018] 指针 - 内存偏移
Program ended with exit code: 0
复制代码
结论:
变量:值拷贝
对象:指针地址与内容地址都不同
数组:首地址作为数组地址
复制代码
类的内存结构
通过打印class的内存内容,要查找class_data_bits_t的结构体去了解接下来的属性与方法、isa
错误的理念
通过使用ojc4.8的工程编译断点调试!错误的初步推断class的内存分布
错误理解图:
结论:
之所以错误了,就是没有具体分析了cache_t的结构体内容
正确的理念
要具体分析cache_t的结构体占用的内存大小
复制代码
cache_t结构体里方法不会在内存里,static变量是在全局区也不在其占用内存大小里
struct cache_t {
//只有这个部分占用结构体的内存
private:
explicit_atomic<uintptr_t> _bucketsAndMaybeMask; //typedef unsigned long uintptr_t;
//由于这个是泛型,所以大小由unintptr_t确定,而unsigned long占用8字节
union {
struct {
explicit_atomic<mask_t> _maybeMask;//typedef uint32_t mask_t;
//由于unit32_t占用4字节
#if __LP64__
uint16_t _flags; //2字节
#endif
uint16_t _occupied; //2字节
};
explicit_atomic<preopt_cache_t *> _originalPreoptCache;//结构体指针只占用8字节
};//这个共用体刚好占用8字节
.....
//省略了方法与staic变量,因为他们不占用cache_t开拓的内存
.....
}// 这个结构体刚好占用8+8=16字节
复制代码
真正查找到class_data_bits_t
由于上面验证可以得出cache_t
是占用16字节,而Class ISA
占用8字节,Class superclass
占用8字节;所以查找class_data_bits_t
正好是class指针地址+0x20
断点调试:
KCObjcBuild was compiled with optimization - stepping may behave oddly; variables may not be available.
(lldb) x/4gx LGPerson.class
0x100008380: 0x00000001000083a8 0x000000010036a140
0x100008390: 0x0000000100664f10 0x0002802800000003
(lldb) po 0x000000010036a140
NSObject
(lldb) p/x NSObject.class
(Class) $2 = 0x000000010036a140 NSObject
(lldb) 0x0002802800000003 -> bit -> data
error: '0x0002802800000003' is not a valid command.
(lldb) x/5gx LGPerson.class
0x100008380: 0x00000001000083a8 0x000000010036a140
0x100008390: 0x0000000100664f10 0x0002802800000003
0x1000083a0: 0x0000000100664ed4
(lldb) x/6gx LGPerson.class
0x100008380: 0x00000001000083a8 0x000000010036a140
0x100008390: 0x0000000100664f10 0x0002802800000003
0x1000083a0: 0x0000000100664ed4 0x000000010036a0f0
(lldb) po sizeof(LGPerson.class)
8
(lldb) 要获取class_data_bits_t的地址内容要首地址(8bytes)+ superclass(8bytes) + cache(16bytes) = 32
error: '要获取class_data_bits_t的地址内容要首地址(8bytes)+' is not a valid command.
(lldb) po 0x100008380+0x20
4295000992
(lldb) p/x 0x100008380+0x20
(long) $7 = 0x00000001000083a0
(lldb) p (class_data_bits_t *)0x00000001000083a0
(class_data_bits_t *) $8 = 0x00000001000083a0
(lldb) p *$8
(class_data_bits_t) $9 = (bits = 4301672148)
(lldb) p $8->data()
(class_rw_t *) $10 = 0x0000000100664ed0
(lldb) p *$10
(class_rw_t) $11 = {
flags = 2148007936
witness = 1
ro_or_rw_ext = {
std::__1::atomic<unsigned long> = {
Value = 4295000344
}
}
firstSubclass = nil
nextSiblingClass = NSUUID
}
复制代码
结论:
0x000000010036a140
是LGPerson
的superClass
0x100008380+0x20
是LGPerson类的地址+16字节偏移量,刚好是class_data_bits_t
分析class_data_bits_t
结构体
class_rw_t
结构体
通过分析class_data_bits_t
的内容可以发现它的主要数据内容就是输出class_rw_t
的结构体指针
源代码:
struct class_data_bits_t {
friend objc_class;
// Values are the FAST_ flags above.
uintptr_t bits;
private:
bool getBit(uintptr_t bit) const
{
return bits & bit;
}
//此处省略了部分代码
public:
//重点这里输出数据
class_rw_t* data() const {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
//此处省略了部分代码
复制代码
结论:
通过获取class_rw_t* 类型的data()
源代码:
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;
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 {
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};
}
}
const protocol_array_t protocols() 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)->protocols;
} else {
return protocol_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProtocols};
}
}
};
复制代码
结论:
通过解析
class_rw_t
这个结构体可以拿到类的信息,比如这个类的firstSubclass
、methods
、properties
、protocols
、deepCopy
、ro
等等信息
获取class_rw_t
的firstSubclass
成员变量获取流程:
NSObject.class
->class_data_bits_t
->class_rw_t
->firstSubclass
在工程中新建
LGPerson
与LGTeacher
的对象,在LGTeacher
打上断点,第一个断点只实例LGPerson
,第二次运行实例化LGTeacher
,用于验证firstSubclass
的变化。
断点调试
(lldb) p $1->data()
(class_rw_t *) $2 = 0x000000010070d790
(lldb) p *$2
(class_rw_t) $3 = {
flags = 2148007936
witness = 1
ro_or_rw_ext = {
std::__1::atomic<unsigned long> = {
Value = 4295000344
}
}
firstSubclass = nil
nextSiblingClass = NSUUID
}
//进入第二个断点
2021-06-25 01:02:38.456836+0800 KCObjcBuild[25790:2082491] 我来了: <LGTeacher: 0x100604d60>
(lldb) x/4gx LGPerson.class
0x100008388: 0x00000001000083b0 0x000000010036a140
0x100008398: 0x0000000100726800 0x0002802800000003
(lldb) p *$2
(class_rw_t) $5 = {
flags = 2148007936
witness = 1
ro_or_rw_ext = {
std::__1::atomic<unsigned long> = {
Value = 4295000344
}
}
firstSubclass = LGTeacher
nextSiblingClass = NSUUID
}
(lldb)
复制代码
结论:
在子类
LGTeacher
没有实例前,LGPerson
的class_rw_t
中的firstSubclass
是nil,侧面证明Object-C是运行时的。其实在lldb中p LGTeacher.class
也可以改变,原因是OC的class是懒加载
的存在!
获取class_rw_t
的properties
的数组
成员变量获取流程:NSObject.class -> class_data_bits_t -> class_rw_t -> property_array_t -> property_list_t -> property_t
代码:
#import <Foundation/Foundation.h>
@interface LGPerson : NSObject{
NSString *subject;
}
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *hobby;
- (void)sayNB;
+ (void)say666;
// 1: 类的探究分析
// 2: 类方法 + ivar 在哪里
@end
@implementation LGPerson
- (instancetype)init{
if (self = [super init]) {
self.name = @"Cooci";
}
return self;
}
- (void)sayNB{
}
+ (void)say666{
}
@end
@interface LGTeacher : LGPerson
@property (nonatomic, copy) NSString *hobby;
- (void)teacherSay;
@end
@implementation LGTeacher
- (instancetype)init{
if (self == [super init]) {
NSLog(@"我来了: %@",self);
return self;
}
return nil;
}
- (void)teacherSay{
NSLog(@"%s",__func__);
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 类的内存结构
// 并非 8 * 3 = 24 (错误的内存大小)
//
LGPerson *p1 = [[LGPerson alloc] init];
LGTeacher *p2 = [[LGTeacher alloc] init];
// class_data_bits_t
}
return 0;
}
复制代码
断点调试:
(lldb) x/4gx LGPerson.class
0x100008388: 0x00000001000083b0 0x000000010036a140
0x100008398: 0x0000000101907be0 0x0002802800000003
(lldb) p/x 0x100008388 + 0x20
(long) $1 = 0x00000001000083a8
(lldb) p (class_data_bits_t *)0x00000001000083a8
(class_data_bits_t *) $2 = 0x00000001000083a8
(lldb) p $2->data()
(class_rw_t *) $3 = 0x00000001019080a0
(lldb) p $3->properties()
(const property_array_t) $4 = {
list_array_tt<property_t, property_list_t, RawPtr> = {
= {
list = {
ptr = 0x0000000100008260
}
arrayAndFlag = 4295000672
}
}
}
(lldb) p $4.list
(const RawPtr<property_list_t>) $5 = {
ptr = 0x0000000100008260
}
(lldb) p $5.ptr
(property_list_t *const) $6 = 0x0000000100008260
(lldb) p *$6
(property_list_t) $7 = {
entsize_list_tt<property_t, property_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 16, count = 2)
}
(lldb) p $7.get(0)
(property_t) $8 = (name = "name", attributes = "T@\"NSString\",C,N,V_name")
(lldb) p $7.get(1)
(property_t) $9 = (name = "hobby", attributes = "T@\"NSString\",C,N,V_hobby")
复制代码
步骤:
x/4gx LGPerson.class
格式化输出LGPerson.class
,拿到类的首地址0x100008388
p/x 0x100008388 + 0x20
首地址偏移32个字节(isa
8字节、superclass
8字节、cache
16字节),拿到类对象属性地址0x00000001000083a8
p (class_data_bits_t *)0x00000001000083a8
将地址转化成class_data_bits_t
类型,为了使用class_data_bits_t
的函数p $2->data()
使用class_data_bits_t
的data()
函数,拿到class_rw_t
类型的地址0x00000001019080a0
p $3->properties()
通过properties()
函数获取LGPerson
的成员变量列表p $4.list
和p $5.ptr
解析出property_list_t
的地址p *$6
通过取地址的方式获取成员变量property_list_t
p $7.get(0)
与p $7.get(1)
通过c++函数单个获取类的成员变量name
、hobby
获取class_rw_t
的methods
的数组
实例方法获取流程:
NSObject.class
->class_data_bits_t
->class_rw_t
->method_array_t
->method_list_t
->method_t
->big
与properties()一样,但是是获取class_rw_t对象的method()方法,有区别的地方在于当拿到method之后并不能直接向properties一样就可以解析出来,在method_t结构体中还有一层结构体big(),所以在获取实例方法的时候得多解析一层结构体。但是过程中发现没有类方法!说明这个是实例方法
源代码:
struct method_t {
static const uint32_t smallMethodListFlag = 0x80000000;
method_t(const method_t &other) = delete;
// The representation of a "big" method. This is the traditional
// representation of three pointers storing the selector, types
// and implementation.
struct big {
SEL name;
const char *types;
MethodListIMP imp;
};
//此处省略了部分源码
}
复制代码
对比method与propetry异同图:
断点调试:
(lldb) x/4gx LGPerson.class
0x100008388: 0x00000001000083b0 0x000000010036a140
0x100008398: 0x0000000101304120 0x0002802800000003
(lldb) p/x 0x100008388 + 0x20
(long) $1 = 0x00000001000083a8
(lldb) p (class_data_bits_t *)0x00000001000083a8
(class_data_bits_t *) $2 = 0x00000001000083a8
(lldb) p $2->data()
(class_rw_t *) $3 = 0x00000001013040a0
(lldb) p $3->methods()
(const method_array_t) $4 = {
list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
= {
list = {
ptr = 0x0000000100008160
}
arrayAndFlag = 4295000416
}
}
}
(lldb) p $4.list
(const method_list_t_authed_ptr<method_list_t>) $5 = {
ptr = 0x0000000100008160
}
(lldb) p $5.ptr
(method_list_t *const) $6 = 0x0000000100008160
(lldb) p *$6
(method_list_t) $7 = {
entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 27, count = 6)
}
(lldb) p $7.get(0).big()
(method_t::big) $8 = {
name = "sayNB"
types = 0x0000000100003f77 "v16@0:8"
imp = 0x0000000100003d40 (KCObjcBuild`-[LGPerson sayNB])
}
(lldb) p $7.get(1).big()
(method_t::big) $9 = {
name = "hobby"
types = 0x0000000100003f6f "@16@0:8"
imp = 0x0000000100003db0 (KCObjcBuild`-[LGPerson hobby])
}
(lldb) p $7.get(2).big()
(method_t::big) $10 = {
name = "setHobby:"
types = 0x0000000100003f8b "v24@0:8@16"
imp = 0x0000000100003de0 (KCObjcBuild`-[LGPerson setHobby:])
}
(lldb) p $7.get(3).big()
(method_t::big) $11 = {
name = "init"
types = 0x0000000100003f6f "@16@0:8"
imp = 0x0000000100003ce0 (KCObjcBuild`-[LGPerson init])
}
(lldb) p $7.get(4).big()
(method_t::big) $12 = {
name = "name"
types = 0x0000000100003f6f "@16@0:8"
imp = 0x0000000100003d50 (KCObjcBuild`-[LGPerson name])
}
(lldb) p $7.get(5).big()
(method_t::big) $13 = {
name = "setName:"
types = 0x0000000100003f8b "v24@0:8@16"
imp = 0x0000000100003d80 (KCObjcBuild`-[LGPerson setName:])
}
复制代码
步骤:
x/4gx LGPerson.class
格式化输出LGPerson.class
,拿到类的首地址0x100008388
p/x 0x100008388 + 0x20
首地址偏移32个字节(ISA8字节、superclass8字节、cache16字节),拿到类对象属性地址0x00000001000083a8
p (class_data_bits_t *)0x00000001000083a8
将地址转化成class_data_bits_t
类型,为了使用class_data_bits_t
的函数p $2->data()
使用class_data_bits_t
的data()
函数,拿到class_rw_t
类型的地址0x00000001013040a0
p $3->methods()
通过methods()
函数获取LGPerson
的实例方法列表method_array_t
p $4.list
和p $5.ptr
解析出method_list_t
的地址p *$6
通过取地址的方式获实例变量数组method_list_t
,entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 27, count = 6)
,非常明显有6个实例方法- 通过c++函数
get()
与big()
单个获取类的实例方法:
p $7.get(0).big()
:-[LGPerson sayNB]自定义实例方法sayNB
p $7.get(1).big()
:-[LGPerson hobby]成员变量hobby
的getter
方法,是系统生成的
p $7.get(2).big()
:-[LGPerson init]自定义init
方法- (instancetype)init
p $7.get(3).big()
:-[LGPerson name]成员变量name
的getter方法,是系统生成的
p $7.get(0).big()
:-[LGPerson setName:]成员变量name
的setter方法,是系统生成的
获取class_rw_t的ivars(成员变量)
ivars获取流程源码:
NSObject.class
->class_data_bits_t
->class_rw_t
->class_ro_t
->ivar_list_t
->ivar_t
源代码:
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize;
#ifdef __LP64__
uint32_t reserved;
#endif
union {
const uint8_t * ivarLayout;
Class nonMetaclass;
};
explicit_atomic<const char *> name;
// With ptrauth, this is signed if it points to a small list, but
// may be unsigned if it points to a big list.
void *baseMethodList;
protocol_list_t * baseProtocols;
const ivar_list_t * ivars;
const uint8_t * weakIvarLayout;
property_list_t *baseProperties;
//省略了部分代码
}
复制代码
struct ivar_t {
#if __x86_64__
// *offset was originally 64-bit on some x86_64 platforms.
// We read and write only 32 bits of it.
// Some metadata provides all 64 bits. This is harmless for unsigned
// little-endian values.
// Some code uses all 64 bits. class_addIvar() over-allocates the
// offset for their benefit.
#endif
int32_t *offset;
const char *name;
const char *type;
// alignment is sometimes -1; use alignment() instead
uint32_t alignment_raw;
uint32_t size;
uint32_t alignment() const {
if (alignment_raw == ~(uint32_t)0) return 1U << WORD_SHIFT;
return 1 << alignment_raw;
}
};
复制代码
lldb动态调试
(lldb) x/4gx LGPerson.class
0x100008388: 0x00000001000083b0 0x000000010036a140
0x100008398: 0x000000010135c630 0x0002802800000003
(lldb) p/x 0x100008388 + 0x20
(long) $1 = 0x00000001000083a8
(lldb) p (class_data_bits_t *)$1
(class_data_bits_t *) $2 = 0x00000001000083a8
(lldb) p $2->data()
(class_rw_t *) $3 = 0x000000010135c5f0
(lldb) p $3->ro()
(const class_ro_t *) $4 = 0x0000000100008118
(lldb) p *$4
(const class_ro_t) $5 = {
flags = 0
instanceStart = 8
instanceSize = 32
reserved = 0
= {
ivarLayout = 0x0000000000000000
nonMetaclass = nil
}
name = {
std::__1::atomic<const char *> = "LGPerson" {
Value = 0x0000000100003ea8 "LGPerson"
}
}
baseMethodList = 0x0000000100008160
baseProtocols = 0x0000000000000000
ivars = 0x00000001000081f8
weakIvarLayout = 0x0000000000000000
baseProperties = 0x0000000100008260
_swiftMetadataInitializer_NEVER_USE = {}
}
(lldb) p $5.ivars
(const ivar_list_t *const) $6 = 0x00000001000081f8
(lldb) p *$6
(const ivar_list_t) $7 = {
entsize_list_tt<ivar_t, ivar_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 32, count = 3)
}
(lldb) p $7.get(0)
(ivar_t) $8 = {
offset = 0x0000000100008320
name = 0x0000000100003f2f "subject"
type = 0x0000000100003f7f "@\"NSString\""
alignment_raw = 3
size = 8
}
(lldb) p $7.get(1)
(ivar_t) $9 = {
offset = 0x0000000100008328
name = 0x0000000100003f37 "_name"
type = 0x0000000100003f7f "@\"NSString\""
alignment_raw = 3
size = 8
}
(lldb) p $7.get(2)
(ivar_t) $10 = {
offset = 0x0000000100008330
name = 0x0000000100003f3d "_hobby"
type = 0x0000000100003f7f "@\"NSString\""
alignment_raw = 3
size = 8
}
复制代码
步骤
x/4gx LGPerson.class
格式化输出LGPerson.class
,获取到首地址0x100008388
p/x 0x100008388 + 0x20
首地址偏移32字节(isa
8字节、superclass
8字节、cache_t
16字节),拿到包含类属性方法成员变量的对象class_data_bits_t
的地址0x00000001000083a8
p (class_data_bits_t *)$1
将地址转换为class_data_bits_t
,为了使用class_data_bits_t
的函数p $2->data()
使用class_data_bits_t
的data()
函数,拿到class_rw_t
类型的地址0x000000010135c5f0
p $3->ro
使用class_rw_t
的ro
函数,拿到class_ro_t
类型的地址0x0000000100008118
p *$4
取lass_ro_t
类型地址0x0000000100008118
的值,拿到了class_ro_t
对象p $5.ivars
使用ivars
函数获取class_ro_t
对象的ivars
,得到了指向ivar_list_t
地址0x00000001000081f8
的指针p *$6
通过取地址0x00000001000081f8
的方式获实例变量数组ivar_list_t
,entsize_list_tt<ivar_t, ivar_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 32, count = 3)
,可以看到有3个ivars- 通过c++函数
get()
单个获取类的实例方法:用户声明的成员变量
subject
p $7.get(0)
:
(ivar_t) $8 = {
offset = 0x0000000100008320
name = 0x0000000100003f2f “subject
“
type = 0x0000000100003f7f “@”NSString
“”
alignment_raw = 3
size = 8
}属性
name
,系统自行生成变量_name
p $7.get(1)
:
(ivar_t) $9 = {
offset = 0x0000000100008328
name = 0x0000000100003f37 “_name
“
type = 0x0000000100003f7f “@”NSString
“”
alignment_raw = 3
size = 8
}属性
hobby
,系统自行生成变量_hobby
p $7.get(2)
:
(ivar_t) $10 = {
offset = 0x0000000100008330
name = 0x0000000100003f3d “_hobby
“
type = 0x0000000100003f7f “@”NSString
“”
alignment_raw = 3
size = 8
}
结论:ivars存在ro中,成员变量自动生成了属性_name,_hobby
获取class_rw_t的类方法
上面我们已经获取过class的方法列表,发现没有类方法,所以类Class
直接获取的methods
是实例方法
,而类方法
无处去吗?那类方法
只能是元类metalClass
里面了。
类方法获取流程:
NSObject.class
->metaClass
->class_data_bits_t
->class_rw_t
->method_array_t
->method_list_t
->method_t
->big
断点调试打印:
(lldb) x/4gx LGPerson.class
0x100008390: 0x00000001000083b8 0x000000010036a140
0x1000083a0: 0x0000000101470ab0 0x0002802800000003
(lldb) p/x 0x00000001000083b8 & 0x00007ffffffffff8
(long) $1 = 0x00000001000083b8
(lldb) x/4gx 0x00000001000083b8
0x1000083b8: 0x000000010036a0f0 0x000000010036a0f0
0x1000083c8: 0x0000000100731720 0x0001e03100000007
(lldb) p/x 0x1000083b8 + 0x20
(long) $2 = 0x00000001000083d8
(lldb) p (class_data_bits_t *)$2
(class_data_bits_t *) $3 = 0x00000001000083d8
(lldb) p $3->data()
(class_rw_t *) $4 = 0x0000000101470a50
(lldb) p $4->methods()
(const method_array_t) $5 = {
list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
= {
list = {
ptr = 0x00000001000082d0
}
arrayAndFlag = 4295000784
}
}
}
(lldb) p $5.list
(const method_list_t_authed_ptr<method_list_t>) $6 = {
ptr = 0x00000001000082d0
}
(lldb) p $6.ptr
(method_list_t *const) $7 = 0x00000001000082d0
(lldb) p *$7
(method_list_t) $8 = {
entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 27, count = 1)
}
(lldb) p $8.get(0).big()
(method_t::big) $9 = {
name = "say666"
types = 0x0000000100003f77 "v16@0:8"
imp = 0x0000000100003e10 (KCObjcBuild`+[LGPerson say666])
}
(lldb)
复制代码
步骤:
x/4gx LGPerson.class
格式化打印类LGPerson
,得到类的首地址0x100008390
p/x 0x00000001000083b8 & 0x00007ffffffffff8
将isa指针
和ISA_MASK
做与
操作,拿到LGPerson
的元类metaClass
x/4gx 0x00000001000083b8
,格式化打印LGPerson
的metaClass
,拿到元类的首地址0x1000083b8
p/x 0x1000083b8 + 0x20
,将元类的首地址偏移32个字节(ISA
8字节、superclass
8字节、cache_t
16字节),那多元类的class_data_bits_t
对象地址0x00000001000083d8
p (class_data_bits_t *)0x00000001000083d8
将地址转化为class_data_bits_t
对象,方便调用函数p $3->data()
调用class_data_bits_
t的data
函数,拿到class_rw_t
对象地址0x0000000101470a50
p $4->methods()
获取class_rw_t
的methods
方法列表p $5.list
和p $5.ptr
拿到指向method_list_t
地址的指针0x00000001000082d0
p *$7
取地址,拿到了method_list_t
对象,count为1,entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 27, count = 1)
, 有一个类方法- 通过c++函数
get()
与big()
单个获取类的类方法:p $7.get(0).big()
:(method_t::big) $9 = {
name = “say666
“
types = 0x0000000100003f77 “v16@0:8”
imp = 0x0000000100003e10 (KCObjcBuild+[LGPerson say666]
)
}