isa(二)

前一篇介绍过isa的优化方式以及从被优化过的isa中获取真正的struct objc_class指针。然而我们对知识的渴望,并不允许自己仅仅只是知道它、了解它而已,还想进一步分析struct objc_class结构体,以及用它来做点什么。
接下来,从isa指针中到底有哪些信息获取到这些信息可以用来做什么两个方面,来进一步揭开isa的面纱。

isa指针中到底有哪些信息?

既然已经可以从一个实例对象中获取到isa指针,那么我们就直接用这个指针来struct objc_class中的数据。
要解析指针指向的地址中存放的数据,肯定需要知道struct objc_class的定义(中间有很多方法直接忽略掉,只看其数据结构):

1
2
3
4
5
6
7
struct objc_class : objc_object {
// 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
...
}

因为指针占用的内存空间是8字节,所以通过看其结构体的构成,可以知道前16个字节里存放的是struct objc_class *类型的指针ISAsuperclass
这两个指针,实际上存储的是类对象的父类以及元类的指针。父类和元类的解析,因为类型一样,所以和自身的解析是一样的。
第17个字节开始,存放的是cache_t类型的数据,但是这个数据并没有使用指针,所以它里面的数据,会有序并字节对齐的存放在第17个字节开始的位置。可以通过cache_t结构体来查看其数据类型:

1
2
3
4
5
6
7
8
9
struct cache_t {
struct bucket_t *_buckets;
mask_t _mask;
mask_t _occupied;
}

#if __LP64__
typedef uint32_t mask_t; // x86_64 & arm64 asm are less efficient with 16-bits

struct cache_t里面有一个指针,占8字节内存;有两个mask_t类型(即uint32_t类型)的数据,每个占4字节的内存,所以cache_t cache一共占16个字节的内存。
这个字段里,存放的是方法缓存相关的信息。
最后一个结构体class_data_bits_t bits

1
2
3
4
struct class_data_bits_t {
// Values are the FAST_ flags above.
uintptr_t bits;
}

里面就一个占8字节的数据。
从这个结构体的名字可以看出,类的主要信息都存储在这个字段里。这个字段里存储的是class_rw_t类型的指针,但是也不是直接将指针的值存进去的,和被优化的isa一样被优化过的,所以不能直接取出来用。那么要怎么获取到这个指针呢?
在源码中往下看struct class_data_bits_t这个结构体,会发现有一个data()方法,返回一个class_rw_t*

1
2
3
4
5
6
// data pointer
#define FAST_DATA_MASK 0x00007ffffffffff8UL

class_rw_t* data() {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}

所以其实我们也可以将获取的bits的值,和0x00007ffffffffff8UL进行&运算,得到class_rw_t*指针。
写个简单的代码,可以走一下这个顺序,验证一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;

appDelegateClassName = NSStringFromClass([AppDelegate class]);

CustomClass *c = [CustomClass new];
c.i = 10;
c.obj = [NSObject new];
c.str = @"i love code";
NSLog(@"c:%p",c);

//1、通过实例对象`c`,获取 isa 指针
uintptr_t c_isa = (*(uintptr_t *)c) & 0x0000000FFFFFFFF8ULL;

//2、获取 `class_data_bits_t bits` 的值
uintptr_t c_bits = *(uintptr_t *)((unsigned long long)c_isa + 4*8);

//3、获取 `class_rw_t *`指针
uintptr_t rw_t = c_bits & 0x00007ffffffffff8UL;


return UIApplicationMain(argc, argv, nil, appDelegateClassName);

}

虽说是验证,但是取到这个值,也不知道是不是正确的,反正值是取到了!
要想验证取的值是否正确,还得看看class_rw_t *里面的数据,是否是这个类的信息。
所以还是要跟进去看这个结构体:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

struct class_rw_t {
// Be warned that Symbolication knows the layout of this structure.
uint32_t flags;
uint32_t version;

const class_ro_t *ro;

method_array_t methods;
property_array_t properties;
protocol_array_t protocols;

Class firstSubclass;
Class nextSiblingClass;

char *demangledName;

还是按照刚才的思路,挨个字段去解析:

