前期回顾
- 研究iOS底层,从对象入手
- alloc 分析的思维 LLDB x/4gx ;汇编断点;源码;
- 内存申请 多少 原则8字节对齐 16字节对齐
- po person VC LGPerson ->isa
- 元类 -> isa走位图
- 类的内存结构 isa superclass cache_t bit(protocol)
- bit 属性 方法 clang
- cache_t 结构 mask ocpp buckets(是什么样的结构?)
- 哈希函数 insert 3/4 处理
isa
1、isa存储类 + 其他值(是否正在释放+引用计数+weak)
2、对象的isa和类的不一样
3、类的isa和元类的一样
isa流程图
1、cache数据结构
cache_t 结构体的定义位于:objc > ProjectHeaders > objc-runtime-new.h > cache_t
struct cache_t {
private:
explicit_atomic<uintptr_t> _bucketsAndMaybeMask; // 8
union {
struct {
explicit_atomic<mask_t> _maybeMask; // 4
#if __LP64__
uint16_t _flags; // 2
#endif
uint16_t _occupied; // 2
};
explicit_atomic<preopt_cache_t *> _originalPreoptCache; // 8
};
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
// _bucketsAndMaybeMask is a buckets_t pointer
// _maybeMask is the buckets mask
static constexpr uintptr_t bucketsMask = ~0ul;
static_assert(!CONFIG_USE_PREOPT_CACHES, "preoptimized caches not supported");
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
static constexpr uintptr_t maskShift = 48;
static constexpr uintptr_t maxMask = ((uintptr_t)1 << (64 - maskShift)) - 1;
static constexpr uintptr_t bucketsMask = ((uintptr_t)1 << maskShift) - 1;
static_assert(bucketsMask >= MACH_VM_MAX_ADDRESS, "Bucket field doesn't have enough bits for arbitrary pointers.");
#if CONFIG_USE_PREOPT_CACHES
static constexpr uintptr_t preoptBucketsMarker = 1ul;
static constexpr uintptr_t preoptBucketsMask = bucketsMask & ~preoptBucketsMarker;
#endif
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
// _bucketsAndMaybeMask is a buckets_t pointer in the low 48 bits
// _maybeMask is unused, the mask is stored in the top 16 bits.
// How much the mask is shifted by.
static constexpr uintptr_t maskShift = 48;
// Additional bits after the mask which must be zero. msgSend
// takes advantage of these additional bits to construct the value
// `mask << 4` from `_maskAndBuckets` in a single instruction.
static constexpr uintptr_t maskZeroBits = 4;
// The largest mask value we can store.
static constexpr uintptr_t maxMask = ((uintptr_t)1 << (64 - maskShift)) - 1;
// The mask applied to `_maskAndBuckets` to retrieve the buckets pointer.
static constexpr uintptr_t bucketsMask = ((uintptr_t)1 << (maskShift - maskZeroBits)) - 1;
// Ensure we have enough bits for the buckets pointer.
static_assert(bucketsMask >= MACH_VM_MAX_ADDRESS,
"Bucket field doesn't have enough bits for arbitrary pointers.");
#if CONFIG_USE_PREOPT_CACHES
static constexpr uintptr_t preoptBucketsMarker = 1ul;
#if __has_feature(ptrauth_calls)
// 63..60: hash_mask_shift
// 59..55: hash_shift
// 54.. 1: buckets ptr + auth
// 0: always 1
static constexpr uintptr_t preoptBucketsMask = 0x007ffffffffffffe;
static inline uintptr_t preoptBucketsHashParams(const preopt_cache_t *cache) {
uintptr_t value = (uintptr_t)cache->shift << 55;
// masks have 11 bits but can be 0, so we compute
// the right shift for 0x7fff rather than 0xffff
return value | ((objc::mask16ShiftBits(cache->mask) - 1) << 60);
}
#else
// 63..53: hash_mask
// 52..48: hash_shift
// 47.. 1: buckets ptr
// 0: always 1
static constexpr uintptr_t preoptBucketsMask = 0x0000fffffffffffe;
static inline uintptr_t preoptBucketsHashParams(const preopt_cache_t *cache) {
return (uintptr_t)cache->hash_params << 48;
}
#endif
#endif // CONFIG_USE_PREOPT_CACHES
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
// _bucketsAndMaybeMask is a buckets_t pointer in the top 28 bits
// _maybeMask is unused, the mask length is stored in the low 4 bits
static constexpr uintptr_t maskBits = 4;
static constexpr uintptr_t maskMask = (1 << maskBits) - 1;
static constexpr uintptr_t bucketsMask = ~maskMask;
static_assert(!CONFIG_USE_PREOPT_CACHES, "preoptimized caches not supported");
#else
#error Unknown cache mask storage type.
#endif
bool isConstantEmptyCache() const;
bool canBeFreed() const;
mask_t mask() const;
#if CONFIG_USE_PREOPT_CACHES
void initializeToPreoptCacheInDisguise(const preopt_cache_t *cache);
const preopt_cache_t *disguised_preopt_cache() const;
#endif
void incrementOccupied();
void setBucketsAndMask(struct bucket_t *newBuckets, mask_t newMask);
void reallocate(mask_t oldCapacity, mask_t newCapacity, bool freeOld);
void collect_free(bucket_t *oldBuckets, mask_t oldCapacity);
static bucket_t *emptyBuckets();
static bucket_t *allocateBuckets(mask_t newCapacity);
static bucket_t *emptyBucketsForCapacity(mask_t capacity, bool allocate = true);
static struct bucket_t * endMarker(struct bucket_t *b, uint32_t cap);
void bad_cache(id receiver, SEL sel) __attribute__((noreturn, cold));
public:
// The following four fields are public for objcdt's use only.
// objcdt reaches into fields while the process is suspended
// 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;
void initializeToEmpty();
#if CONFIG_USE_PREOPT_CACHES
bool isConstantOptimizedCache(bool strict = false, uintptr_t empty_addr = (uintptr_t)&_objc_empty_cache) const;
bool shouldFlush(SEL sel, IMP imp) const;
bool isConstantOptimizedCacheWithInlinedSels() const;
Class preoptFallbackClass() const;
void maybeConvertToPreoptimized();
void initializeToEmptyOrPreoptimizedInDisguise();
#else
inline bool isConstantOptimizedCache(bool strict = false, uintptr_t empty_addr = 0) const { return false; }
inline bool shouldFlush(SEL sel, IMP imp) const {
return cache_getImp(cls(), sel) == imp;
}
inline bool isConstantOptimizedCacheWithInlinedSels() const { return false; }
inline void initializeToEmptyOrPreoptimizedInDisguise() { initializeToEmpty(); }
#endif
void insert(SEL sel, IMP imp, id receiver);
void copyCacheNolock(objc_imp_cache_entry *buffer, int len);
void destroy();
void eraseNolock(const char *func);
static void init();
static void collectNolock(bool collectALot);
static size_t bytesForCapacity(uint32_t cap);
#if __LP64__
bool getBit(uint16_t flags) const {
return _flags & flags;
}
void setBit(uint16_t set) {
__c11_atomic_fetch_or((_Atomic(uint16_t) *)&_flags, set, __ATOMIC_RELAXED);
}
void clearBit(uint16_t clear) {
__c11_atomic_fetch_and((_Atomic(uint16_t) *)&_flags, ~clear, __ATOMIC_RELAXED);
}
#endif
#if FAST_CACHE_ALLOC_MASK
bool hasFastInstanceSize(size_t extra) const
{
if (__builtin_constant_p(extra) && extra == 0) {
return _flags & FAST_CACHE_ALLOC_MASK16;
}
return _flags & FAST_CACHE_ALLOC_MASK;
}
size_t fastInstanceSize(size_t extra) const
{
ASSERT(hasFastInstanceSize(extra));
if (__builtin_constant_p(extra) && extra == 0) {
return _flags & FAST_CACHE_ALLOC_MASK16;
} else {
size_t size = _flags & FAST_CACHE_ALLOC_MASK;
// remove the FAST_CACHE_ALLOC_DELTA16 that was added
// by setFastInstanceSize
return align16(size + extra - FAST_CACHE_ALLOC_DELTA16);
}
}
void setFastInstanceSize(size_t newSize)
{
// Set during realization or construction only. No locking needed.
uint16_t newBits = _flags & ~FAST_CACHE_ALLOC_MASK;
uint16_t sizeBits;
// Adding FAST_CACHE_ALLOC_DELTA16 allows for FAST_CACHE_ALLOC_MASK16
// to yield the proper 16byte aligned allocation size with a single mask
sizeBits = word_align(newSize) + FAST_CACHE_ALLOC_DELTA16;
sizeBits &= FAST_CACHE_ALLOC_MASK;
if (newSize <= sizeBits) {
newBits |= sizeBits;
}
_flags = newBits;
}
#else
bool hasFastInstanceSize(size_t extra) const {
return false;
}
size_t fastInstanceSize(size_t extra) const {
abort();
}
void setFastInstanceSize(size_t extra) {
// nothing
}
#endif
};
复制代码
分析:
explicit_atomic<uintptr_t> _bucketsAndMaybeMask; // 8
是指针地址,存首地址;void insert(SEL sel, IMP imp, id receiver);
2、cache底层LLDB分析
(lldb) p/x pClass
(Class) $0 = 0x0000000100008428 LGPerson
(lldb) p/x (cache_t *)0x0000000100008438
(cache_t *) $1 = 0x0000000100008438
(lldb) p *$1
(cache_t) $2 = {
_bucketsAndMaybeMask = {
std::__1::atomic<unsigned long> = {
Value = 4298515328
}
}
= {
= {
_maybeMask = {
std::__1::atomic<unsigned int> = {
Value = 0
}
}
_flags = 32808
_occupied = 0
}
_originalPreoptCache = {
std::__1::atomic<preopt_cache_t *> = {
Value = 0x0000802800000000
}
}
}
}
(lldb) p $2._bucketsAndMaybeMask
(explicit_atomic<unsigned long>) $3 = {
std::__1::atomic<unsigned long> = {
Value = 4298515328
}
}
(lldb) p $2._maybeMask
(explicit_atomic<unsigned int>) $4 = {
std::__1::atomic<unsigned int> = {
Value = 0
}
}
(lldb) p $2._originalPreoptCache
(explicit_atomic<preopt_cache_t *>) $5 = {
std::__1::atomic<preopt_cache_t *> = {
Value = 0x0000802800000000
}
}
(lldb) p $2.buckets()
(bucket_t *) $6 = 0x0000000100362380
(lldb) p [p saySomething]
2021-06-25 14:24:54.244298+0800 KCObjcBuild[7282:138289] -[LGPerson saySomething]
(lldb) p [p saySomething]
2021-06-25 14:24:54.432538+0800 KCObjcBuild[7282:138289] -[LGPerson saySomething]
(lldb) p/x (cache_t *)0x0000000100008438
(cache_t *) $7 = 0x0000000100008438
(lldb) p $7
(cache_t *) $7 = 0x0000000100008438
(lldb) p *$7
(cache_t) $8 = {
_bucketsAndMaybeMask = {
std::__1::atomic<unsigned long> = {
Value = 4314922352
}
}
= {
= {
_maybeMask = {
std::__1::atomic<unsigned int> = {
Value = 7
}
}
_flags = 32808
_occupied = 3
}
_originalPreoptCache = {
std::__1::atomic<preopt_cache_t *> = {
Value = 0x0003802800000007
}
}
}
}
(lldb) p $8.buckets()[1]
(bucket_t) $9 = {
_sel = {
std::__1::atomic<objc_selector *> = "" {
Value = ""
}
}
_imp = {
std::__1::atomic<unsigned long> = {
Value = 3440056
}
}
}
(lldb) p $9.sel()
(SEL) $10 = "class"
(lldb) p $9.imp(nil,pClass)
(IMP) $11 = 0x000000010034f990 (libobjc.A.dylib`-[NSObject class] at NSObject.mm:2243)
复制代码
3、脱离源码分析
#import <Foundation/Foundation.h>
#import "LGPerson.h"
#import <objc/runtime.h>
typedef uint32_t mask_t; // x86_64 & arm64 asm are less efficient with 16-bits
struct kc_bucket_t {
SEL _sel;
IMP _imp;
};
struct kc_cache_t {
struct kc_bucket_t *_bukets; // 8
mask_t _maybeMask; // 4
uint16_t _flags; // 2
uint16_t _occupied; // 2
};
struct kc_class_data_bits_t {
uintptr_t bits;
};
// cache class
struct kc_objc_class {
Class isa;
Class superclass;
struct kc_cache_t cache; // formerly cache pointer and vtable
struct kc_class_data_bits_t bits;
};
int main(int argc, const char * argv[]) {
@autoreleasepool {
LGPerson *p = [LGPerson alloc];
Class pClass = p.class; // objc_clas
[p say1];
[p say2];
[p say3];
[p say4];
[p say1];
[p say2];
// [p say3];
[pClass sayHappy];
struct kc_objc_class *kc_class = (__bridge struct kc_objc_class *)(pClass);
NSLog(@"%hu - %u",kc_class->cache._occupied,kc_class->cache._maybeMask);
// 0 - 8136976 count
// 1 - 3
// 1: 源码无法调试
// 2: LLDB
// 3: 小规模取样
// 底层原理
// a: 1-3 -> 1 - 7
// b: (null) - 0x0 方法去哪???
// c: 2 - 7 + say4 - 0xb850 + 没有类方法
// d: NSObject 父类
for (mask_t i = 0; i<kc_class->cache._maybeMask; i++) {
struct kc_bucket_t bucket = kc_class->cache._bukets[i];
NSLog(@"%@ - %pf",NSStringFromSelector(bucket._sel),bucket._imp);
}
NSLog(@"Hello, World!");
}
return 0;
}
复制代码
4、cache底层原理分析
待补充
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END