从C的伪代码到汇编-动手实现objc-msgSend.doc
《从C的伪代码到汇编-动手实现objc-msgSend.doc》由会员分享,可在线阅读,更多相关《从C的伪代码到汇编-动手实现objc-msgSend.doc(10页珍藏版)》请在淘文阁 - 分享文档赚钱的网站上搜索。
1、.1/10从从 C C 的伪代码到汇编的伪代码到汇编,动手实现动手实现 objc_msgSendobjc_msgSendobjc_msgSend 函数支撑了我们使用 Objective-C 实现的一切。Gwynne Raskind,Friday Q&A 的读者,建议我谈谈 objc_msgSend 的部实现。要理解某件事还有比自己动手实现一次更好的方法吗?咱们来自己动手实现一个 objc_msgSend。Tramapoline!Tramapoline!Trampopoline!Trampopoline!(蹦床蹦床)当你写了一个发送 Objective-C 消息的方法:1.obj message
2、编译器会生成一个 objc_msgSend 调用:1.objc_msgSend(obj,selector(message);之后之后 objc_msgSendobjc_msgSend 会负责转发这个消息。会负责转发这个消息。它都做了什么?它会查找适宜的函数指针或者 IMP,然后调用,最后跳转。任何传给objc_msgSend 的参数,最终都会成为 IMP 的参数。IMP 的返回值成为了最开始被调用的方法的返回值。因为 objcmsgSend 只是负责接收参数,找到适宜的函数指针,然后跳转,有时管这种叫做 trampoline(译注:蹦床(s:/en.wikipedia.org/wiki/Tra
3、mpoline(computing)).更通用的来说,任何一段负责把一段代码转发到另一处的代码,都可以被叫做 trampoline。这种转发的行为使 objc_msgSend 变得特殊起来。因为它只是简单的查找适宜的代码,然后直接跳转过去,这相当的通用。传入任何参数组合都可以,因为它只是把这些参数留给IMP 去读取。返回值有些棘手,但最终都可以看成 objc_msgSend 的不同变种。不幸的是,这些转发行为都不能用纯 C 实现。因为没有方法可以将传入 C 函数的泛参(generic parameters)传给另一个函数。你可以使用变参,但是变参和普通参数的传递方法不同,而且慢,所以这不适合普
4、通的 C 参数。如果要用 C 来实现 objc_msgSend,基本样子应该像这样:1.id objc_msgSend(id self,SEL _cmd,.)2.3.Class c=object_getClass(self);4.IMP imp=class_getMethodImplementation(c,_cmd);.2/105.returnreturn imp(self,_cmd,.);6.这有点过于简单。事实上会有一个方法缓存来提升查找速度,像这样:1.id objc_msgSend(id self,SEL _cmd,.)2.3.Class c=object_getClass(self
5、);4.IMP imp=cache_lookup(c,_cmd);5.if(!imp)6.imp=class_getMethodImplementation(c,_cmd);7.return imp(self,_cmd,.);8.通常为了速度,cache_lookup 使用 inline 函数实现。汇编汇编在 Apple 版的 runtime 中,为了最大化速度,整个函数是使用汇编实现的。在Objective-C 中每次发送消息都会调用 objc_msgSend,在一个应用中最简单的动作都会有成千或者上百万的消息。为了让事情更简单,我自己的实现中会尽可能少的使用汇编,使用独立的 C 函数抽象复
6、杂度。汇编代码会实现下面的功能:1.id objc_msgSend(id self,SEL _cmd,.)2.3.IMP imp=GetImplementation(self,_cmd);4.imp(self,_cmd,.);5.6.7.GetImplementation 可以用更可读的方式工作。汇编代码需要:汇编代码需要:1.把所有潜在的参数存储在安全的地方,确保 GetImplementation 不会覆盖它们。2.调用 GetImplementation。3.把返回值保存在某处。4.恢复所有的参数值。5.跳转到 GetImplementation 返回的 IMP。.3/10让我们开始吧!
7、这里我会尝试使用 x86-64 汇编,这样可以很方便的在 Mac 上工作。这些概念也可以应用于 i386 或者 ARM。这个函数会保存在独立的文件中,叫做 msgsend-asm.s。这个文件可以像源文件那样传递给编译器,然后会被编译并到程序中。第一件事要做的是声明全局的符号(global symbol)。因为一些无聊的历史原因,C 函数的 global symbol 会在名字前有个下划线:1.globl _objc_msgSend2._objc_msgSend:编译器会很高兴的最近可使用的(nearest available)objc_msgSend。简单的这个到一个测试 app 已经可以让
8、 obj message 表达式使用我们自己的代码而不是苹果的runtime,这样可以相当方便的测试我们的代码确保它可以工作。整型数和指针参数会被传入寄存器%rsi,%rdi,%rdx,%rcx,%r8 和%r9。其他类型的参数会被传进栈(stack)中。这个函数最先做的事情是把这六个寄存器中的值保存在栈中,这样它们可以在之后被恢复:1.pushq%rsi2.pushq%rdi3.pushq%rdx4.pushq%rcx5.pushq%r86.pushq%r9除了这些寄存器,寄存器%rax 扮演了一个隐藏的参数。它用于变参的调用,并保存传入的向量寄存器(vector registers)的数量
9、,用于被调用的函数可以正确的准备变参列表。以防目标函数是个变参的方法,我同样也保存了这个寄存器中的值:1.pushq%rax为了完整性,用于传入浮点类型参数的寄存器%xmm 也应该被保存。但是,要是我能确保 GetImplementation 不会传入任何的浮点数,我就可以忽略掉它们,这样我就可以让代码更简洁。.4/10接着,对齐栈。Mac OS X 要求一个函数调用栈需要对齐 16 字节边界。上面的代码已经是栈对齐的,但是还是需要显式手动处理下,这样可以确保所有都是对齐的,就不用担心动态调用函数时会崩溃。要对齐栈,在保存%r12 的原始值到栈中后,我把当前的栈指针保存到了%r12 中。%r1
10、2 是随便选的,任何保存的调用者寄存器(caller-saved register)都可以。重要的是在调用完 GetImplementation 后这些值仍然存在。然后我把栈指针按位与(and)上-0 x10,这样可以清除栈底的四位:1.pushq%r122.mov%rsp,%r123.andq$-0 x10,%rsp现在栈指针是对齐的了。这样可以安全的避开上面(above)保存的寄存器,因为栈是向下增长的,这种对齐的方法会让它更向下(move it further down)。是时候该调用 GetImplementation 了。它接收两个参数,self 和 _cmd。调用习惯是把这两个参数
11、分别保存到%rsi 和%rdi 中。然而传入 objc_msgSend 中时就是那样了,它们没有被移动过,所以不需要改变它们。所有要做的事情实际上是调用 GetImplementation,方法名前面也要有一个下划线:1.callq _GetImplementation整型数和指针类型的返回值保存在%rax 中,这就是找到返回的 IMP 的地方。因为%rax 需要被恢复到初始的状态,返回的 IMP 需要被移动到别的地方。我随便选了个%r11。1.mov%rax,%r11现在是时候该恢复原样了。首先要恢复之前保存在%r12 中的栈指针,然后恢复旧的%r12 的值:1.mov%r12,%rsp2.
12、popq%r12然后按压入栈的相反顺序恢复寄存器的值:1.popq%rax2.popq%r93.popq%r84.popq%rcx5.popq%rdx6.popq%rdi.5/107.popq%rsi现在一切都已经准备好了。参数寄存器(argument registers)都恢复到了之前的样子。目标函数需要的参数都在适宜的位置了。IMP 在寄存器%r11 中,现在要做的是跳转到那里:1.jmp*%r11就这样!不需要其他的汇编代码了。jump 把控制权交给了方法实现。从代码的角度看,就好像发送消息者直接调用的这个方法。之前的那些迂回的调用方法都消失了。当方法返回,它会直接放回到 objc_ms
13、gSend 的调用处,不需要其他的操作。这个方法的返回值可以在适宜的地方找到。非常规的返回值有一些细节需要注意。比如大的结构体(不能用一个寄存器大小保存的返回值)。在 x86-64,大的结构体使用隐藏的第一个参数返回。当你像这样调用:1.NSRect r=SomeFunc(a,b,c);这个调用会被翻译成这样:1.NSRect r;2.SomeFunc(&r,a,b,c);用于返回值的存地址被传入到%rdi 中。因为 objc_msgSend 期望%rdi 和%rsi 中包含 self 和 _cmd,当一个消息返回大的结构体时不会起作用的。同样的问题存在于多个不同平台上。runtime 提供了
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 代码 汇编 动手 实现 objc msgSend
限制150内