Hi 👋

  • 📚 Personal site: lab.lawliet
  • 📦 Technical documentation
  • 🐙 making
  • Wechat: RyukieW

My apps

Mine Elic endless sky ladder Dream of books
type The game financial
AppStore Elic Umemi

preface

  • This article is based on
    • Objc4-818.2 -The source code
    • IOS14.6 iPhoneX
  • Source version, system version, device different words, real machine debugging assembly and source will be different. Be careful when self-debugging
  • Important parts are commented in the source code,.Indicates that some other architectural or irrelevant code has been omitted
  • In order to facilitate reading, the assembly code doesThe indentation to adjustandBlock commentsIt should be a lot easier to read
  • The computer sideBetter reading experience
  • This article looks tired, but uses a more relaxed and pleasant topic.
    • Pair it with a cup of ☕️
  • objc_msgSendThere is much more to this article, and it will be updated and corrected
    • The specific update content can be viewedUpdate record

Welcome attention, collection, communication, catch insects

Update record

Updated date change
20210701 First release
20210703 1. Add a slow search process; 2. Optimize the flow chart; 3. Supplement “Class implementation and initialization”

How do we usually call methods?

There are three ways to do this: OC Method

[model doSomething]
Copy the code

NSObject API

[model isKindOfClass:[NSString class]]
Copy the code

Objc API.

class_getSuperclass([RYModel class])
Copy the code

The API hierarchy is as follows:

1.1 Three methods of experiment

RYModel *obj = [[RYModel alloc] init];
[obj dosomethingA];

[obj performSelector:NSSelectorFromString(@"dosomethingA")];

// < Xcode 12
// objc_msgSend((id)obj, sel_registerName("dosomethingA"));

// Xcode 12 needs to be converted to Build setting
((void (*)(id, SEL))(void *)objc_msgSend)(obj, sel_registerName("dosomethingA"));
Copy the code

Points to note:

  • #import <objc/message.h>
  • Xcode 12The following
    • Build setting -> objc_msgSend -> Enable Strict Checking of objc_msgSend Calls -> NO
  • Xcode 12The above
    • Objc_msgSend can be properly invoked only when the transfer type is changed

Output: can be called normally

ObjcMsgSend[17313:6255181] -[RYModel dosomethingA]
ObjcMsgSend[17313:6255181] -[RYModel dosomethingA]
ObjcMsgSend[17313:6255181] -[RYModel dosomethingA]
Copy the code

1.2 Calling superclass methods

@interface RYSubModel : RYModel

- (BOOL)isSub;

@end

@implementation RYSubModel

- (BOOL)isSub {
    NSLog(@"%s",__FUNCTION__);
    [super dosomethingA];
    return  YES;
}

@end
Copy the code

RYSubModel inherits RYModel. Let’s look at isSub in the C++ file.

static BOOL _I_RYSubModel_isSub(RYSubModel * self, SEL _cmd) {
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_py_7v1wvf813z5bmw0hr97yplwc0000gn_T_RYModel_c52934_mi_6,__FUNCTION__);  ((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("RYSubModel"))}, sel_registerName("dosomethingA"));
    return ((bool)1);
}
Copy the code

Here [super dosomethingA] turns to C++ and finds an objc_msgSendSuper method that calls a method found within the superclass.

1.3 objc_msgSendSuper

