在剖析objc_msgSend之前,先來搞清楚另一個問題。
函數是什麼?能夠會答 void foo(void) {} 像這樣就是一個函數。或許函數包括函數原型和函數定義,是一段執行某樣功用的機器代碼。
調用函數時必需要預備兩個要素,函數原型和函數入口地址。
函數原型的作用是什麼?答聲明了函數調用的方式。不夠詳細。函數原型是函數調用方和函數定義之間的關於參數傳遞和後果前往的協議商定。這個協議辨別作用在函數入口兩邊的代碼,一邊是調用方在調用途協議的構建,另一邊是函數定義對協議的訪問解釋。傳統地就是調用棧。原型是一個協議,協議是可以傳遞的。所以在函數被調用途,到函數的入口地址之間,是可以做任何處置,只需協議不被毀壞。
而objc在函數調用和函數入口之間參加了靜態綁定的處置,這個處置就是msgSend。
大家都知道這個原型id(*IMP)(id, char*, …),而這個卻只是用於傳遞的協議,並非函數的真正的原型。關於前面的省略號,傳統地是va_list訪問,但是實踐上省略號即第三個參數開端可以是任何其實傳參方式。至於從第三個參數開端之後的協議是怎樣商定的,在objc函數調用途和函數定義是必需明白清楚的。但是在之兩者之間的中繼路由進程中,只需求知道前兩個參數的商定,就是這個原型id()(id, char*,…),所以msgSend也是這個原型。
舉例-(id)foo:(int)i;
id foo(id, char*, int) —> id msgSend(id, char*, …) —> (id()(id, char*, …))foo
作為objc中函數調用的樞紐,我們如今就來看一下它的反匯編樣貌:
libobjc.A.dylib`objc_msgSend: -> 0x107b68800 <+0>: testq %rdi, %rdi 0x107b68803 <+3>: jle 0x107b68850 ; <+80> // except the bad pointer to obj 0x107b68805 <+5>: movq (%rdi), %r11 0x107b68808 <+8>: movq %rsi, %r10 0x107b6880b <+11>: andl 0x18(%r11), %r10d ; // (int32)(%rsi) &= (int32)0x18(%rdi) , (%rsi) <<= 4, (int64)(%rsi) += (int64)0x10(%rdi). 0x107b6880f <+15>: shlq $0x4, %r10 0x107b68813 <+19>: addq 0x10(%r11), %r10 0x107b68817 <+23>: cmpq (%r10), %rsi 0x107b6881a <+26>: jne 0x107b68820 ; <+32> 0x107b6881c <+28>: jmpq *0x8(%r10) ; // jmp to imp 0x107b68820 <+32>: cmpq $0x1, (%r10) 0x107b68824 <+36>: jbe 0x107b68833 ; <+51> 0x107b68826 <+38>: addq $0x10, %r10 0x107b6882a <+42>: cmpq (%r10), %rsi 0x107b6882d <+45>: jne 0x107b68820 ; <+32> 0x107b6882f <+47>: jmpq *0x8(%r10) 0x107b68833 <+51>: jb 0x107b68871 ; <+113> 0x107b68835 <+53>: movq 0x8(%r10), %r10 0x107b68839 <+57>: jmp 0x107b68845 ; <+69> 0x107b6883b <+59>: cmpq $0x1, (%r10) 0x107b6883f <+63>: jbe 0x107b6884e ; <+78> 0x107b68841 <+65>: addq $0x10, %r10 0x107b68845 <+69>: cmpq (%r10), %rsi 0x107b68848 <+72>: jne 0x107b6883b ; <+59> 0x107b6884a <+74>: jmpq *0x8(%r10) 0x107b6884e <+78>: jmp 0x107b68871 ; <+113> 0x107b68850 <+80>: je 0x107b68866 ; <+102> // a neg pointer is a objc debug tagged pointer classes. 0x107b68852 <+82>: leaq 0x348df7(%rip), %r11 ; objc_debug_taggedpointer_classes 0x107b68859 <+89>: movq %rdi, %r10 0x107b6885c <+92>: shrq $0x3c, %r10 0x107b68860 <+96>: movq (%r11,%r10,8), %r11 0x107b68864 <+100>: jmp 0x107b68808 ; <+8> // jump back and deal with this debug obj. 0x107b68866 <+102>: xorl %eax, %eax ; // deal with a nil obj. 0x107b68868 <+104>: xorl %edx, %edx 0x107b6886a <+106>: xorps %xmm0, %xmm0 0x107b6886d <+109>: xorps %xmm1, %xmm1 0x107b68870 <+112>: retq ; // bad return clause. 0x107b68871 <+113>: pushq %rbp 0x107b68872 <+114>: movq %rsp, %rbp 0x107b68875 <+117>: subq $0x88, %rsp ; // and total push size 0x38, %rsp is 0xc0 bytes far away from %rbp when next call in soon 0x107b6887c <+124>: movdqa %xmm0, -0x80(%rbp) 0x107b68881 <+129>: pushq %rax 0x107b68882 <+130>: movdqa %xmm1, -0x70(%rbp) 0x107b68887 <+135>: pushq %rdi 0x107b68888 <+136>: movdqa %xmm2, -0x60(%rbp) 0x107b6888d <+141>: pushq %rsi 0x107b6888e <+142>: movdqa %xmm3, -0x50(%rbp) 0x107b68893 <+147>: pushq %rdx 0x107b68894 <+148>: movdqa %xmm4, -0x40(%rbp) 0x107b68899 <+153>: pushq %rcx 0x107b6889a <+154>: movdqa %xmm5, -0x30(%rbp) 0x107b6889f <+159>: pushq %r8 0x107b688a1 <+161>: movdqa %xmm6, -0x20(%rbp) 0x107b688a6 <+166>: pushq %r9 0x107b688a8 <+168>: movdqa %xmm7, -0x10(%rbp) 0x107b688ad <+173>: movq %rdi, %rdi 0x107b688b0 <+176>: movq %rsi, %rsi 0x107b688b3 <+179>: movq %r11, %rdx ; // isa member of obj of %rdi 0x107b688b6 <+182>: callq 0x107b59c57 ; _class_lookupMethodAndLoadCache3 0x107b688bb <+187>: movq %rax, %r11 0x107b688be <+190>: movdqa -0x80(%rbp), %xmm0 0x107b688c3 <+195>: popq %r9 0x107b688c5 <+197>: movdqa -0x70(%rbp), %xmm1 0x107b688ca <+202>: popq %r8 0x107b688cc <+204>: movdqa -0x60(%rbp), %xmm2 0x107b688d1 <+209>: popq %rcx 0x107b688d2 <+210>: movdqa -0x50(%rbp), %xmm3 0x107b688d7 <+215>: popq %rdx 0x107b688d8 <+216>: movdqa -0x40(%rbp), %xmm4 0x107b688dd <+221>: popq %rsi 0x107b688de <+222>: movdqa -0x30(%rbp), %xmm5 0x107b688e3 <+227>: popq %rdi 0x107b688e4 <+228>: movdqa -0x20(%rbp), %xmm6 0x107b688e9 <+233>: popq %rax 0x107b688ea <+234>: movdqa -0x10(%rbp), %xmm7 0x107b688ef <+239>: leave 0x107b688f0 <+240>: cmpq %r11, %r11 0x107b688f3 <+243>: jmpq *%r11 ; // the real imp address related to set 0x107b688f6 <+246>: nopw %cs:(%rax,%rax)
代碼中分兩局部,第一局部是取出正確的receiver,請看我的反c偽代碼:
從代碼中可以看到,0指針被過濾直接前往,正數指針被轉換成正確的指針。正數指針?第一眼你能夠會和我一樣以為這是一個訪問到了內核空間的指針,由於在32位體系零碎中,普通地高2G地址是內核地址,最高位為1。但是在64位下並非就代表訪問到了內核地址,如今的x64處置器無效尋址不是64位無效尋址,而是48位,而且高16位必需與第48位分歧,其他的看作有效地址。這個正數地址,其實是一類被定義為tagged的指針,作用類似於erlang的原子量atom。
接上去是另一局部,找到正確的地址入口,然後跳過來,調用協議原封不動。請看我的反c偽代碼:
從代碼中可以看到SEL自始至終也只不過是一個調用稱號,SEL和IMP以key-value方式寄存在各種查找表中。不必多說,先從常用cache中查找,沒有就從類描繪中找出真實入口地址。在cache查找中有這麼3點邏輯,
1.不命中,而且無效地址,下一個key-value
2.不命中,並且有效地址,中止在cache的查找
3.不命中,並且為1,必需還是初次遇到1,然後cache forward,持續在cache中查找。
最後是我手工對msgSend原代碼中各處調用宏後的代碼
/******************************************************************** * * id objc_msgSend(id self, SEL _cmd,...); * ********************************************************************/ .data .align 3 .globl _objc_debug_taggedpointer_classes _objc_debug_taggedpointer_classes: .fill 16, 8, 0 ENTRY _objc_msgSend MESSENGER_START GetIsaCheckNil NORMAL // r11 = self->isa, or return zero CacheLookup NORMAL // calls IMP on success GetIsaSupport NORMAL // cache miss: go search the method lists LCacheMiss: // isa still in r11 MethodTableLookup %a1, %a2 // r11 = IMP cmp %r11, %r11 // set eq (nonstret) for forwarding jmp *%r11 // goto *imp END_ENTRY _objc_msgSend /******************************************************************** * * id objc_msgSend(id self, SEL _cmd,...); Expand * ********************************************************************/ .data .align 3 .globl _objc_debug_taggedpointer_classes _objc_debug_taggedpointer_classes: .fill 16, 8, 0 // ENTRY _objc_msgSend .text .globl _objc_msgSend .align 6, 0x90 _objc_msgSend: .cfi_startproc // MESSENGER_START 4: .section __DATA,__objc_msg_break .quad 4b .quad ENTER .text testq %a1, %a1 jle LNilOrTagged_f // MSB tagged pointer looks negative movq (%a1), %r11 // r11 = isa LGetIsaDone: movq %a2, %r10 // r10 = _cmd andl 24(%r11), %r10d // r10 = _cmd & class->cache.mask shlq $$4, %r10 // r10 = offset = (_cmd & mask)<<4 addq 16(%r11), %r10 // r10 = class->cache.buckets + offset cmpq (%r10), %a2 // if (bucket->sel != _cmd) jne 1f // scan more // CacheHit must always be preceded by a not-taken `jne` instruction CacheHit $0 // call or return imp 1: // loop cmpq $$1, (%r10) jbe 3f // if (bucket->sel <= 1) wrap or miss addq $$16, %r10 // bucket++ 2: cmpq (%r10), %a2 // if (bucket->sel != _cmd) jne 1b // scan more // CacheHit must always be preceded by a not-taken `jne` instruction CacheHit $0 // call or return imp 3: // wrap or miss jb LCacheMiss_f // if (bucket->sel < 1) cache miss // wrap movq 8(%r10), %r10 // bucket->imp is really first bucket jmp 2f // Clone scanning loop to miss instead of hang when cache is corrupt. // The slow path may detect any corruption and halt later. 1: // loop cmpq $$1, (%r10) jbe 3f // if (bucket->sel <= 1) wrap or miss addq $$16, %r10 // bucket++ 2: cmpq (%r10), %a2 // if (bucket->sel != _cmd) jne 1b // scan more // CacheHit must always be preceded by a not-taken `jne` instruction CacheHit $0 // call or return imp 3: // double wrap or miss jmp LCacheMiss_f .align 3 LNilOrTagged: jz LNil_f // flags set by NilOrTaggedTest // tagged leaq _objc_debug_taggedpointer_classes(%rip), %r11 movq %a1, %r10 shrq $$60, %r10 movq (%r11, %r10, 8), %r11 // read isa from table jmp LGetIsaDone_b LNil: // nil xorl %eax, %eax xorl %edx, %edx xorps %xmm0, %xmm0 xorps %xmm1, %xmm1 4: .section __DATA,__objc_msg_break .quad 4b .quad NIL_EXIT .text ret // cache miss: go search the method lists LCacheMiss: // isa still in r11 4: .section __DATA,__objc_msg_break .quad 4b .quad SLOW_EXIT .text SaveRegisters // _class_lookupMethodAndLoadCache3(receiver, selector, class) movq %a1, %a1 movq %a2, %a2 movq %r11, %a3 call __class_lookupMethodAndLoadCache3 // IMP is now in %rax movq %rax, %r11 RestoreRegisters cmp %r11, %r11 // set eq (nonstret) for forwarding jmp *%r11 // goto *imp // END_ENTRY _objc_msgSend .cfi_endproc LExit_objc_msgSend:
【反匯編剖析objc函數樞紐objc_msgSend】的相關資料介紹到這裡,希望對您有所幫助! 提示:不會對讀者因本文所帶來的任何損失負責。如果您支持就請把本站添加至收藏夾哦!