反汇编分析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 <+>: 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的更多相关文章
- objc反汇编分析,block函数块为何物?
上一篇向大家介绍了__block变量的反汇编和它的伪代码,本篇函数块block,通常定义成原型(^){},它在反汇编中是什么东西. 我们先定义将要反汇编的例子,为减少篇幅例子采用non-arc环境. ...
- arm汇编进入C函数分析,C函数压栈,出栈,传参,返回值
环境及代码介绍 环境和源码 由于有时候要透彻的理解C里面的一些细节问题,所有有必要看看汇编,首先这一切的开始就是从汇编代码进入C的main函数过程.这里不使用编译器自动生成的这部分汇编代码,因为编译器 ...
- Linux下简单C语言小程序的反汇编分析
韩洋原创作品转载请注明出处<Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 写在开始,本文为因为参加MOO ...
- 可以在函数中间打点了,以分析bpf_prog_load函数为例
可以在函数中间打点了, sudo stap -L 'process("./test").statement("func@test.c:10")' //12.10 ...
- 逆向分析objc,所有类的信息都能在动态调试中获取。
因为objc是动态绑定的,程序运行时必须知道如何绑定,依靠的就是类描述.只要知道类描述是如何组织的就可以获取一切有用的信息.不知道是幸运还是不幸,这些信息全部都在运行的程序中.即使没有IDA这样的工具 ...
- 性能测试分享: Jmeter的源码分析main函数参数
性能测试分享: Jmeter的源码分析main函数参数 poptest是国内唯一一家培养测试开发工程师的培训机构,以学员能胜任自动化测试,性能测试,测试工具开发等工作为目标.如果对课程感兴趣,请大 ...
- 《linux内核分析》第六周:分析fork函数对应的系统调用处理过程
一. 阅读理解task_struct数据结构http://codelab.shiyanlou.com/xref/linux-3.18.6/include/linux/sched.h#1235: 进程是 ...
- jQuery源码分析-each函数
本文部分截取自且行且思 jQuery.each方法用于遍历一个数组或对象,并对当前遍历的元素进行处理,在jQuery使用的频率非常大,下面就这个函数做了详细讲解: 复制代码代码 /*! * jQuer ...
- LiteOS-任务篇-源码分析-任务调度函数
目录 前言 笔录草稿 核心源码分析 osTaskSchedule函数源码分析 osPendSV函数源码分析 TaskSwitch函数源码分析 调度上层源码分析 osSchedule函数源码分析 LOS ...
随机推荐
- IIS6.0使用冒号上传漏洞利用
利用条件: 1.iis版本为6.0 2.上传文件名不会重命名 利用: 上传一个jpg木马图片 名字为:cs.asp:.jpg 注意是: 默认windows是不允许文件字含:(冒号)的 所以需要抓包后 ...
- xss姿势利用
1.定位页面可以出现xss的位置 可能会出现联合点利用 一个页面多个存储位置或者一个页面多个参数联合利用 例如输入xss 查看页面源码页面里有多个xss 或者多个参数显示 可以利用 需要注意的是有的是 ...
- hadoop2.x的安装
可以自己从官网编译打包也可以直接下载官网的.gz包.自己编译打包的过程如下: .查看是否安装cmake.svn.openssl.ncurses,没有的直接安装上 yum list|grep cmake ...
- 每日温度(LeetCode Medium难度算法题)题解
LeetCode 题号739中等难度 每日温度 题目描述: 根据每日 气温 列表,请重新生成一个列表,对应位置的输入是你需要再等待多久温度才会升高超过该日的天数.如果之后都不会升高,请在该位置用 0 ...
- Java面试题---基础篇
经常阅读一些牛人的基础博以及相关个人经历,你才发现自己真的不够努力,人生路漫漫,希望本人以及看客能走出一条自己不后悔的人生路,骚年 加油!!! 回归正题 一 java基础知识点 1)java面向对 ...
- ESP8266 打造一款物联网产品---搭建环境编译及烧录
一 前记 作为一个在wifi领域耕耘了多年的人,以前一直在外企和大公司做芯片,没有怎么使用过国内的芯片公司做出来的芯片.最近正好有一个项目需要用到一款低成本的wifi芯片,找来找去,发现乐鑫的最适合. ...
- vue的数据双向绑定
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...
- json基础用法
JSON格式 JSON格式(JavaScript Object Notation的缩写)是一种用于数据交换的文本格式,2001年由Douglas Crockford提出,目的是取代繁琐笨重的XML格式 ...
- 设计模式C++描述----19.命令(Command)模式
一. 举例说明 我们知道,在多线程程序中,多个用户都给系统发 Read 和 Write 命令.这里有几点需要说明: 1. 首先明确一点,所有的这些 Read 和 Write 命令都是调用一个库函数. ...
- 百度地图Javascript API 调用示例
调用示例 !<!DOCTYPE html> <html> <head> <title>百度地图DEMO</title> </head& ...