• 反汇编分析objc函数枢纽objc_msgSend


    在分析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。

  • 相关阅读:
    LeetCode 109 Convert Sorted List to Binary Search Tree
    LeetCode 108 Convert Sorted Array to Binary Search Tree
    LeetCode 107. Binary Tree Level Order Traversal II
    LeetCode 106. Construct Binary Tree from Inorder and Postorder Traversal
    LeetCode 105. Construct Binary Tree from Preorder and Inorder Traversal
    LeetCode 103 Binary Tree Zigzag Level Order Traversal
    LeetCode 102. Binary Tree Level Order Traversal
    LeetCode 104. Maximum Depth of Binary Tree
    接口和多态性
    C# 编码规范
  • 原文地址:https://www.cnblogs.com/bbqzsl/p/5110098.html
Copyright © 2020-2023  润新知