  • flags
  • version
  • ro
    const 修饰的 class_ro_t * 指针,储了当前类在编译期就已经确定的属性、方法以及遵循的协议,不可修改。
  • methods
    实例方法列表(元类中存储的是类方法列表),是一个指向method_t的二级指针。
  • properties
    属性列表。
  • protocols
    协议列表
    这里主要关注romethodsproperties以及protocols字段。
    先看一下ro的类型class_ro_t 的结构:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23

    struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;
    #ifdef __LP64__
    uint32_t reserved;
    #endif

    const uint8_t * ivarLayout;

    const char * name;
    method_list_t * baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;

    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;

    method_list_t *baseMethods() const {
    return baseMethodList;
    }
    };
    刚刚的代码中,我们以及获取到了class_rw_t 指针的值,我们可以通过读取class_rw_tro字段的值,然后获取class_ro_t里面name,来判断我们获取的指针是否正确:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    int main(int argc, char * argv[]) {
    NSString * appDelegateClassName;

    appDelegateClassName = NSStringFromClass([AppDelegate class]);

    CustomClass *c = [CustomClass new];
    c.i = 10;
    c.obj = [NSObject new];
    c.str = @"i love code";
    NSLog(@"c:%p",c);

    //1、通过实例对象`c`,获取 isa 指针
    uintptr_t c_isa = (*(uintptr_t *)c) & 0x0000000FFFFFFFF8ULL;

    //2、获取 `class_data_bits_t bits` 的值
    uintptr_t c_bits = *(uintptr_t *)((unsigned long long)c_isa + 4*8);

    //3、获取 `class_rw_t *`指针
    uintptr_t rw_t = c_bits & 0x00007ffffffffff8UL;

    //4、获取 `class_rw_t` 中 ro 指针
    uintptr_t ro = *(uintptr_t *)((unsigned long long)rw_t+4+4);

    //5、获取 `class_ro_t` 中的 `name`

    const char *name = (const char *)(*(uintptr_t *)((unsigned long long)ro + 4 +4 + 4 +4 + 8));


    /*这里下断点*/ return UIApplicationMain(argc, argv, nil, appDelegateClassName);

    }
    1
    2
    3
    4
    2019-11-25 00:00:30.956322+0800 IvarDemo[11902:3499502] c:0x1740261c0
    (lldb) p/s name
    (const char *) $0 = "CustomClass" "CustomClass"
    (lldb)
    看log,可以看出,取出的值都是正确的。
    既然name可以取出,那么其他的字段,也是可以取出的,例如在 8byte~12byte 存储的instanceSize
    1
    2
    3
    4
    5
    6
        
    //6、获取 `class_ro_t` 中的 `instanceSize`
    uint32_t instanceSize = *(uint32_t *)((unsigned long long)ro + 4 +4);
    /*
    使用 class_getInstanceSize() 函数验证
    */
    1
    2
    3
    4
    5
    6
    7
    (lldb) p/d instanceSize
    (uint32_t) $0 = 32
    Printing description of c:
    <CustomClass: 0x17002f340>
    (lldb) p/d (uint32_t)class_getInstanceSize([c class])
    (uint32_t) $3 = 32
    (lldb)
    其他字段就不一一获取。
    回过头去看看class_rw_t 中的methods 字段。
    还是先看其定义:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    class method_array_t : 
    public list_array_tt<method_t, method_list_t>
    {
    typedef list_array_tt<method_t, method_list_t> Super;

    public:
    method_list_t **beginCategoryMethodLists() {
    return beginLists();
    }

    method_list_t **endCategoryMethodLists(Class cls);

    method_array_t duplicate() {
    return Super::duplicate<method_array_t>();
    }
    };
    method_array_t是一个C++类,且其继承自模板类list_array_tt,所以我们还得了解一下list_array_tt 这个模板类:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    template <typename Element, typename List>
    class list_array_tt {
    ...
    private:
    union {
    List* list;
    uintptr_t arrayAndFlag;
    };
    ...
    }
    从结构中可以看出,method_array_t类只占8字节的内存空间,成员用union联合,说明list arrayAndFlag 公用8字节的空间。
    我们知道List在类method_array_t中是method_array_t,但是既然定义成联合,那么肯定不是简单的将method_array_t指针存入这8个字节的,所以现在问题就变成了:怎么获取真正的method_array_t指针的值。
    观察这个模板类,发现里面有一个List** beginLists()方法:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    List** beginLists() {
    if (hasArray()) {
    return array()->lists;
    } else {
    return &list;
    }
    }

    bool hasArray() const {
    return arrayAndFlag & 1;
    }

    array_t *array() {
    return (array_t *)(arrayAndFlag & ~1);
    }

    以上三个的函数告诉了咱们怎么通过arrayAndFlag 获取真正的List**
    1、判断arrayAndFlag最后一位是否为1;
    2、通过判断结果,分别获取List**
  • 判断正确:array()->lists;
  • 否则:&list;

