现在的位置: 首页 > 综合 > 正文

Objective-C 函数参数汇编分析

2013年07月12日 ⁄ 综合 ⁄ 共 3738字 ⁄ 字号 评论关闭

作者:ani_di 
版权所有,转载务必保留此链接 http://blog.csdn.net/ani_di

Objective-C 函数参数汇编分析

环境 Mac OS X 10.7.5,Xcode 4.3.2,64-bit,Debug,lldb

先看三个简单的方法

-(void)print 
{
    NSLog(@"0");
}

-(void)print:(NSString*)s1
{
    NSLog(@"1 %@", s1);
}

-(void)print:(NSString*)s1 s2:(NSString*)s2
{
    NSLog(@"2 %@ %@", s1, s2);
}

-(void)print:(NSString*)s1 s2:(NSString*)s2 s3:(NSString*)s3
{
    NSLog(@"3 %@ %@ %@", s1, s2, s3);
}

在函数入口点下断点,lldb输入dis -fm查看对应的反汇编

15      -(void)print 
16      {
0x10fbfc1a0:  pushq  %rbp
0x10fbfc1a1:  movq   %rsp, %rbp
0x10fbfc1a4:  subq   $16, %rsp
0x10fbfc1a8:  leaq   13681(%rip), %rax        ; 0x000000010fbff720 @"'0'"
0x10fbfc1af:  movq   %rdi, -8(%rbp)
0x10fbfc1b3:  movq   %rsi, -16(%rbp)
testm`-[BaseTest print] + 23 at BaseTest.m:17
16      {
-> 17       NSLog(@"0");
18      }
-> 0x10fbfc1b7:  movq   %rax, %rdi
0x10fbfc1ba:  movb   $0, %al
0x10fbfc1bc:  callq  0x000000010fbfc88e       ; NSLog
testm`-[BaseTest print] + 33 at BaseTest.m:18
17          NSLog(@"0");
18      }
19      
0x10fbfc1c1:  addq   $16, %rsp
0x10fbfc1c5:  popq   %rbp
0x10fbfc1c6:  ret

虽然没有参数,但是我们看到:最后出栈时,rsp还是增加了16字节,相当于两个指针参数。究其原因,调用Objective-C的方法其实是给对象发消息,最底层都是由objc_mesgSend完成。objc_msgSend大致是下面这样:

IMP class_getMethodImplementation(Class cls, SEL name);

id objc_msgSend(id receiver, SEL name, arguments...) {
    IMP function = class_getMethodImplementation(receiver->isa, name);
    return function(receiver, name, arguments); 
}

[self print]等于 objc_msgSend(self,
@selector(print:)); // ps:准确说,第一个参数不是self)

据实际观察,-16(%rbp)是SEL参数"print:",-8(%rbp)是self对象的地址。又由于它们分别从rdi和rsi得来。所以,objc_msgSend的这两个标准参数是存放在rdi和rsi寄存器中的。

下面看看1个参数的反汇编

20      -(void)print1:(NSString*)s1
21      {
0x10fbfc1d0:  pushq  %rbp
0x10fbfc1d1:  movq   %rsp, %rbp
0x10fbfc1d4:  subq   $32, %rsp
0x10fbfc1d8:  leaq   13665(%rip), %rax        ; 0x000000010fbff740 @"1 %@"
0x10fbfc1df:  movq   %rdi, -8(%rbp)
0x10fbfc1e3:  movq   %rsi, -16(%rbp)
0x10fbfc1e7:  movq   %rdx, -24(%rbp)
testm`-[BaseTest print1:] + 27 at BaseTest.m:22
21      {
-> 22       NSLog(@"1 %@", s1);
23      }
-> 0x10fbfc1eb:  movq   -24(%rbp), %rsi
0x10fbfc1ef:  movq   %rax, %rdi
0x10fbfc1f2:  movb   $0, %al
0x10fbfc1f4:  callq  0x000000010fbfc88e       ; NSLog
testm`-[BaseTest print1:] + 41 at BaseTest.m:23
22          NSLog(@"1 %@", s1);
23      }
24      
0x10fbfc1f9:  addq   $32, %rsp
0x10fbfc1fd:  popq   %rbp
0x10fbfc1fe:  ret    
0x10fbfc1ff:  nop

