在分析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. Twitter-Snowflake:自增ID算法

    简介 Twitter 早期用 MySQL 存储数据,随着用户的增长,单一的 MySQL 实例没法承受海量的数据,后来团队就研究如何产生完美的自增ID,以满足两个基本的要求: 每秒能生成几十万条 ID ...

  2. Lambda函数及其用法

    Lambda函数又称匿名函数,匿名函数就是没有名字的函数,函数没有名字也行? 当然可以啦.有些函数如果只是临时一用,而且它的业务逻辑也很简单时,就没必要非给它取个名字不可. 先来看个简单lambda函 ...

  3. (day33)数据库

    目录 1. 数据库是什么 2. 为什么使用数据库 3. 数据库的分类 1. 关系型数据库 2. 非关系型数据库 4. mysql的架构 5. mysql的安装 1. windows的安装 2. win ...

  4. Tkinter 之Entry输入框标签

    一.参数说明 语法 作用 Entry(root,width=20) 组件的宽度(所占字符个数) Entry(root,fg='blue') 前景字体颜色 Entry(root,bg='blue') 背 ...

  5. F#周报2019年第44期

    新闻 Elmish.WPF教程 介绍Orleans 3.0 GC配置历史 介绍ONNX运行时1.0 介绍微软Q&A(预览) 使用App中心持续布署与监控你的UWP,WPF与Windows Fo ...

  6. SQLite AUTOINCREMENT

    只有integer字段可以设置autoincrement.int不行.integer是无符号整型.

  7. 2018.8.2 python中is和==的区别

    一.is 和==的区别 1.is 比较的是左右两边的内存地址, ==比较的是左右两边的值. 2.id() 通过id()可以查看一个变量表示的值得内存中的地址. s = 'alex' s1 = 'ale ...

  8. 【已解决】ArcGIS Engine无法创建拓扑的问题(CreateTopology)

    也许,你的问题是这样的 ①System.Runtime.InteropServices.COMException:"未找到拓扑." ②myTopology结果是null,程序跳转到 ...

  9. C++:类中创建线程

    #include <iostream> #include <stdio.h> #include <stdlib.h> #include <iostream&g ...

  10. Kong04- Kong 四大参考说明

    Kong 四大参考说明 Kong 的官方有很多详细的参考说明,比如配置文件.命令行.Admin API.代理.负载均衡,接下来我们简单看一下,都提供什么内容. 本文主要基于 Kong 1.3 版本进行 ...