Let’s see if we can just call objc_msgSendSuper.

  • objc_msgSendSuper(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
    • super
      • Objc_super pointer to the structure
    • op
      • selector
/// Specifies the superclass of an instance. 
struct objc_super {
    /// Specifies an instance of a class.
    __unsafe_unretained _Nonnull id receiver;

    /// Specifies the particular superclass of the instance to message. 
#if! defined(__cplusplus) && ! __OBJC2__
    /* For compatibility with old objc-runtime.h header */
    __unsafe_unretained _Nonnull Class class;
#else
    __unsafe_unretained _Nonnull Class super_class;
#endif
    /* super_class is the first class to search */
};
Copy the code

objc_super

/// Specifies the superclass of an instance. 
struct objc_super {
    /// Specifies an instance of a class.
    __unsafe_unretained _Nonnull id receiver;

    /// Specifies the particular superclass of the instance to message. 
#if! defined(__cplusplus) && ! __OBJC2__// 
    /* For compatibility with old objc-runtime.h header */
    __unsafe_unretained _Nonnull Class class;
#else
    __unsafe_unretained _Nonnull Class super_class;
#endif
    /* super_class is the first class to search */
};
Copy the code

code

struct objc_super ry_super;
ry_super.receiver = obj;
ry_super.super_class = [RYModel class];
((void (*)(id, SEL))(void *)objc_msgSendSuper)((__bridge id)(&ry_super), sel_registerName("dosomethingA")); Output: ObjcMsgSend [17330:6265149] -[RYModel dosomethingA] // Calling the superclass method succeeded
Copy the code

Example Modify the parameters of the parent class

Let’s try modifying the parent class to its own class object. ry_super.super_class = [RYSubModel class]; , the output is still correct. There’s a search chain inside.

1.4 the source code

Let’s look at the source code for objc_msgSend.

The source code for the ARM64 version is in objc-msg-ARM64.s and is compiled.

You can also view assembly code by adding symbolic breakpoints.

Two, compilation instructions

2.1 Main assembly instruction remarks used here

instruction The sample note
cmp cmp x0, #0x0 The difference is calculated, the result is discarded and only the status register is adjustedCPSRSign a
b.le b.le 0x1afad2b98; meaningless than or equal toResults,< = 0, jump instruction0x1afad2b98
b.eq b.eq 0x1afad2b68 meaningequalResults,= = 0, jump instruction0x1afad2b68
and and x16, x13 X16 & X13 with operation
ldr ldr x13, [x0] Put the data from memory at address X0 into register X13
lsr lsr #48 Logic moves 48 bits to the right
lsl lsl #4 Logic moves 4 bits to the left
ldp ldp x17, x9, [x12] Take the value of X12 and store it in X17,x9
b.ne b.ne 0x1afad2b54 Not equal, jump instruction0x1afad2b54
tbnz tbnz p11, #0, LLookupPreopt\Function Test and branch Not zero, that is, if bit 0 of P11 is not 0LLookupPreopt\Function
TBZ To 0, andtbnzsimilar
adrp adrp x0, 1 1. Shift the value of 1 12 bits to the left1 0000 0000 0000 == 0x1000; \n 2. Reset the low 12 bits of the PC register0x1045228b0 ==> 0x104522000; 3. Add the results of 1 and 2 to register X0
eor Exclusive or

2.2 Register Description

register

  • x0-x7Is used to pass parameters.
    • X0: message receiver
    • The x1: sel

☕️ Take a break. You’ll be tired at the beginning

Three _objc_msgSend source code assembly

ENTRY _objc_msgSend
UNWIND _objc_msgSend, NoFrame

// Determine whether a message receiver exists
cmp	p0, #0			// nil check and tagged pointer check compare the P0 register to 0

#if SUPPORT_TAGGED_POINTERS 
SUPPORT_TAGGED_POINTERS = 1; // SUPPORT_TAGGED_POINTERS = 1
// Jump to LNilOrTagged based on the status register result p0-0 <= 0
b.le	LNilOrTagged		// (MSB tagged pointer looks negative)
#else
// LReturnZero = nil
b.eq	LReturnZero
#endif

// ISA is stored in P13
ldr	p13, [x0]		// p13 = isa

// Get the Class from ISA. P16 stores a pointer to the Class
GetClassFromIsa_p16 p13, 1, x0	// p16 = class

// After obtaining ISA, execute:
LGetIsaDone:
// calls imp or objc_msgSend_uncached
// Call IMP or check the cache
CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached

END_ENTRY _objc_msgSend
Copy the code

3.1 GetClassFromIsa_p16

Get Class to store in P16

/******************************************************************** * GetClassFromIsa_p16 src, needs_auth, auth_address * src is a raw isa field. Sets p16 to the corresponding class pointer. * The raw isa might be an indexed isa to be decoded, or a * packed isa that needs to be masked. * * On exit: * src is unchanged * p16 is a class pointer * x10 is clobbered * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
.macro GetClassFromIsa_p16 src, needs_auth, auth_address /* note: auth_address is not required if ! needs_auth */

#if SUPPORT_INDEXED_ISA.#elif __LP64__
.if \needs_auth == 0 // _cache_getImp takes an authed class already
	mov	p16, \src
.else // needs_auth == 1
	// 64-bit packed isa
	ExtractISA p16, \src, \auth_address
.endif
#else
	// 32-bit raw isa
	mov	p16, \src

#endif

.endmacro
Copy the code

3.2 ExtractISA Obtains ClassISA

// XS/XS Max/XR A12 processor start
#if __has_feature(ptrauth_calls)
// JOP. .macro ExtractISA// ISA($1) & ISA_MASK saves p16p16($0), which is class
	and	$0, $1, #ISA_MASK
#if ISA_SIGNING_AUTH_MODE == ISA_SIGNING_STRIP
	xpacd	$0
#elif ISA_SIGNING_AUTH_MODE == ISA_SIGNING_AUTH
	mov	x10, $2
	movk	x10, #ISA_SIGNING_DISCRIMINATOR, LSL #48
	autda	$0, x10
#endif
.endmacro
...

// JOP
#else
// not JOP. .macro ExtractISA// ISA($1) & ISA_MASK saves p16($0), which is class
	and    $0, $1, #ISA_MASK
.endmacro

// not JOP
#endif
Copy the code

3.3 CacheLookup Searches for the cache

Parameters:

  • Mode: indicates the cache matching Mode
    • CacheHit
  • Function
    • Called after a cache is hit
  • MissLabelDynamic
    • Missed call
  • MissLabelConstant
    • MissLabelConstant is only used for the GETIMP variant

The official comments

  • I suggest you check it out to help you understand
/******************************************************************** * * CacheLookup NORMAL|GETIMP|LOOKUP 
      
        MissLabelDynamic MissLabelConstant * * MissLabelConstant is only used for the GETIMP variant. * * Locate the implementation for a selector in a class method cache. * * When this is used in a function that doesn't hold the runtime  lock, * this represents the critical section that may access dead memory. * If the kernel causes one of these functions to go down the recovery * path, we pretend the lookup failed by jumping the JumpMiss branch. * * Takes: * x1 = selector * x16 = class to be searched * * Kills: * x9,x10,x11,x12,x13,x15,x1 * * Untouched: * x14 * * On exit: (found) calls or returns IMP * with x16 = class, x17 = IMP * In LOOKUP mode, the two low bits are set to 0x3 * if we hit a constant cache (used in objc_trace) * (not found) jumps to LCacheMiss * with x15 = class * For constant caches in LOOKUP mode, the low bit * of x16 is set to 0x1 to indicate we had to fallback. * In addition, when LCacheMiss is __objc_msgSend_uncached or * __objc_msgLookup_uncached, 0x2 will be set in x16 * to remember we took the slowpath. * So the two low bits of x16 on exit mean: * 0: dynamic hit * 1: fallback to the parent class, when there is a preoptimized cache * 2: slowpath * 3: preoptimized cache hit * ********************************************************************/
      
Copy the code

The source code

.macro CacheLookup Mode, Function, MissLabelDynamic, MissLabelConstant
	//
	// Restart protocol:
	//
	// As soon as we're past the LLookupStart\Function label we may have
	// loaded an invalid cache pointer or mask.
	//
	// When task_restartable_ranges_synchronize() is called,
	// (or when a signal hits us) before we're past LLookupEnd\Function,
	// then our PC will be reset to LLookupRecover\Function which forcefully
	// jumps to the cache-miss codepath which have the following
	// requirements:
	//
	// GETIMP:
	// The cache-miss is just returning NULL (setting x0 to 0)
	//
	// NORMAL and LOOKUP:
	// - x0 contains the receiver
	// - x1 contains the selector
	// - x16 contains the isa
	// - other registers are set as per calling conventions
	//

    // Move x16(class) to x15
	mov	x15, x16			// stash the original isa

LLookupStart\Function:
...


.endmacro

Copy the code

The source code is longer, we see parts

3.4 LLookupStart\Function Start search

  • Gets the current bucket_T and HashIndex to look up the cache
    • Here’s the source codeif-elseIndent to adjust, easy to view
LLookupStart\Function:
	// p1 = _cmd, p16 = isa
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
// arm64&&__lp64__ : MacOS or emulator.#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
// ARM64 && __LP64__
    // ClassISA + # cache_t(p11)
	ldr	p11, [x16, #CACHE]			// p11 = mask|buckets
    #if CONFIG_USE_PREOPT_CACHES
        / / real machine
        #if __has_feature(ptrauth_calls)
        // > < span style = "box-sizing: border-box; color: RGB (74, 74, 74)
            // Determine whether bit 0 of cache_t(p11) is not 0, and if it is 0, whether there is a shared cache (first_shared_cache_sel)
            // No cache: keep going
            // With cache: jump to LLookupPreopt\Function
            tbnz	p11, #0, LLookupPreopt\Function
            // cache_t(p11) & #0x0000ffffffffffff -> p10(buckets)
            // Take the lower 48 bits
            and	p10, p11, #0x0000ffffffffffff	// p10 = buckets
        #else
            and	p10, p11, #0x0000fffffffffffe	// p10 = buckets
            tbnz	p11, #0, LLookupPreopt\Function
        #endif

        // (_cmd ^ _cmd >> 7) -> p12
        eor	p12, p1, p1, LSR #7
        // ((cache_t & p12) >> 48) -> p12(HashIndex)
        and	p12, p12, p11, LSR #48		// x12 = (_cmd(p1) ^ (_cmd(p1) >> 7)) & mask
    #else
        and	p10, p11, #0x0000ffffffffffff	// p10 = buckets
        and	p12, p1, p11, LSR #48		// x12 = _cmd & mask
    #endif // CONFIG_USE_PREOPT_CACHES

#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
// ARM64 && ! __LP64__ : real machine#else
#error Unsupported cache mask storage for ARM64.
#endif
    // ((buckets + HashIndex) << 1+PTRSHIFT) -> p13(bucket_t)
    // Locate bucket_t by offset and store it in P13
    // p12(HashIndex)
    // Buckets' is the first address of buckets, and HashIndex is the index of the Hash table
    // For example, if the HashIndex is 1, the bucket_t length is offset by 16 (sel8+imp8).
    // Shift '(1+PTRSHIFT))' left to 'HashIndex-1', which is consistent with cache_t's logic for inserting into the cache, otherwise it will not be found.
    // P13 stores the bucket_t address
	add	p13, p10, p12, LSL #(1+PTRSHIFT)
						// p13 = buckets + ((_cmd & mask) << (1+PTRSHIFT))
// #define PTRSHIFT 3 // 1<

// Iterate through the cache

// After the first loop

Copy the code

3.5 Loop traversal (on binary search)

						// do {
// #define BUCKET_SIZE (2 * __SIZEOF_POINTER__)
P17 (imp) p9(sel)
// Then '*bucket--' moves forward 2 pointer lengths
// That is, the length of a bucket_t
// *bucket--: value first, then --
1:	ldp	p17, p9, [x13], #-BUCKET_SIZE	// {imp, sel} = *bucket--
    // Compare p1(sel passed in) with P9 (SEL passed in)
	cmp	p9, p1				// if (sel ! = _cmd) {
	b.ne	3f				// scan more
						// } else {
// CacheHit \Mode is called
2:	CacheHit \Mode				// hit: call or return imp
						/ /}
3:	cbz	p9, \MissLabelDynamic		// if (sel == 0) goto Miss;
	cmp	p13, p10			// } while (bucket >= buckets)
    // Return to 1 and continue the loop
	b.hs	1b

Copy the code

3.6 CacheHit

// CacheHit: x17 = cached IMP, x10 = address of buckets, x1 = SEL, x16 = isa
.macro CacheHit
.if $0 == NORMAL
    // Call the cache IMP
	TailCallCachedImp x17, x10, x1, x16	// authenticate and call imp
.elseif $0 == GETIMP
	mov	p0, p17
	cbz	p0, 9f			// don't ptrauth a nil imp
	AuthAndResignAsIMP x0, x10, x1, x16	// authenticate imp and re-sign as IMP
9:	ret				// return IMP
.elseif $0 == LOOKUP
	// No nil check for ptrauth: the caller would crash anyway when they
	// jump to a nil IMP. We don't care if that jump also fails ptrauth.
	AuthAndResignAsIMP x17, x10, x1, x16	// authenticate imp and re-sign as IMP
	cmp	x16, x15
	cinc	x16, x16, ne			// x16 += 1 when x15 ! = x16 (for instrumentation ; fallback to the parent class)
	ret				// return imp via x17
.else
.abort oops
.endif
.endmacro
Copy the code

3.7 TailCallCachedImp Implements IMP caching

#if __has_feature(ptrauth_calls)
// JOP. .macro TailCallCachedImp// $0 = cached imp, $1 = address of cached imp, $2 = SEL, $3 = isa
    // (addressOfCachedIMP ^ SEL) -> addressOfCachedIMP
	eor	$1, $1, $2	// mix SEL into ptrauth modifier
    // (addressOfCachedIMP ^ isa) -> addressOfCachedIMP
	eor	$1, $1, $3  // mix isa into ptrauth modifier
    // This command is not understood
	brab	$0, $1
.endmacro

// JOP
#else
// not JOP. .macro TailCallCachedImp// $0 = cached imp, $1 = address of cached imp, $2 = SEL, $3 = isa
    // (cachedIMP ^ isa) -> cachedIMP
	eor	$0, $0, $3
    // Jump to perform cachedIMP
	br	$0
.endmacro

...

// not JOP
#endif
Copy the code

3.8 Loop Traversal (Binary search)

  • The loop stops when it returns to the first bucket.
  • Buckets + (mask << 1+PTRSHIFT) BUCKETS + (mask << 1+PTRSHIFT)
  • Keep iterating until you’re done
  • If it still misses, _objc_msgSend_uncached
// wrap-around:
// p10 = first bucket
// p11 = mask (and maybe other bits on LP64)
// p12 = _cmd & mask
//
// A full cache can happen with CACHE_ALLOW_FULL_UTILIZATION.
// So stop when we circle back to the first probed bucket
// rather than when hitting the first bucket again.
//
// Note that we might probe the initial bucket twice
// when the first probed slot is the last entry.. add p13, p10, p11, LSR #(48 - (1+PTRSHIFT))
						// p13 = buckets + (mask << 1+PTRSHIFT)
						// see comment about maskZeroBits. add p12, p10, p12, LSL #(1+PTRSHIFT)
						// p12 = first probed bucket

						// do {
4:	ldp	p17, p9, [x13], #-BUCKET_SIZE	// {imp, sel} = *bucket--
	cmp	p9, p1				// if (sel == _cmd)
	b.eq	2b				// goto hit
	cmp	p9, #0				// } while (sel ! = 0 &&
	ccmp	p13, p12, #0, ne		// bucket > first_probed)
	b.hi	4b
Copy the code

3.9 LLookupPreopt\Function: searches for data in the shared cache

#if CONFIG_USE_PREOPT_CACHES. LLookupPreopt\Function:#if __has_feature(ptrauth_calls)
        // cache_t(p11) & # 0x007ffFFFFFFFFffffffe Saves to P10
        and	p10, p11, #0x007ffffffffffffe	// p10 = buckets
        / / verification? Did not find the specific function of this instruction
        autdb	x10, x16			// auth as early as possible
    #endif
        // Find the data in the shared cache
        // x12 = (_cmd - first_shared_cache_sel)
        adrp	x9, _MagicSelRef@PAGE
        ldr	p9, [x9, _MagicSelRef@PAGEOFF]
        sub	p12, p1, p9

        // w9 = ((_cmd - first_shared_cache_sel) >> hash_shift & hash_mask)
    #if __has_feature(ptrauth_calls)
        // bits 63.. 60 of x11 are the number of bits in hash_mask
        // bits 59.. 55 of x11 is hash_shift

        lsr	x17, x11, #55			// w17 = (hash_shift, ...)
        lsr	w9, w12, w17			// >>= shift

        lsr	x17, x11, #60			// w17 = mask_bits
        mov	x11, #0x7fff
        lsr	x11, x11, x17			// p11 = mask (0x7fff >> mask_bits)
        and	x9, x9, x11			// &= mask
    #else
        // bits 63.. 53 of x11 is hash_mask
        // bits 52.. 48 of x11 is hash_shift
        lsr	x17, x11, #48			// w17 = (hash_shift, hash_mask)
        lsr	w9, w12, w17			// >>= shift
        and	x9, x9, x11, LSR #53		// &= mask
    #endif

        ldr	x17, [x10, x9, LSL #3]		// x17 == sel_offs | (imp_offs << 32)
        cmp	x12, w17, uxtw

    .if \Mode == GETIMP
    ...
    .else
        b.ne    5f                // cache miss
        sub    x17, x16, x17, LSR #32        // imp = isa - imp_offs

    .if \Mode == NORMAL
        br	x17
    .elseif \Mode == LOOKUP
    ...

    5:	ldursw	x9, [x10, #- 8 -]			// offset -8 is the fallback offset
        add	x16, x16, x9			// compute the fallback isa
        b	LLookupStart\Function		// lookup again with a new isa
    .endif
#endif // CONFIG_USE_PREOPT_CACHES
Copy the code

3.10 GetTaggedClass

  • if SUPPORT_TAGGED_POINTERS
// Look up the class for a tagged pointer in x0, placing it in x16.
.macro GetTaggedClass

	and	x10, x0, #0x7		// x10 = small tag
	asr	x11, x0, #55		// x11 = large tag with 1s filling the top (because bit 63 is 1 on a tagged pointer)
	cmp	x10, #7		// tag == 7?
	csel	x12, x11, x10, eq	// x12 = index in tagged pointer classes array, negative for extended tags.
					// The extended tag array is placed immediately before the basic tag array
					// so this looks into the right place either way. The sign extension done
					// by the asr instruction produces the value extended_tag - 256, which produces
					// the correct index in the extended tagged pointer classes array.

	// x16 = _objc_debug_taggedpointer_classes[x12]
	adrp	x10, _objc_debug_taggedpointer_classes@PAGE
	add	x10, x10, _objc_debug_taggedpointer_classes@PAGEOFF
	ldr	x16, [x10, x12, LSL #3]

.endmacro
Copy the code

3.11 LNilOrTagged

#if SUPPORT_TAGGED_POINTERS
LNilOrTagged:
b.eq	LReturnZero		// nil check
GetTaggedClass
b	LGetIsaDone
// SUPPORT_TAGGED_POINTERS
#endif
Copy the code

3.12 LReturnZero

LReturnZero:
// x0 is already zero
mov	x1, #0
movi	d0, #0
movi	d1, #0
movi	d2, #0
movi	d3, #0
ret
Copy the code

☕️ Take another break when you see here. The real machine assembly looks more exciting ~

Four, real machine assembly debugging

Personally, I find it more intuitive to look at the assembly of symbolic breakpoints, but there are comments in the source code.

Real machine symbol breakpoint as a reference, and the source code with a look again will be more clear.

Assembly flow chart

libobjc.A.dylib`objc_msgSend:
/*** Start Determines whether the receiver is null ***/
->  0x197b05140 <+0>:   cmp    x0, #0x0                  ; =0x0 
    0x197b05144 <+4>:   b.le   0x197b051e8               ; <+168>
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * /

X13 = isa ***/
    0x197b05148 <+8>:   ldr    x13, [x0]
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * /

/*** Start GetClassFromIsa_p16 -> ExtractISA: isa & ISA_MASK -> x16
    0x197b0514c <+12> :and    x16, x13, #0xffffffff8
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * /

/*** Start CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_cached ***/
    0x197b05150 <+16>:  mov    x15, x16 // LGetIsaDone

    /*** Start LLookupStart\Function: ClassISA + #CACHE(0x10) save to p11, that is, cache_t(p11) check whether bit 0 of cache_t is not 0 and if it is 0, whether there is a shared CACHE. Go to LLookupPreopt ***/
    0x197b05154 <+20>:  ldr    x11, [x16, #0x10]
    0x197b05158 <+24> :and    x10, x11, #0xfffffffffffe
    0x197b0515c <+28>:  tbnz   w11, #0x0.0x197b051b0    ; <+112>

        /*** Start Compute the cache_hash (sel ^ sel >> 7) -> p12((cache_t&p12) >> 48) -> p12(HashIndex) ***/
        0x197b05160 <+32>:  eor    x12, x1, x1, lsr #7
        0x197b05164 <+36> :and    x12, x12, x11, lsr #48
        / * * * * * * * * * * * * * * * * * * * * * * * * * * * /

    / * * * * * * * * * * * * * * * * * * * * * * * * * * * /

    /*** Start select the first address of the buckets from x13 x12(HashIndex); HashIndex is the index of the Hash table. A HashIndex of 1 offsets a bucket_t by the length of 16 (sel8+imp8) to the left of '(1+PTRSHIFT)) to the position of' hashinde-1 ', which is consistent with the cache_t logic for inserting the cache, otherwise it will not be found. Buckets + HashIndex << 1+PTRSHIFT) -> p13(bucket_t) where x13 stores the target bucket_t address ***/
    0x197b05168 <+40>:  add    x13, x10, x12, lsl #4
    / * * * * * * * * * * * * * * * * * * * * * * * * * * * /

    P17 (IMP) p9(sel) 0x10; p17(IMP) p9(sel) 0x10 = 2, that is, 2 units' *bucket-- 'moves forward 2 pointer lengths (sel + IMP), that is, moves forward a bucket_t length, and points x13 to the previous cache compared to _cmd ***/
    0x197b0516c <+44>:  ldp    x17, x9, [x13], #-0x10
    0x197b05170 <+48>:  cmp    x9, x1
    0x197b05174 <+52>:  b.ne   0x197b05180               ; <+64>

        TailCallCachedImp (cachedIMP ^ isa) -> cachedIMP ***/ 
        0x197b05178 <+56>:  eor    x17, x17, x16
        0x197b0517c <+60>:  br     x17
        / * * * * * * * * * * * * * * * * * * * * * * * * * * * /

    /*** Start if (sel(x9) == 0) goto Miss; While (bucket >= buckets) return to +44 and continue the loop ***/
    0x197b05180 <+64>:  cbz    x9, 0x197b054c0           ; _objc_msgSend_uncached
    0x197b05184 <+68>:  cmp    x13, x10// 
    0x197b05188 <+72>:  b.hs   0x197b0516c               ; <+44>
    / * * * * * * * * * * * * * * * * * * * * * * * * * * * /


    /*** Start stop the loop when it returns to the first bucket: * Move p13 to Buckets + (mask << 1+PTRSHIFT) * Continue the traversal until the traversal is complete * if still no hit, _objc_msgSend_uncached ***/
    0x197b0518c <+76>:  add    x13, x10, x11, lsr #44
    0x197b05190 <+80>:  add    x12, x10, x12, lsl #4

        /*** Start the second iteration of ***/
        0x197b05194 <+84>:  ldp    x17, x9, [x13], #-0x10

            TailCallCachedImp ***/ 
            0x197b05198 <+88>:  cmp    x9, x1
            0x197b0519c <+92>:  b.eq   0x197b05178               ; <+56>
            / * * * * * * * * * * * * * * * * * * * * * * * * * * * /
        
        // Loop condition judgment
        0x197b051a0 <+96>:  cmp    x9, #0x0                  ; =0x0 
        0x197b051a4 <+100>: ccmp   x13, x12, #0x0, ne
        // bucket > first_probed)
        0x197b051a8 <+104>: b.hi   0x197b05194               ; <+84>
        / * * * * * * * * * * * * * * * * * * * * * * * * * * * /

        0x197b051ac <+108>: b      0x197b054c0               ; _objc_msgSend_uncached
    / * * * * * * * * * * * * * * * * * * * * * * * * * * * /

    /*** Start LLookupPreopt\Function Find data in the shared cache ***/

    // x12 = (_cmd - first_shared_cache_sel)
    0x197b051b0 <+112>: adrp   x9, 227000
    0x197b051b4 <+116>: add    x9, x9, #0x47b            ; =0x47b 
    0x197b051b8 <+120>: sub    x12, x1, x9

    // bits 63.. 53 of x11 is hash_mask
    // bits 52.. 48 of x11 is hash_shift
    0x197b051bc <+124>: lsr    x17, x11, #48
    0x197b051c0 <+128>: lsr    w9, w12, w17
    0x197b051c4 <+132> :and    x9, x9, x11, lsr #53

    // x17 == sel_offs | (imp_offs << 32)
    0x197b051c8 <+136>: ldr    x17, [x10, x9, lsl #3]
    0x197b051cc <+140>: cmp    x12, w17, uxtw

    0x197b051d0 <+144>: b.ne   0x197b051dc               ; <+156>
    0x197b051d4 <+148>: sub    x17, x16, x17, lsr #32

    0x197b051d8 <+152>: br     x17

        /*** Start 5. ***/
        // offset -8 is the fallback offse
        0x197b051dc <+156>: ldursw x9, [x10, #-0x8]
        // compute the fallback isa
        0x197b051e0 <+160>: add    x16, x16, x9
        // lookup again with a new isa
        0x197b051e4 <+164>: b      0x197b05154               ; <+20>
        / * * * * * * * * * * * * * * * * * * * * * * * * * * * /

    / * * * * * * * * * * * * * * * * * * * * * * * * * * * /


    /*** Start LNilOrTagged ***/ when the receiver is empty
    0x197b051e8 <+168>: b.eq   0x197b0520c               ; <+204>

        /*** Start GetTaggedClass ***/
        0x197b051ec <+172> :and    x10, x0, #0x7
        0x197b051f0 <+176>: asr    x11, x0, #55
        0x197b051f4 <+180>: cmp    x10, #0x7                 ; =0x7 
        0x197b051f8 <+184>: csel   x12, x11, x10, eq
        0x197b051fc <+188>: adrp   x10, 255422
        0x197b05200 <+192>: add    x10, x10, #0xb60          ; =0xb60 
        0x197b05204 <+196>: ldr    x16, [x10, x12, lsl #3]
        / * * * * * * * * * * * * * * * * * * * * * * * * * * * /

    0x197b05208 <+200>: b      0x197b05150               ; <+16> // LGetIsaDone
    / * * * * * * * * * * * * * * * * * * * * * * * * * * * /

    /*** Start LReturnZero ***/
    0x197b0520c <+204>: mov    x1, #0x0
    0x197b05210 <+208>: movi   d0, #0000000000000000
    0x197b05214 <+212>: movi   d1, #0000000000000000
    0x197b05218 <+216>: movi   d2, #0000000000000000
    0x197b0521c <+220>: movi   d3, #0000000000000000
    0x197b05220 <+224>: ret    
    / * * * * * * * * * * * * * * * * * * * * * * * * * * * /   

Copy the code

Cached __objc_msgSend_uncached

STATIC_ENTRY __objc_msgSend_uncached
UNWIND __objc_msgSend_uncached, FrameWithNoSaves

// THIS IS NOT A CALLABLE C FUNCTION
// Out-of-band r10 is the searched class

// r10 is already the class to search
MethodTableLookup NORMAL	// r11 = IMP
jmp	*%r11			// goto *imp

END_ENTRY __objc_msgSend_uncached
Copy the code

So this is going to call MethodTableLookup.

5.1 MethodTableLookup

.macro MethodTableLookup
	// Create a stack that stores the values in registers in preparation for calling the method
	SAVE_REGS MSGSEND

	// lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER)
	// receiver and selector already in x0 and x1
	mov	x2, x16
	mov	x3, #3

	bl	_lookUpImpOrForward

	// IMP in x0
	mov	x17, x0

    // Restore the registers used by SAVE_REGS to the stack
	RESTORE_REGS MSGSEND

.endmacro
Copy the code

5.2 Slow Search for _lookUpImpOrForward

parameter instructions
id inst receiver(x0)
SEL sel selector(x1)
Class cls ClassISA
int behavior method lookup

behavior: LOOKUP_INITIALIZE | LOOKUP_RESOLVER)

The flow chart

The source code parsing

IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
    const IMP forward_imp = (IMP)_objc_msgForward_impcache;
    IMP imp = nil;
    Class curClass;

    runtimeLock.assertUnlocked(a);/** The first message to a Class is usually through the objc_opt_* or various optimized entries: + new + alloc + self but the Class has not yet been initialized */
    if (slowpath(! cls->isInitialized())) { 
        behavior |= LOOKUP_NOCACHE;
     }

    /** Lock to ensure atomicity of method-lookup + cache-fill. Otherwise, the classification may be permanently ignored after being added because the cache is overwritten with old data */
    runtimeLock.lock(a);/** To prevent CFI attacks check whether the Class is registered and make sure the Class is compiled into binary files or use objc_duplicateClass, Objc_initializeClassPair or objc_allocateClassPair Legitimate registered */
    checkIsKnownClass(cls);

    // Initialize class if necessary
    cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE);

    // runtimeLock may have been dropped but is now locked again
    runtimeLock.assertLocked(a); curClass = cls;/** Loop. Jump */ with goto and break
    for (unsigned attempts = unreasonableClassCount();;) {
        if (curClass->cache.isConstantOptimizedCache(/* strict */true)) {... }else {
            // curClass method list.
            // Find sel using dichotomy
            Method meth = getMethodNoSuper_nolock(curClass, sel);
            if (meth) {
                imp = meth->imp(false);
                goto done;
            }

            // Here we get the parent class and assign it to curClass
            if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
                // If superClass == nil is not found, the message is forwarded
                // No implementation found, and method resolver didn't help.
                // Use forwarding.
                imp = forward_imp;
                break; }}...// Superclass cache. CurClass is already the Superclass
        imp = cache_getImp(curClass, sel);
        if (slowpath(imp == forward_imp)) {
            // Message forwarding is required
            break;
        }
        if (fastpath(imp)) {
            // Found the method in a superclass. Cache it in this class.
            gotodone; }}... done:if (fastpath((behavior & LOOKUP_NOCACHE) == 0)) {
#if CONFIG_USE_PREOPT_CACHES
    / / iOS real machine
        while (cls->cache.isConstantOptimizedCache(/* strict */true)) {
            cls = cls->cache.preoptFallbackClass(a); }#endif
        // Insert cache
        log_and_fill_cache(cls, imp, sel, inst, curClass); }... }Copy the code

5.3 Binary search appreciation

It’s a pretty neat algorithm, and it’s worth learning.

/*********************************************************************** * search_method_list_inline * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
template<class getNameFunc>
ALWAYS_INLINE static method_t *
findMethodInSortedMethodList(SEL key, const method_list_t *list, const getNameFunc &getName)
{
    ASSERT(list);

    auto first = list->begin(a);auto base = first;
    decltype(first) probe;

    uintptr_t keyValue = (uintptr_t)key;
    uint32_t count;
    
    /** count >>= 1 */
    for(count = list->count; count ! =0; count >>= 1) {
        // Middle subscript
        probe = base + (count >> 1);
        
        uintptr_t probeValue = (uintptr_t)getName(probe);
        
        if (keyValue == probeValue) {
            // `probe` is a match.
            // Rewind looking for the *first* occurrence of this value.
            // This is required for correct category overrides.
            /** probe > < span style = "box-sizing: border-box; color: RGB (74, 74, 74); line-height: 22px; font-size: 14px; white-space: inherit;" It will fall out of the categories. * /
            while (probe > first && keyValue == (uintptr_t)getName((probe - 1))) {
                probe--;
            }
            return &*probe;
        }
        
        if (keyValue > probeValue) {
            base = probe + 1; count--; }}return nil;
}
Copy the code

🎉 Thanks for your patience. Good for you

  • It’s a real effort to look at the assembly

Attached: Knowledge added

1. Supplementary instructions for buckets process

// Determine whether bit 0 of cache_t(p11) is not 0, and if it is 0, whether there is a shared cache (first_shared_cache_sel)
// No cache: keep going
// With cache: jump to LLookupPreopt\Function
tbnz	p11, #0, LLookupPreopt\Function
// cache_t(p11) & #0x0000ffffffffffff -> p10(buckets)
// Take the lower 48 bits
and	p10, p11, #0x0000ffffffffffff	// p10 = buckets
Copy the code
  • The 0th bit here is the low zero bit

2. OBJC2

We often see the __OBJC2__ macro definition in source code learning.

  • There are two versions of Runtime

    • 1.0: Used on earlier systems, 32-bit MacOS
    • 2.0 (current version) :iOSandMacOS X 10.5Later 64-bit systems
  • To learn more, check out the official documentation below:

    • Objective-C Runtime Programming Guide
    • Objective-C Runtime

3 cache_t

Class: cache_t

Class Realize and Initialize

【Lawliet play iOS source 】 class implementation and initialization