很显然,参数s1是位于rdx并保持在栈-24(%rbp)处。 但是这次出栈有32字节,相比上一次都了16字节。说明栈有4个局部变量,可是我们没看到-32(%rbp)在任何地方使用。先猜测是用于堆栈检查,因为windows经常这么干。

再看看2个参数的情况

33      -(void)print2:(NSString*)s1 s2:(NSString*)s2
34      {
0x10d12c1c0:  pushq  %rbp
0x10d12c1c1:  movq   %rsp, %rbp
0x10d12c1c4:  subq   $32, %rsp
0x10d12c1c8:  leaq   13665(%rip), %rax        ; 0x000000010d12f730 @"2 %@ %@"
0x10d12c1cf:  movq   %rdi, -8(%rbp)
0x10d12c1d3:  movq   %rsi, -16(%rbp)
0x10d12c1d7:  movq   %rdx, -24(%rbp)
0x10d12c1db:  movq   %rcx, -32(%rbp)
testm`-[BaseTest print2:s2:] + 31 at BaseTest.m:35
34      {
-> 35       NSLog(@"2 %@ %@", s1, s2);
36      }
-> 0x10d12c1df:  movq   -24(%rbp), %rsi
0x10d12c1e3:  movq   -32(%rbp), %rdx
0x10d12c1e7:  movq   %rax, %rdi
0x10d12c1ea:  movb   $0, %al
0x10d12c1ec:  callq  0x000000010d12c854       ; NSLog
testm`-[BaseTest print2:s2:] + 49 at BaseTest.m:36
35          NSLog(@"2 %@ %@", s1, s2);
36      }
37      
0x10d12c1f1:  addq   $32, %rsp
0x10d12c1f5:  popq   %rbp
0x10d12c1f6:  ret

第二个参数s2位于%rcx中,但是rsp出栈仍然是32,说明不太可能是栈检查。我们看看三个参数有没有不同

38      -(void)print3:(NSString*)s1 s2:(NSString*)s2 s3:(NSString*)s3
39      {
0x1091f5200:  pushq  %rbp
0x1091f5201:  movq   %rsp, %rbp
0x1091f5204:  subq   $48, %rsp
0x1091f5208:  leaq   13633(%rip), %rax        ; 0x00000001091f8750 @"3 %@ %@ %@"
0x1091f520f:  movq   %rdi, -8(%rbp)
0x1091f5213:  movq   %rsi, -16(%rbp)
0x1091f5217:  movq   %rdx, -24(%rbp)
0x1091f521b:  movq   %rcx, -32(%rbp)
0x1091f521f:  movq   %r8, -40(%rbp)
testm`-[BaseTest print3:s2:s3:] + 35 at BaseTest.m:40
39      {
-> 40       NSLog(@"3 %@ %@ %@", s1, s2, s3);
41      }
-> 0x1091f5223:  movq   -24(%rbp), %rsi
0x1091f5227:  movq   -32(%rbp), %rdx
0x1091f522b:  movq   -40(%rbp), %rcx
0x1091f522f:  movq   %rax, %rdi
0x1091f5232:  movb   $0, %al
0x1091f5234:  callq  0x00000001091f5854       ; NSLog
testm`-[BaseTest print3:s2:s3:] + 57 at BaseTest.m:41
40          NSLog(@"3 %@ %@ %@", s1, s2, s3);
41      }
42      @end
0x1091f5239:  addq   $48, %rsp
0x1091f523d:  popq   %rbp
0x1091f523e:  ret

这次无一例外,参数还是通过寄存器传递。这次常用寄存器不够,就放在%r8。iMac的General Purpose Register有r8~r15,通常我们根本用不完。 最后出栈变成了48而不是40,从这个规律可以发现,rsp的增长都是以偶数个寄存器长度。读者有兴趣试一试4、5个参数。

另外,像NSLog这种可变参数的C api没有遵循__cdecl那种调用方清栈的规则,也是通过寄存器,以movb $0, %al作为结尾,通过其他方式计算个数。

抱歉!评论已关闭.