I cache_t structure analysis
First, attach a cache_t principle analysis diagram
1.cache_t source code analysis
First, take a look at the general structure of cache in the source code
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 }; //Partial code
Try through the rough analysis of the source code and print through LLDB mode. The code is as follows
int main(int argc, const char * argv[]) { @autoreleasepool { LGPerson *p = [LGPerson alloc]; Class pClass = [LGPerson class]; [p saySomething]; NSLog(@"%@",pClass); } return 0; }
Start breakpoint debugging
//Get cache_ Tinternal properties (lldb) p/x pClass (Class) $0 = 0x0000000100004518 LGPerson (lldb) x/4gx $0 0x100004518: 0x0000000100004540 0x0000000100353140 0x100004528: 0x000000010034b380 0x0000802800000000 (lldb) p (cache_t *)0x000000010034b380 (cache_t *) $2 = 0x000000010034b380 (lldb) p *$2 (cache_t) $3 = { _bucketsAndMaybeMask = { std::__1::atomic<unsigned long> = { Value = 0 } } = { = { _maybeMask = { std::__1::atomic<unsigned int> = { Value = 0 } } _flags = 0 _occupied = 0 } _originalPreoptCache = { std::__1::atomic<preopt_cache_t *> = { Value = 0x0000000000000000 } } } } (lldb)
Through the above debugging, it seems that there is no useful data, so you can only continue to check the source code to see if there is any method to obtain the desired data. Refer to the previous analysis
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;//translation 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) //Note means to indicate what 64 bits in memory represent respectively. Refer to the previous isa memory analysis // 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); }
Judging from this source code, it is found that cache_ It seems that some data in the structure of t can also be obtained through memory translation
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;//Structure pointer Class cls() const;
Discovery bucket_t is used very frequently, so click in to have a look
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 // Compute the ptrauth signing modifier from &_imp, newSel, and cls. uintptr_t modifierForSEL(bucket_t *base, SEL newSel, Class cls) const { return (uintptr_t)base ^ (uintptr_t)newSel ^ (uintptr_t)cls; }
Finally found the data we are interested in_ imp,_sel, and each sel corresponds to an imp, then continue debugging through LLDB
2.LLDB debugging and analysis cache_t
Through cache_ For memory translation of buckets() in T, you must first understand that buckets() is not an array. Through the previous structural analysis, it is a structure pointer. Naturally, the translation here is also carried out in the unit of (bucket_t *) to obtain another bucket_ T's memory address, and each structure bucket_ There are corresponding in t_ imp,_sel, the results are as follows
//Find cache_ Tinternal method (lldb) p/x pClass (Class) $0 = 0x0000000100004500 LGPerson (lldb) p (cache_t *)0x0000000100004510 (cache_t *) $1 = 0x0000000100004510 (lldb) p *$1 (cache_t) $2 = { _bucketsAndMaybeMask = { std::__1::atomic<unsigned long> = { Value = 4301545824 } } = { = { _maybeMask = { std::__1::atomic<unsigned int> = { Value = 3 } } _flags = 32808 _occupied = 1 } _originalPreoptCache = { std::__1::atomic<preopt_cache_t *> = { Value = 0x0001802800000003 } } } } (lldb) p $2.buckets()[0]//Memory translation. buckets() here is not an array (bucket_t) $3 = { _sel = { std::__1::atomic<objc_selector *> = (null) { Value = (null) } } _imp = { std::__1::atomic<unsigned long> = { Value = 0 } } } (lldb) p $2.buckets()[1] (bucket_t) $4 = { _sel = { std::__1::atomic<objc_selector *> = (null) { Value = (null) } } _imp = { std::__1::atomic<unsigned long> = { Value = 0 } } } (lldb) p $2.buckets()[2] (bucket_t) $5 = { _sel = { std::__1::atomic<objc_selector *> = "" { Value = "" } } _imp = { std::__1::atomic<unsigned long> = { Value = 30976 } } }
I believe you also found the problem, that is, print p $2 When buckets() [2], values have values, but not before. After you add and call more methods, the order of values is not orderly, which involves hash function and Zipper method In this aspect, you can check it by yourself. In short, the main purpose of this method is to speed up the efficiency of method insertion and reading, because if you use an array to access the method list, it will greatly affect the efficiency of insertion and reading hash function Each bucket address will have a corresponding hash value Zipper method Solve the conflict problem of hash algorithm to facilitate addition, deletion, modification and query
The problem now is how to get the sel and imp in buckets and continue the source code
inline SEL sel() const { return _sel.load(memory_order_relaxed); } inline IMP imp(UNUSED_WITHOUT_PTRAUTH bucket_t *base, Class cls) const {
From the source code, you can see that you can call the corresponding function
(lldb) p $6.imp(nil,pClass) (IMP) $7 = 0x0000000100003c30 (KCObjcBuild`-[LGPerson sayNoBB]) (lldb) p $6.sel() (SEL) $8 = "sayNoBB"
II cache_ Analysis of source code structure
1. Why break away from source code analysis
I believe that through these days of study, we will find that various data in the source code, such as the structure, will be mixed with many static variables, some environmental judgments, or the definition and implementation of functions, so that we will be confused and disturbed in the viewing process. If we remove the code we don't care about and concentrate on analyzing the content we want to explore, Is it easier to sort out the relationship? Then let's try it and summarize it as follows
- The source code cannot be debugged
- Complex structure relationship and cumbersome LLDB debugging
- Small scale sampling (it will not be affected by other internal factors, so the understanding is simpler and clearer)
2. Source code analysis
First of all, we all know_ objc_class, the main member variable, we also define the following to simplify the code
// cache class struct ycx_objc_class { Class isa; Class superclass; struct ycx_cache_t cache; // formerly cache pointer and vtable struct ycx_class_data_bits_t bits; };
And then_ class_data_bits_t
struct ycx_class_data_bits_t { uintptr_t bits; };
Then cache_t
typedef uint32_t mask_t; // x86_64 & arm64 asm are less efficient with 16-bits //struct bucket_t *cache_t::buckets() const //{ // uintptr_t addr = _bucketsAndMaybeMask.load(memory_order_relaxed); // return (bucket_t *)(addr & bucketsMask); //} //_ Buckets substitution_ bucketsAndMaybeMask struct ycx_cache_t { struct ycx_bucket_t *_bukets; // 8 mask_t _maybeMask; // 4 uint16_t _flags; // 2 uint16_t _occupied; // 2 };
Because we want to analyze the cache_t's bucket_t. Let's also define it
struct ycx_bucket_t { SEL _sel; IMP _imp; };
Next, I will try the code. Of course, the following code is the result of many attempts combined with the source code
LGPerson *p = [[LGPerson alloc]init]; Class pClass = p.class; // objc_clas [p say1]; [p say2]; [p say3]; [p say4]; [p say5]; [p say6]; [pClass sayHappy]; struct ycx_objc_class *ycx_class = (__bridge struct ycx_objc_class *)(pClass); NSLog(@"%hu - %u",ycx_class->cache._occupied,ycx_class->cache._maybeMask); for (mask_t i = 0; i<ycx_class->cache._maybeMask; i++) { struct ycx_bucket_t bucket = ycx_class->cache._bukets[i]; NSLog(@"%@ - %pf",NSStringFromSelector(bucket._sel),bucket._imp); }
The printing results are as follows
2021-06-24 15:55:12.950582+0800 003-cache_t Break away from source environment analysis[17820:270757] 5 - 7 2021-06-24 15:55:12.950650+0800 003-cache_t Break away from source environment analysis[17820:270757] say4 - 0x5910f 2021-06-24 15:55:12.950742+0800 003-cache_t Break away from source environment analysis[17820:270757] say6 - 0x59b0f 2021-06-24 15:55:12.950788+0800 003-cache_t Break away from source environment analysis[17820:270757] say3 - 0x5920f 2021-06-24 15:55:12.950854+0800 003-cache_t Break away from source environment analysis[17820:270757] (null) - 0x0f 2021-06-24 15:55:12.950914+0800 003-cache_t Break away from source environment analysis[17820:270757] say5 - 0x5940f 2021-06-24 15:55:12.950966+0800 003-cache_t Break away from source environment analysis[17820:270757] say2 - 0x58f0f 2021-06-24 15:55:12.950997+0800 003-cache_t Break away from source environment analysis[17820:270757] (null) - 0x0f
3. Several problems are found through multiple tests:
- Why does it exist (null) - 0x0f
- Where are the class methods
- What does 5-7 mean
- Why does a method call not print out
III Analyze the principle of cache inserting data
1.cache insertion
First, find the main process code of insert
void cache_t::insert(SEL sel, IMP imp, id receiver) { runtimeLock.assertLocked(); // Never cache before +initialize is done if (slowpath(!cls()->isInitialized())) { return; } if (isConstantOptimizedCache()) { _objc_fatal("cache_t::insert() called with a preoptimized cache for %s", cls()->nameForLogging()); } #if DEBUG_TASK_THREADS return _collecting_in_critical(); #else #if CONFIG_USE_CACHE_LOCK mutex_locker_t lock(cacheUpdateLock); #endif ASSERT(sel != 0 && cls()->isInitialized()); // Use the cache as-is if until we exceed our expected fill ratio. mask_t newOccupied = occupied() + 1; // 1+1 unsigned oldCapacity = capacity(), capacity = oldCapacity; if (slowpath(isConstantEmptyCache())) { // Cache is read-only. Replace it. The cache is read-only and can only be replaced if (!capacity) capacity = INIT_CACHE_SIZE;//4 reallocate(oldCapacity, capacity, /* freeOld */false); } // CACHE_END_MARKER 1 is reserved for comparison calculation after + 1 else if (fastpath(newOccupied + CACHE_END_MARKER <= cache_fill_ratio(capacity))) { // Cache is less than 3/4 or 7/8 full. Use it as-is. //More than 75% capacity expansion } #if CACHE_ALLOW_FULL_UTILIZATION else if (capacity <= FULL_UTILIZATION_CACHE_SIZE && newOccupied + CACHE_END_MARKER <= capacity) { // Allow 100% cache utilization for small buckets. Use it as-is. } #endif else { //Double capacity expansion in case of more than 75% capacity = capacity ? capacity * 2 : INIT_CACHE_SIZE; if (capacity > MAX_CACHE_SIZE) { capacity = MAX_CACHE_SIZE; } //true is passed here, indicating that the old buckets memory will be cleaned up in the case of capacity expansion reallocate(oldCapacity, capacity, true); } bucket_t *b = buckets(); mask_t m = capacity - 1; // 4-1=3 mask_t begin = cache_hash(sel, m);//hash address mask_t i = begin; // Scan for the first unused slot and insert there. // There is guaranteed to be an empty slot. //Prevent hash conflict and cycle hash until it is different from the initial hash do { if (fastpath(b[i].sel() == 0)) { incrementOccupied();//_ occupied++; To put it bluntly, insert a new bucket every time_ t _ occupied++ b[i].set<Atomic, Encoded>(b, sel, imp, cls()); return; } if (b[i].sel() == sel) { // The entry was added to the cache by some other thread // before we grabbed the cacheUpdateLock. return; } } while (fastpath((i = cache_next(i, m)) != begin)); bad_cache(receiver, (SEL)sel); #endif // !DEBUG_TASK_THREADS }
Reallocate cache function cache_t::reallocate code
void cache_t::reallocate(mask_t oldCapacity, mask_t newCapacity, bool freeOld) { bucket_t *oldBuckets = buckets(); bucket_t *newBuckets = allocateBuckets(newCapacity); // Cache's old contents are not propagated. // This is thought to save cache memory at the cost of extra cache fills. // fixme re-measure this //Cached old content is not propagated //This is considered to save cache memory at the cost of additional cache filling. //Measure again ASSERT(newCapacity > 0); ASSERT((uintptr_t)(mask_t)(newCapacity-1) == newCapacity-1); //Set BucketsAndMask to insert empty container (bucket) setBucketsAndMask(newBuckets, newCapacity - 1); //Clear original memory if (freeOld) { collect_free(oldBuckets, oldCapacity); } }
2. Analyze the source code and verify the cache insertion algorithm
Through the above source code analysis, the following conclusions are drawn
- 1. Each bucket_ T (bucket) corresponds to a sel, imp memory address
- 2. Storage bucket_ The base value of T (bucket) is capacity = INIT_CACHE_SIZE = 4
- 3. Double capacity expansion if it exceeds 3 / 4 (capacity = capacity? Capacity * 2: init_cache_size)
- 4. Each capacity expansion is to re open up new storage space (setBucketsAndMask(newBuckets, newCapacity - 1))
- 5. Each capacity expansion will empty the original cache collection_ Free (oldbuckets, oldcapacity), restart storage;
- 6. Each bucket_ T (bucket) addresses are hash function cache_hash(sel, m) is calculated (so printing will find that it is not orderly)
Next, let's verify:
1. Do not call any method first. You can see that class will be called by default
2. Supplementary method say1 can be found_ occupied is 2, no more than 3 / 4 of 4, that is, 3, so the printing method is say1, class
3. Continue to supplement the method say2. After more than 3 / 4, double the capacity expansion, and reduce 1 to 7. Then empty the old method, and only say2 is left
4. Let him expand the capacity again to determine whether our conclusion is correct
3. Final questions
Through the above calculation, I believe careful students have found a problem. It seems that the capacity expansion has started before 3 / 4 every time. For example, only adding method say1 and adding the default class will have two methods, but when you add say2, the capacity has been expanded, that is, 2-3 ((3 + 1) * 3 / 4). Then add five methods, and the first one is cleared, that is, 5-7 ((7 + 1) * 3 / 4), It seems that each expansion starts when it is 1 less than 3 / 4 of the maximum capacity. In order to confirm my conjecture, it is verified once
//If the code is too long, intercept the key places [p say1];//2 (+class) [p say2];//First expansion [p say3]; [p say4]; [p say5]; [p say6];//5 [p say7];//Second expansion [p say8]; [p say9]; [p say10]; [p say11]; [p say12]; [p say13]; [p say14]; [p say15]; [p say16]; [p say3];//11 [p say4];//The third expansion
As a result, only one say4 was printed
2021-06-25 16:11:18.967561+0800 003-cache_t Break away from source environment analysis[18318:292158] LGPerson say : -[LGPerson say1] 2021-06-25 16:11:18.968502+0800 003-cache_t Break away from source environment analysis[18318:292158] LGPerson say : -[LGPerson say2] 2021-06-25 16:11:18.968550+0800 003-cache_t Break away from source environment analysis[18318:292158] LGPerson say : -[LGPerson say3] 2021-06-25 16:11:18.968577+0800 003-cache_t Break away from source environment analysis[18318:292158] LGPerson say : -[LGPerson say4] 2021-06-25 16:11:18.968599+0800 003-cache_t Break away from source environment analysis[18318:292158] LGPerson say : -[LGPerson say5] 2021-06-25 16:11:18.968620+0800 003-cache_t Break away from source environment analysis[18318:292158] LGPerson say : -[LGPerson say6] 2021-06-25 16:11:18.968641+0800 003-cache_t Break away from source environment analysis[18318:292158] LGPerson say : -[LGPerson say7] 2021-06-25 16:11:18.968662+0800 003-cache_t Break away from source environment analysis[18318:292158] LGPerson say : -[LGPerson say8] 2021-06-25 16:11:18.968683+0800 003-cache_t Break away from source environment analysis[18318:292158] LGPerson say : -[LGPerson say9] 2021-06-25 16:11:18.969202+0800 003-cache_t Break away from source environment analysis[18318:292158] LGPerson say : -[LGPerson say10] 2021-06-25 16:11:18.969246+0800 003-cache_t Break away from source environment analysis[18318:292158] LGPerson say : -[LGPerson say11] 2021-06-25 16:11:18.969282+0800 003-cache_t Break away from source environment analysis[18318:292158] LGPerson say : -[LGPerson say12] 2021-06-25 16:11:18.969306+0800 003-cache_t Break away from source environment analysis[18318:292158] LGPerson say : -[LGPerson say13] 2021-06-25 16:11:18.969327+0800 003-cache_t Break away from source environment analysis[18318:292158] LGPerson say : -[LGPerson say14] 2021-06-25 16:11:18.969348+0800 003-cache_t Break away from source environment analysis[18318:292158] LGPerson say : -[LGPerson say15] 2021-06-25 16:11:18.969375+0800 003-cache_t Break away from source environment analysis[18318:292158] LGPerson say : -[LGPerson say16] 2021-06-25 16:11:18.969396+0800 003-cache_t Break away from source environment analysis[18318:292158] LGPerson say : -[LGPerson say3] 2021-06-25 16:11:18.969468+0800 003-cache_t Break away from source environment analysis[18318:292158] LGPerson say : -[LGPerson say4] 2021-06-25 16:11:18.978601+0800 003-cache_t Break away from source environment analysis[18318:292158] 1 - 31 2021-06-25 16:11:18.978656+0800 003-cache_t Break away from source environment analysis[18318:292158] (null) - 0x0f 2021-06-25 16:11:18.978684+0800 003-cache_t Break away from source environment analysis[18318:292158] (null) - 0x0f 2021-06-25 16:11:18.978707+0800 003-cache_t Break away from source environment analysis[18318:292158] (null) - 0x0f 2021-06-25 16:11:18.978728+0800 003-cache_t Break away from source environment analysis[18318:292158] (null) - 0x0f 2021-06-25 16:11:18.978747+0800 003-cache_t Break away from source environment analysis[18318:292158] (null) - 0x0f 2021-06-25 16:11:18.978767+0800 003-cache_t Break away from source environment analysis[18318:292158] (null) - 0x0f 2021-06-25 16:11:18.978787+0800 003-cache_t Break away from source environment analysis[18318:292158] (null) - 0x0f 2021-06-25 16:11:18.978875+0800 003-cache_t Break away from source environment analysis[18318:292158] say4 - 0xc7e0f 2021-06-25 16:11:18.978905+0800 003-cache_t Break away from source environment analysis[18318:292158] (null) - 0x0f 2021-06-25 16:11:18.978926+0800 003-cache_t Break away from source environment analysis[18318:292158] (null) - 0x0f 2021-06-25 16:11:18.979386+0800 003-cache_t Break away from source environment analysis[18318:292158] (null) - 0x0f 2021-06-25 16:11:18.979411+0800 003-cache_t Break away from source environment analysis[18318:292158] (null) - 0x0f 2021-06-25 16:11:18.979432+0800 003-cache_t Break away from source environment analysis[18318:292158] (null) - 0x0f 2021-06-25 16:11:18.979451+0800 003-cache_t Break away from source environment analysis[18318:292158] (null) - 0x0f 2021-06-25 16:11:18.979576+0800 003-cache_t Break away from source environment analysis[18318:292158] (null) - 0x0f 2021-06-25 16:11:18.979601+0800 003-cache_t Break away from source environment analysis[18318:292158] (null) - 0x0f 2021-06-25 16:11:18.979783+0800 003-cache_t Break away from source environment analysis[18318:292158] (null) - 0x0f 2021-06-25 16:11:18.979843+0800 003-cache_t Break away from source environment analysis[18318:292158] (null) - 0x0f 2021-06-25 16:11:18.979958+0800 003-cache_t Break away from source environment analysis[18318:292158] (null) - 0x0f 2021-06-25 16:11:18.979991+0800 003-cache_t Break away from source environment analysis[18318:292158] (null) - 0x0f 2021-06-25 16:11:18.980014+0800 003-cache_t Break away from source environment analysis[18318:292158] (null) - 0x0f 2021-06-25 16:11:18.980035+0800 003-cache_t Break away from source environment analysis[18318:292158] (null) - 0x0f 2021-06-25 16:11:18.980076+0800 003-cache_t Break away from source environment analysis[18318:292158] (null) - 0x0f 2021-06-25 16:11:18.980161+0800 003-cache_t Break away from source environment analysis[18318:292158] (null) - 0x0f 2021-06-25 16:11:18.980339+0800 003-cache_t Break away from source environment analysis[18318:292158] (null) - 0x0f 2021-06-25 16:11:18.980471+0800 003-cache_t Break away from source environment analysis[18318:292158] (null) - 0x0f 2021-06-25 16:11:18.980755+0800 003-cache_t Break away from source environment analysis[18318:292158] (null) - 0x0f 2021-06-25 16:11:18.980797+0800 003-cache_t Break away from source environment analysis[18318:292158] (null) - 0x0f 2021-06-25 16:11:18.980822+0800 003-cache_t Break away from source environment analysis[18318:292158] (null) - 0x0f 2021-06-25 16:11:18.980844+0800 003-cache_t Break away from source environment analysis[18318:292158] (null) - 0x0f 2021-06-25 16:11:18.980864+0800 003-cache_t Break away from source environment analysis[18318:292158] (null) - 0x0f 2021-06-25 16:11:18.981074+0800 003-cache_t Break away from source environment analysis[18318:292158] Hello, World! Program ended with exit code: 0
It can be seen from this that in fact, during each capacity expansion, a piece of memory will be occupied by default. As for what it is, don't worry. For more highlights, please listen to the next chapter!!!