在分析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 <+>: testq %rdi, %rdi
0x107b68803 <+>: jle 0x107b68850 ; <+80> // except the bad pointer to obj
0x107b68805 <+>: movq (%rdi), %r11
0x107b68808 <+>: movq %rsi, %r10
0x107b6880b <+>: andl 0x18(%r11), %r10d ; // (int32)(%rsi) &= (int32)0x18(%rdi) , (%rsi) <<= 4, (int64)(%rsi) += (int64)0x10(%rdi).
0x107b6880f <+>: shlq $0x4, %r10
0x107b68813 <+>: addq 0x10(%r11), %r10
0x107b68817 <+>: cmpq (%r10), %rsi
0x107b6881a <+>: jne 0x107b68820 ; <+32>
0x107b6881c <+>: jmpq *0x8(%r10) ; // jmp to imp
0x107b68820 <+>: cmpq $0x1, (%r10)
0x107b68824 <+>: jbe 0x107b68833 ; <+51>
0x107b68826 <+>: addq $0x10, %r10
0x107b6882a <+>: cmpq (%r10), %rsi
0x107b6882d <+>: jne 0x107b68820 ; <+32>
0x107b6882f <+>: jmpq *0x8(%r10)
0x107b68833 <+>: jb 0x107b68871 ; <+113>
0x107b68835 <+>: movq 0x8(%r10), %r10
0x107b68839 <+>: jmp 0x107b68845 ; <+69>
0x107b6883b <+>: cmpq $0x1, (%r10)
0x107b6883f <+>: jbe 0x107b6884e ; <+78>
0x107b68841 <+>: addq $0x10, %r10
0x107b68845 <+>: cmpq (%r10), %rsi
0x107b68848 <+>: jne 0x107b6883b ; <+59>
0x107b6884a <+>: jmpq *0x8(%r10)
0x107b6884e <+>: jmp 0x107b68871 ; <+113>
0x107b68850 <+>: je 0x107b68866 ; <+102> // a neg pointer is a objc debug tagged pointer classes.
0x107b68852 <+>: leaq 0x348df7(%rip), %r11 ; objc_debug_taggedpointer_classes
0x107b68859 <+>: movq %rdi, %r10
0x107b6885c <+>: shrq $0x3c, %r10
0x107b68860 <+>: movq (%r11,%r10,), %r11
0x107b68864 <+>: jmp 0x107b68808 ; <+8> // jump back and deal with this debug obj.
0x107b68866 <+>: xorl %eax, %eax ; // deal with a nil obj.
0x107b68868 <+>: xorl %edx, %edx
0x107b6886a <+>: xorps %xmm0, %xmm0
0x107b6886d <+>: xorps %xmm1, %xmm1
0x107b68870 <+>: retq ; // bad return clause.
0x107b68871 <+>: pushq %rbp
0x107b68872 <+>: movq %rsp, %rbp
0x107b68875 <+>: subq $0x88, %rsp ; // and total push size 0x38, %rsp is 0xc0 bytes far away from %rbp when next call in soon
0x107b6887c <+>: movdqa %xmm0, -0x80(%rbp)
0x107b68881 <+>: pushq %rax
0x107b68882 <+>: movdqa %xmm1, -0x70(%rbp)
0x107b68887 <+>: pushq %rdi
0x107b68888 <+>: movdqa %xmm2, -0x60(%rbp)
0x107b6888d <+>: pushq %rsi
0x107b6888e <+>: movdqa %xmm3, -0x50(%rbp)
0x107b68893 <+>: pushq %rdx
0x107b68894 <+>: movdqa %xmm4, -0x40(%rbp)
0x107b68899 <+>: pushq %rcx
0x107b6889a <+>: movdqa %xmm5, -0x30(%rbp)
0x107b6889f <+>: pushq %r8
0x107b688a1 <+>: movdqa %xmm6, -0x20(%rbp)
0x107b688a6 <+>: pushq %r9
0x107b688a8 <+>: movdqa %xmm7, -0x10(%rbp)
0x107b688ad <+>: movq %rdi, %rdi
0x107b688b0 <+>: movq %rsi, %rsi
0x107b688b3 <+>: movq %r11, %rdx ; // isa member of obj of %rdi
0x107b688b6 <+>: callq 0x107b59c57 ; _class_lookupMethodAndLoadCache3
0x107b688bb <+>: movq %rax, %r11
0x107b688be <+>: movdqa -0x80(%rbp), %xmm0
0x107b688c3 <+>: popq %r9
0x107b688c5 <+>: movdqa -0x70(%rbp), %xmm1
0x107b688ca <+>: popq %r8
0x107b688cc <+>: movdqa -0x60(%rbp), %xmm2
0x107b688d1 <+>: popq %rcx
0x107b688d2 <+>: movdqa -0x50(%rbp), %xmm3
0x107b688d7 <+>: popq %rdx
0x107b688d8 <+>: movdqa -0x40(%rbp), %xmm4
0x107b688dd <+>: popq %rsi
0x107b688de <+>: movdqa -0x30(%rbp), %xmm5
0x107b688e3 <+>: popq %rdi
0x107b688e4 <+>: movdqa -0x20(%rbp), %xmm6
0x107b688e9 <+>: popq %rax
0x107b688ea <+>: movdqa -0x10(%rbp), %xmm7
0x107b688ef <+>: leave
0x107b688f0 <+>: cmpq %r11, %r11
0x107b688f3 <+>: jmpq *%r11 ; // the real imp address related to set
0x107b688f6 <+>: 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
.globl _objc_debug_taggedpointer_classes
_objc_debug_taggedpointer_classes:
.fill , , 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
.globl _objc_debug_taggedpointer_classes
_objc_debug_taggedpointer_classes:
.fill , , // ENTRY _objc_msgSend
.text
.globl _objc_msgSend
.align , 0x90
_objc_msgSend:
.cfi_startproc // MESSENGER_START
:
.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 (%r11), %r10d // r10 = _cmd & class->cache.mask
shlq $$, %r10 // r10 = offset = (_cmd & mask)<<4
addq (%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 $ // call or return imp :
// loop
cmpq $$, (%r10)
jbe 3f // if (bucket->sel <= 1) wrap or miss addq $$, %r10 // bucket++
:
cmpq (%r10), %a2 // if (bucket->sel != _cmd)
jne 1b // scan more
// CacheHit must always be preceded by a not-taken `jne` instruction
CacheHit $ // call or return imp :
// wrap or miss
jb LCacheMiss_f // if (bucket->sel < 1) cache miss
// wrap
movq (%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. :
// loop
cmpq $$, (%r10)
jbe 3f // if (bucket->sel <= 1) wrap or miss addq $$, %r10 // bucket++
:
cmpq (%r10), %a2 // if (bucket->sel != _cmd)
jne 1b // scan more
// CacheHit must always be preceded by a not-taken `jne` instruction
CacheHit $ // call or return imp :
// double wrap or miss
jmp LCacheMiss_f .align
LNilOrTagged:
jz LNil_f // flags set by NilOrTaggedTest // tagged leaq _objc_debug_taggedpointer_classes(%rip), %r11
movq %a1, %r10
shrq $$, %r10
movq (%r11, %r10, ), %r11 // read isa from table
jmp LGetIsaDone_b LNil:
// nil
xorl %eax, %eax
xorl %edx, %edx
xorps %xmm0, %xmm0
xorps %xmm1, %xmm1 :
.section __DATA,__objc_msg_break
.quad 4b
.quad NIL_EXIT
.text
ret // cache miss: go search the method lists
LCacheMiss:
// isa still in r11 :
.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函数枢纽objc_msgSend的更多相关文章

  1. objc反汇编分析,block函数块为何物?

    上一篇向大家介绍了__block变量的反汇编和它的伪代码,本篇函数块block,通常定义成原型(^){},它在反汇编中是什么东西. 我们先定义将要反汇编的例子,为减少篇幅例子采用non-arc环境. ...

  2. arm汇编进入C函数分析,C函数压栈,出栈,传参,返回值

    环境及代码介绍 环境和源码 由于有时候要透彻的理解C里面的一些细节问题,所有有必要看看汇编,首先这一切的开始就是从汇编代码进入C的main函数过程.这里不使用编译器自动生成的这部分汇编代码,因为编译器 ...

  3. Linux下简单C语言小程序的反汇编分析

    韩洋原创作品转载请注明出处<Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 写在开始,本文为因为参加MOO ...

  4. 可以在函数中间打点了,以分析bpf_prog_load函数为例

    可以在函数中间打点了, sudo stap -L 'process("./test").statement("func@test.c:10")' //12.10 ...

  5. 逆向分析objc,所有类的信息都能在动态调试中获取。

    因为objc是动态绑定的,程序运行时必须知道如何绑定,依靠的就是类描述.只要知道类描述是如何组织的就可以获取一切有用的信息.不知道是幸运还是不幸,这些信息全部都在运行的程序中.即使没有IDA这样的工具 ...

  6. 性能测试分享: Jmeter的源码分析main函数参数

    性能测试分享: Jmeter的源码分析main函数参数   poptest是国内唯一一家培养测试开发工程师的培训机构,以学员能胜任自动化测试,性能测试,测试工具开发等工作为目标.如果对课程感兴趣,请大 ...

  7. 《linux内核分析》第六周:分析fork函数对应的系统调用处理过程

    一. 阅读理解task_struct数据结构http://codelab.shiyanlou.com/xref/linux-3.18.6/include/linux/sched.h#1235: 进程是 ...

  8. jQuery源码分析-each函数

    本文部分截取自且行且思 jQuery.each方法用于遍历一个数组或对象,并对当前遍历的元素进行处理,在jQuery使用的频率非常大,下面就这个函数做了详细讲解: 复制代码代码 /*! * jQuer ...

  9. LiteOS-任务篇-源码分析-任务调度函数

    目录 前言 笔录草稿 核心源码分析 osTaskSchedule函数源码分析 osPendSV函数源码分析 TaskSwitch函数源码分析 调度上层源码分析 osSchedule函数源码分析 LOS ...

随机推荐

  1. Vue 上传材料并传给后端接口(使用input)

    最近工作中接到一个需求,需要上传一个文件材料,提交时传给后端.使用的框架是Vue,废话不说直接上代码 <template> <div> <input type=" ...

  2. httprunner-1-linux下搭建hrun(上)

    前言 相信不少小伙伴对开源项目 httprunner 都很感兴趣,我们来看下它的有哪些特点吧: 项目管理:新增项目.列表展示及相关操作,支持用例批量上传(标准化的HttpRunner json和yam ...

  3. N042第一周

    1.按系列罗列Linux的发行版,并描述不同发行版之间的联系与区别. slackware:SUSE Linux Enterprise Server,OpenSuse debian:ubuntu,dee ...

  4. Java基础(二十三)集合(6)Map集合

    Map接口作为Java集合框架中的第二类接口,其子接口为SortedMap接口,SortedMap接口的子接口为NavigableMap接口. 实现了Map接口具体类有:HashMap(子类Linke ...

  5. 设计模式(十五)Facade模式

    Facade模式可以为相互关联在一起的错综复杂的类整理出高层接口,可以让系统对外只有一个简单的接口,而且还会考虑到系统内部各个类之间的责任关系和依赖关系,按照正常的顺序调用各个类. 还是先看一下示例程 ...

  6. node-批量上传文件

    很多项目可能都会涉及到的业务是关于多文件上传的,那么需要使用到redis或者第三方库(使用redis)来实现任务队列,甚至需要控制并发量和分包(一次请求传多个文件),这样每次都会以实现功能来完成,但是 ...

  7. ios发送短信验证码计时器的swift实现

    转载自:http://www.jianshu.com/p/024dd2d6e6e6# Update: Xcode 8.2.1 Swift 3 先介绍一下 属性观测器(Property Observer ...

  8. javadoc的使用

    在进行项目开发过程中,项目接口文档是很重要的一块内容,在java项目中我们可以用swagger,asciidoc,javadoc等方式来生产文档,而其中最基本的文档生成方式就是javadoc,它一般用 ...

  9. vue---Excel表格导出

    一.安装依赖 npm install file-saver --save npm install xlsx --save npm install script-loader --save-dev 二. ...

  10. 解决移动端touch事件与click冲突的问题

    最简单的办法,就只绑定一个事件不就行了: 第二种,我觉得和第一种也没啥区别.. const Button = document.getElementById("targetButton&qu ...