接下来,我们也可以书写代码,尝试着获取List**:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;

appDelegateClassName = NSStringFromClass([AppDelegate class]);

CustomClass *c = [CustomClass new];
c.i = 10;
c.obj = [NSObject new];
c.str = @"i love code";
NSLog(@"c:%p",c);

//1、通过实例对象`c`,获取 isa 指针
uintptr_t c_isa = (*(uintptr_t *)c) & 0x0000000FFFFFFFF8ULL;

//2、获取 `class_data_bits_t bits` 的值
uintptr_t c_bits = *(uintptr_t *)((unsigned long long)c_isa + 4*8);

//3、获取 `class_rw_t *`指针
uintptr_t rw_t = c_bits & 0x00007ffffffffff8UL;

//4、获取 `class_rw_t` 中 ro 指针
uintptr_t ro = *(uintptr_t *)((unsigned long long)rw_t+4+4);

//5、获取 `class_ro_t` 中的 `name`

const char *name = (const char *)(*(uintptr_t *)((unsigned long long)ro + 4 +4 + 4 +4 + 8));

//6、获取 `class_ro_t` 中的 `instanceSize`
uint32_t instanceSize = *(uint32_t *)((unsigned long long)ro + 4 +4);

//7、获取 `class_rw_t` 中 `method`

uintptr_t method = *((uintptr_t *)(((unsigned long long)rw_t)+4+4+8));

/*注:因为`method`成员没有用指针修饰,所以`method`的值即为
union
{List *list;
uintptr_t arrayAndFlag;

}
的值。
*/
//8、获取 `arrayAndFlag`的值
uintptr_t arrayAndFlag = method;

//9、//获取 `List **`
uintptr_t *list;
//判断 `arrayAndFlag` 标志位
if (arrayAndFlag & 1) {
//获取 `array_t *`
uintptr_t array_t = arrayAndFlag & ~1;
list = &(*((uintptr_t *)((unsigned long long)array_t+8)));
}else {
//获取 `List **`
list = &arrayAndFlag;
}

/*这里下断点*/ return UIApplicationMain(argc, argv, nil, appDelegateClassName);

}

虽然到这里,已经获取到了List **(也就是method_list_t**),但是我们要找的方法列表,还只是初见端倪,并没有完全浮出水面。所以我们还是要进一步分析一下method_list_t

1
2
3
4
5
6
7
8
9
10
11
struct method_list_t : entsize_list_tt<method_t, method_list_t, 0x3> {
bool isFixedUp() const;
void setFixedUp();

uint32_t indexOfMethod(const method_t *meth) const {
uint32_t i =
(uint32_t)(((uintptr_t)meth - (uintptr_t)this) / entsize());
assert(i < count);
return i;
}
};
1
2
3
4
5
6
7
template <typename Element, typename List, uint32_t FlagMask>
struct entsize_list_tt {
uint32_t entsizeAndFlags;
uint32_t count;
Element first;
...
}

method_list_t 结构体继承自模板结构体entsize_list_tt ,所以method_list_t的成员变量有3个:

1
2
3
uint32_t entsizeAndFlags;
uint32_t count;
Element first;

我们要关注的,是countfirstcount中存储的是方法个数,而first则是方法列表的其实地址,也就是方法数组的第一个元素。(至于第一个entsizeAndFlags,存储的是数组中每个元素的大小。)
既然可以看到其内存布局,也就意味着我们可以获取到数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;

appDelegateClassName = NSStringFromClass([AppDelegate class]);

CustomClass *c = [CustomClass new];
NSLog(@"c:%p",c);

//1、通过实例对象`c`,获取 isa 指针
uintptr_t c_isa = (*(uintptr_t *)c) & 0x0000000FFFFFFFF8ULL;

