001-cache数据结构
翻看2020WWDC,我们能看到类里面有个
Method cache
翻看objc源码,可以看到类的结构也可以看到
cache_t cache,接下来探究一下
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
…
}
因为是类结构里面的
cache,所以先拿到类对象
打印类地址
(lldb) p/x pClass
(Class) $0 = 0x00000001000084b8
平移
16个单位(加0x10)
(lldb) p (cache_t *)0x00000001000084c8
(cache_t *) $1 = 0x00000001000084c8
打印
$1
p *$1
(cache_t) $2 = {
_bucketsAndMaybeMask = {
std::__1::atomic<unsigned long> = {
Value = 4298576216
}
}
= {
= {
_maybeMask = {
std::__1::atomic<unsigned int> = {
Value = 0
}
}
_flags = 32808
_occupied = 0
}
_originalPreoptCache = {
std::__1::atomic<preopt_cache_t *> = {
Value = 0x0000802800000000
}
}
}
}
再看源码
cache_t结构
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;
};
...
}
完全一致,再查看源码
...
public:
// The following four fields are public for objcdt's use only.
//下面四个字段是公共的,仅供objcdt使用。
// objcdt reaches into fields while the process is suspended
//当进程挂起时,objcdt进入字段
// hence doesn't care for locks and pesky little details like this
//因此不关心锁和像这样烦人的小细节
// and can safely use these.
//可以安全地使用这些。
unsigned capacity() const;
struct bucket_t *buckets() const;
Class cls() const;
#if CONFIG_USE_PREOPT_CACHES
const preopt_cache_t *preopt_cache() const;
#endif
mask_t occupied() const;
...
可以看到有一个
bucket_t类型的结构体指针buckets,查看bucket_t
...
struct bucket_t {
private:
// IMP-first is better for arm64e ptrauth and no worse for arm64.
// SEL-first is better for armv7* and i386 and x86_64.
#if __arm64__
explicit_atomic<uintptr_t> _imp;
explicit_atomic<SEL> _sel;
#else
explicit_atomic<SEL> _sel;
explicit_atomic<uintptr_t> _imp;
#endif
...
就是
imp和sel,可以得出如下结构
classDiagram
Class --> cache_t
cache_t --> bucket_t
Class : Class ISA
Class : Class superclass
Class: cache_t cache
Class: class_data_bits_t bits
class cache_t{
buckets
}
class bucket_t{
imp
sel
}
接下来进行LLDB调试,验证一下
002-cache底层LLDB分析
测试代码如下
先拿到类对象
(lldb) p/x pClass
(Class) $0 = 0x00000001000084b8
平移
16个字节,拿到cache地址
(lldb) p (cache_t *)0x00000001000084c8
(cache_t *) $1 = 0x00000001000084c8
打印
$1
(lldb) p *$1
(cache_t) $2 = {
_bucketsAndMaybeMask = {
std::__1::atomic<unsigned long> = {
Value = 4298576216
}
}
= {
= {
_maybeMask = {
std::__1::atomic<unsigned int> = {
Value = 0
}
}
_flags = 32808
_occupied = 0
}
_originalPreoptCache = {
std::__1::atomic<preopt_cache_t *> = {
Value = 0x0000802800000000
}
}
}
}
获取对象的
buckets
(lldb) p $2.buckets()
(bucket_t *) $3 = 0x0000000100371158
(lldb) p *$3
(bucket_t) $4 = {
_imp = {
std::__1::atomic<unsigned long> = {
Value = 0
}
}
_sel = {
std::__1::atomic<objc_selector *> = (null) {
Value = nil
}
}
}
返回
nil,想想应该是没有缓存,方法列表如下:
@interface ApplePerson : NSObject{
NSString *salary;
}
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *workNumber;
- (void)sayHenXi;
+ (void)sayNiuBi;
@end
lldb调用sayHenXi
(lldb) p [p sayHenXi]
2021-10-11 16:42:38.792443+0800 ObjcBuild[40603:4400942] -[ApplePerson sayHenXi]
再次跑之前的调试代码
(lldb) p/x pClass
(Class) $0 = 0x00000001000084b8
(lldb) p (cache_t *)0x00000001000084c8
(cache_t *) $1 = 0x00000001000084c8
(lldb) p *$1
(cache_t) $2 = {
_bucketsAndMaybeMask = {
std::__1::atomic<unsigned long> = {
Value = 844429232542976
}
}
= {
= {
_maybeMask = {
std::__1::atomic<unsigned int> = {
Value = 0
}
}
_flags = 32808
_occupied = 1
}
_originalPreoptCache = {
std::__1::atomic<preopt_cache_t *> = {
Value = 0x0001802800000000
}
}
}
}
拿到
$2,是想要的cache_t类型,它有一个buckets的属性
public:
…
unsigned capacity() const;
struct bucket_t *buckets() const;
Class cls() const;
…
获取buckets()
(lldb) p $2.buckets()
(bucket_t *) $3 = 0x0000000100933a60
得到
bucket_t *类型的$3,查看buckets
buckets
指向方法数据结构的指针数组。这个数组不能包含超过掩码+ 1的项。注意,指针可以是NULL,
表示缓存桶未被占用,并且被占用的桶可能不是连续的。这个数组可能会随着时间的推移而增长。
既然是数组
array,那挨个试,数组从0开始
(lldb) p $3[0]
(bucket_t) $4 = {
_imp = {
std::__1::atomic<unsigned long> = {
Value = 0
}
}
_sel = {
std::__1::atomic<objc_selector *> = (null) {
Value = nil
}
}
}
获取
sel方法
(lldb) p $4.sel()
(SEL) $5 = (null)
nil那就从接着1
(lldb) p $3[1]
(bucket_t) $6 = {
_imp = {
std::__1::atomic<unsigned long> = {
Value = 45576
}
}
_sel = {
std::__1::atomic<objc_selector *> = "" {
Value = ""
}
}
}
再获取
sel方法
(lldb) p $6.sel()
(SEL) $7 = "sayHenXi"
最终拿到了
sayHenXi的方法,再获取imp,先看源码
inline IMP imp(UNUSED_WITHOUT_PTRAUTH bucket_t *base, Class cls) const {
…
}
传入两个值,一个
bucket_t类型指针,先传个nil,第二个是类对象
(lldb) p $6.imp(nil,pClass)
(IMP) $8 = 0x00000001000036b0 (ObjcBuild`-[ApplePerson sayHenXi])
获取到的
IMP类型的对象[ApplePerson sayHenXi]就是ApplePerson类的函数指针