在分析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. 【教程】基于Ubuntu系统的PyTorch虚拟环境配置

    目录 一.PyTorch虚拟环境配置 二.PyTorch虚拟环境使用 三.常用命令 Editor: Veagau Time: 2019/10/17 一.PyTorch虚拟环境配置 该部分操作均在终端( ...

  2. 详解 Redis 内存管理机制和实现

    Redis是一个基于内存的键值数据库,其内存管理是非常重要的.本文内存管理的内容包括:过期键的懒性删除和过期删除以及内存溢出控制策略. 最大内存限制 Redis使用 maxmemory 参数限制最大可 ...

  3. Python之路(第四十篇)进程池

    一.进程池 进程池也是通过事先划分一块系统资源区域,这组资源区域在服务器启动时就已经创建和初始化,用户如果想创建新的进程,可以直接取得资源,从而避免了动态分配资源(这是很耗时的). 线程池内子进程的数 ...

  4. CasperJS 前端功能测试

    CasperJS 是一个开源的导航脚本和测试组件.它提供实用的高级函数.方法和语法糖,可完成以下任务: 对浏览导航步骤的定义和排序 填写和提交表单 点击和跟踪链接 获取页面快照(或者页面中的某部分) ...

  5. linux下shell脚本启动jar包

    本文采用的jar包是通过idea下maven打包的springboot项目. 写这个shell脚本是为了在linux下方便启动jar包时不用输入太多的shell命令,将启动脚本的一系列shell命令整 ...

  6. fenby C语言 P26

    指针 地址 类型+“*”+变量名=指针变量 int *p1;//指向整型变量的指针变量p1 float *p2;//指向浮点型变量的指针变量p2 char *p3;//指向字符型变量的指针变量p3 “ ...

  7. Spring使用@Async注解

    本文讲述@Async注解,在Spring体系中的应用.本文仅说明@Async注解的应用规则,对于原理,调用逻辑,源码分析,暂不介绍.对于异步方法调用,从Spring3开始提供了@Async注解,该注解 ...

  8. 2018.8.6 Python中的文件操作

    前言: 使用python来读写文件是非常简单的操作,我们使用open()函数来打开一个文件,获取到文件句柄.然后通过文件句柄就可以进行各种操作了,根据打开方式的不同能够执行的操作也会有相应的差异. 打 ...

  9. ASP.NET Core如何使用压缩中间件提高Web应用程序性能

    前言 压缩可以大大的降低我们Web服务器的响应速度,压缩从而提高我们网页的加载速度,以及节省一定的带宽. 何时使用相应压缩中间件 在IIS,Apache,Nginx中使用基于服务端的响应压缩技术.中间 ...

  10. MySQL、Oracle、SqlServer的区别

    鉴于和数据库打交道日益频繁,遂决定写一篇关于Oracle.SqlServer.MySQL区别的个人观点. MySQL是大学时的主要学习对象,但刚参加工作时转到了SqlServer,现在主要接触的是Or ...