//2、获取 `class_data_bits_t bits` 的值
uintptr_t c_bits = *(uintptr_t *)((unsigned long long)c_isa + 4*8);

//3、获取 `class_rw_t *`指针
uintptr_t rw_t = c_bits & 0x00007ffffffffff8UL;

//4、获取 `class_rw_t` 中 ro 指针
uintptr_t ro = *(uintptr_t *)((unsigned long long)rw_t+4+4);

//5、获取 `class_ro_t` 中的 `name`

const char *name = (const char *)(*(uintptr_t *)((unsigned long long)ro + 4 +4 + 4 +4 + 8));

//6、获取 `class_ro_t` 中的 `instanceSize`
uint32_t instanceSize = *(uint32_t *)((unsigned long long)ro + 4 +4);

//7、获取 `class_rw_t` 中 `method`

uintptr_t method = *((uintptr_t *)(((unsigned long long)rw_t)+4+4+8));

/*注:因为`method`成员没有用指针修饰,所以`method`的值即为
union
{List *list;
uintptr_t arrayAndFlag;

}
的值。
*/
//8、获取 `arrayAndFlag`的值
uintptr_t arrayAndFlag = method;

//9、//获取 `List **`
uintptr_t *list;
//判断 `arrayAndFlag` 标志位
if (arrayAndFlag & 1) {
//获取 `array_t *`
uintptr_t array_t = arrayAndFlag & ~1;
list = &(*((uintptr_t *)((unsigned long long)array_t+8)));
}else {
//获取 `List **`
list = &arrayAndFlag;
}

//10、获取`method_list_t` 中的 `count`
//获取`method_list_t *`指针
uintptr_t method_list_t = *list;
//获取`count`
uint32_t count = *(uint32_t *)((unsigned long long)method_list_t + 4);

//11、获取 `method_list_t`中的`first`
/* 这里为了更好的获取方法信息,定义结构体struct method_t */
struct sl_method_t {
SEL name;
const char *types;
IMP imp;
};
struct sl_method_t first =*((struct sl_method_t *)((unsigned long long)method_list_t + 4 + 4));

//12、打印所有方法的信息
//获取数组中每个元素的大小
uint32_t entsizeAndFlags = *(uint32_t *)((unsigned long long)method_list_t);
/*
uint32_t entsize() const {
return entsizeAndFlags & ~FlagMask;
}
*/
uint32_t entsize = entsizeAndFlags & ~0x3;

for (int i=0; i<count; i++) {

struct sl_method_t method = *((struct sl_method_t *)((unsigned long long)method_list_t + 4 + 4 + i*entsize));

printf("method_name:%s \t method_types:%s \t method_imp:0x%llx",method.name,method.types,(unsigned long long)(method.imp));

printf("\n");
}

/*这里下断点*/ return UIApplicationMain(argc, argv, nil, appDelegateClassName);

}
1
2
3
4
5
6
7
8
method_name:setStr: 	 method_types:v24@0:8@16 	 method_imp:0x1000a8120
method_name:setObj: method_types:v24@0:8@16 method_imp:0x1000a8174
method_name:.cxx_destruct method_types:v16@0:8 method_imp:0x1000a81b4
method_name:str method_types:@16@0:8 method_imp:0x1000a80e8
method_name:setI: method_types:v20@0:8i16 method_imp:0x1000a80c4
method_name:i method_types:i16@0:8 method_imp:0x1000a80a8
method_name:obj method_types:@16@0:8 method_imp:0x1000a8158
(lldb)

到这里,就意味着struct class_rw_t 中的method_array_t methods已经完全被解析出来了。
那么同样的,其他几个字段的内容,也是可以获取到的。(例如: property_array_t properties; protocol_array_t protocols)。这里就不一一读取。
我们接下来的问题是:

获取到这些信息可以用来做什么?

最简单的,我们既然可以获取到类中每个方法的IMP,那么我们是否可以替换掉其指针,到达Hook的效果呢?
简单修改一下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
NSString * sl_str(id self,SEL sel){
return @"hooked!!!";
}

