环境 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作为结尾,通过其他方式计算个数。