int main(int argc, char * argv[]) {
NSString * appDelegateClassName;

appDelegateClassName = NSStringFromClass([AppDelegate class]);

CustomClass *c = [CustomClass new];
NSLog(@"c:%p",c);

//1、通过实例对象`c`,获取 isa 指针
uintptr_t c_isa = (*(uintptr_t *)c) & 0x0000000FFFFFFFF8ULL;

//2、获取 `class_data_bits_t bits` 的值
uintptr_t c_bits = *(uintptr_t *)((unsigned long long)c_isa + 4*8);

//3、获取 `class_rw_t *`指针
uintptr_t rw_t = c_bits & 0x00007ffffffffff8UL;

//4、获取 `class_rw_t` 中 ro 指针
uintptr_t ro = *(uintptr_t *)((unsigned long long)rw_t+4+4);

//5、获取 `class_ro_t` 中的 `name`

const char *name = (const char *)(*(uintptr_t *)((unsigned long long)ro + 4 +4 + 4 +4 + 8));

//6、获取 `class_ro_t` 中的 `instanceSize`
uint32_t instanceSize = *(uint32_t *)((unsigned long long)ro + 4 +4);

//7、获取 `class_rw_t` 中 `method`

uintptr_t method = *((uintptr_t *)(((unsigned long long)rw_t)+4+4+8));

/*注:因为`method`成员没有用指针修饰,所以`method`的值即为
union
{List *list;
uintptr_t arrayAndFlag;

}
的值。
*/
//8、获取 `arrayAndFlag`的值
uintptr_t arrayAndFlag = method;

//9、//获取 `List **`
uintptr_t *list;
//判断 `arrayAndFlag` 标志位
if (arrayAndFlag & 1) {
//获取 `array_t *`
uintptr_t array_t = arrayAndFlag & ~1;
list = &(*((uintptr_t *)((unsigned long long)array_t+8)));
}else {
//获取 `List **`
list = &arrayAndFlag;
}

//10、获取`method_list_t` 中的 `count`
//获取`method_list_t *`指针
uintptr_t method_list_t = *list;
//获取`count`
uint32_t count = *(uint32_t *)((unsigned long long)method_list_t + 4);

//11、获取 `method_list_t`中的`first`
/* 这里为了更好的获取方法信息,定义结构体struct method_t */
struct sl_method_t {
SEL name;
const char *types;
IMP imp;
};
struct sl_method_t first =*((struct sl_method_t *)((unsigned long long)method_list_t + 4 + 4));

//12、打印所有方法的信息
//获取数组中每个元素的大小
uint32_t entsizeAndFlags = *(uint32_t *)((unsigned long long)method_list_t);
/*
uint32_t entsize() const {
return entsizeAndFlags & ~FlagMask;
}
*/
uint32_t entsize = entsizeAndFlags & ~0x3;

for (int i=0; i<count; i++) {

struct sl_method_t method = *((struct sl_method_t *)((unsigned long long)method_list_t + 4 + 4 + i*entsize));

printf("method_name:%s \t method_types:%s \t method_imp:0x%llx",method.name,method.types,(unsigned long long)(method.imp));

/*Hook `str` 方法*/
if (!strcmp((char *)(method.name), "str")) {
//修改IMP的值
*(uintptr_t *)((unsigned long long)method_list_t + 4 + 4 + i*entsize+8+8) = (uintptr_t)&sl_str;
}

printf("\n");
}

//调用,检查是否被hook
NSString *str = [c str];

/*这里下断点*/ return UIApplicationMain(argc, argv, nil, appDelegateClassName);

}
1
2
3
4
5
6
7
8
9
10
11
method_name:setStr: 	 method_types:v24@0:8@16 	 method_imp:0x1000880fc
method_name:setObj: method_types:v24@0:8@16 method_imp:0x100088150
method_name:.cxx_destruct method_types:v16@0:8 method_imp:0x100088190
method_name:str method_types:@16@0:8 method_imp:0x1000880c4
method_name:setI: method_types:v20@0:8i16 method_imp:0x1000880a0
method_name:i method_types:i16@0:8 method_imp:0x100088084
method_name:obj method_types:@16@0:8 method_imp:0x100088134
(lldb) po str
hooked!!!

(lldb)

看到最后的hooked!!!说明,hook成功了!
当然,完整的hook,并不是单纯的替换函数指针即可,还需要考虑很多问题,这里只是简单的测试一下可行性。