上一篇介紹了vc(windows)平台在x64體系當中,c函數的傳參方式。本篇將要介紹gcc(類linux,mac)平台在x64中,c函數是如何傳參的。
為節約時間和篇幅,首先來定義一個有十個參數的函數,參數類型包羅了內嵌類型:
int foo(char c, short s, int i, long l, long long ll, char* p, // 前6個參數,注意我的划分和參數類型 void** pp, float f, void* x, double d);
反匯編調用
foo('c', 0, 1, 2, 3, (char*)0x4, (void**)0x5, 1.f, (void*)6, 2.f); 0x000000000040067b <+55>: movsd 0x2d5(%rip),%xmm0 # 0x400958 <__dso_handle+8> double d = 2.f 0x0000000000400683 <+63>: movq $0x6,0x8(%rsp) # (void*) 6 0x000000000040068c <+72>: movq $0x5,(%rsp) # (void**) 0x5 0x0000000000400694 <+80>: movapd %xmm0,%xmm1 0x0000000000400698 <+84>: movss 0x2c0(%rip),%xmm0 # 0x400960 <__dso_handle+16> float f = 1.f 0x00000000004006a0 <+92>: mov $0x4,%r9d # (char*) 0x4 0x00000000004006a6 <+98>: mov $0x3,%r8d # (long long) 3 0x00000000004006ac <+104>: mov $0x2,%ecx # (long) 2 0x00000000004006b1 <+109>: mov $0x1,%edx # (int) 1 0x00000000004006b6 <+114>: mov $0x0,%esi # (short) 0 0x00000000004006bb <+119>: mov $0x63,%edi # (char) 'c' 0x00000000004006c0 <+124>: callq 0x4005c4 <_Z3foocsilxPcPPvfS0_d>
可以看到數據類型分兩類,浮點和非浮點型。我傳的實參數也是按這兩類划分遞增的。
非浮點參數分別是 'c', 0, 1, 2, 3, (char*)0x4, (void**)0x5, (void*)6。先將前6個優先按順序按排到rdi,rsi,rdx,rcx,r8和r9。剩下(void**)5,(void*)6。
浮點參數分別是 1.f, 2.f。 按順序安排到xmm0,xmm1。
最后將兩種類型不能放入寄存器的剩余參數,由右向左依次入棧。
下面再定義一個超級無敵多參數的函數,用盡全部傳參寄存器,印證我上面的分析。
int foo2(char c, short s, int i, long l, long long ll, char* p, void** pp, float f, void* x, double d, // 至此和上面foo定義一樣 float xmm2, float xmm3, float xmm4, float xmm5, float xmm6, float xmm7, // 追加6個浮點型用盡余下的寄存器 float xmmUnknow);
反匯編調用
foo2('c', 0, 1, 2, 3, (char*)4, (void**)5, 1.f, (void*)6, 2.f, 3.f, 4.f, 5.f, 6.f, 7.f, 8.f, (float)i); 0x00000000004006c5 <+129>: cvtsi2ssl -0xc(%rbp),%xmm0 0x00000000004006ca <+134>: movsd 0x286(%rip),%xmm1 # 0x400958 <__dso_handle+8> 0x00000000004006d2 <+142>: movss %xmm0,0x10(%rsp) # *** 最尾的浮點型只被放入堆棧中 0x00000000004006d8 <+148>: movq $0x6,0x8(%rsp) # *** 和foo一樣 0x00000000004006e1 <+157>: movq $0x5,(%rsp) # *** 和foo一樣 0x00000000004006e9 <+165>: movss 0x273(%rip),%xmm7 # 0x400964 <__dso_handle+20> 0x00000000004006f1 <+173>: movss 0x26f(%rip),%xmm6 # 0x400968 <__dso_handle+24> 0x00000000004006f9 <+181>: movss 0x26b(%rip),%xmm5 # 0x40096c <__dso_handle+28> 0x0000000000400701 <+189>: movss 0x267(%rip),%xmm4 # 0x400970 <__dso_handle+32> 0x0000000000400709 <+197>: movss 0x263(%rip),%xmm3 # 0x400974 <__dso_handle+36> 0x0000000000400711 <+205>: movss 0x25f(%rip),%xmm2 # 0x400978 <__dso_handle+40> 0x0000000000400719 <+213>: movss 0x23f(%rip),%xmm0 # 0x400960 <__dso_handle+16> 0x0000000000400721 <+221>: mov $0x4,%r9d 0x0000000000400727 <+227>: mov $0x3,%r8d 0x000000000040072d <+233>: mov $0x2,%ecx 0x0000000000400732 <+238>: mov $0x1,%edx 0x0000000000400737 <+243>: mov $0x0,%esi 0x000000000040073c <+248>: mov $0x63,%edi 0x0000000000400741 <+253>: callq 0x4005f5 <_Z4foo2csilxPcPPvfS0_dfffffff>
非浮點參數分別是 'c', 0, 1, 2, 3, (char*)0x4, (void**)0x5, (void*)6。先將前6個優先按順序按排到rdi,rsi,rdx,rcx,r8和r9。剩下(void**)5,(void*)6。
浮點參數分別是 1.f, 2.f, 3.f, 4.f, 5.f, 6.f, 7.f, 8.f, (float)i。 按順序安排到xmm0-xmm7,剩下(float)i。
最后將兩種類型不能放入寄存器的剩余參數,分別是(void**)5,(void*)6,(float)i,由右向左依次入棧。
最后我選取一個特例來作為本篇結束,gcc如何傳遞臨時對象。
struct point {float x,y;}; struct obj { int i; float f[8]; void foo(point pt) { f[2] += pt.x; f[3] *= pt.y; } };
反匯編調用
obj j; point pt; j.foo(pt); 0x000000000040078d <+329>: movq -0x20(%rbp),%xmm0 0x0000000000400792 <+334>: lea -0x50(%rbp),%rax 0x0000000000400796 <+338>: mov %rax,%rdi 0x0000000000400799 <+341>: callq 0x400814 <_ZN3obj3fooE5point>
rdi是什么大家都清楚,剩下另一個參數載體就是xmm0了。再看一看函數定義,參數是個臨時對象,再看對象定義,point結構體是兩個單精浮點,共占64位。而xmm寄存器可以存放4個單精浮點數據。
下面再看成員函數foo的反匯編剛好印證了。
Dump of assembler code for function _ZN3obj3fooE5point: 0x0000000000400814 <+0>: push %rbp 0x0000000000400815 <+1>: mov %rsp,%rbp 0x0000000000400818 <+4>: mov %rdi,-0x8(%rbp) 0x000000000040081c <+8>: movq %xmm0,-0x10(%rbp) # 低64位存放了臨時對象 0x0000000000400821 <+13>: mov -0x8(%rbp),%rax 0x0000000000400825 <+17>: movss 0xc(%rax),%xmm1 0x000000000040082a <+22>: movss -0x10(%rbp),%xmm0 # pt.x 0x000000000040082f <+27>: addss %xmm1,%xmm0 0x0000000000400833 <+31>: mov -0x8(%rbp),%rax 0x0000000000400837 <+35>: movss %xmm0,0xc(%rax) 0x000000000040083c <+40>: mov -0x8(%rbp),%rax 0x0000000000400840 <+44>: movss 0x10(%rax),%xmm1 0x0000000000400845 <+49>: movss -0xc(%rbp),%xmm0 # pt.y 0x000000000040084a <+54>: mulss %xmm1,%xmm0 0x000000000040084e <+58>: mov -0x8(%rbp),%rax 0x0000000000400852 <+62>: movss %xmm0,0x10(%rax) 0x0000000000400857 <+67>: leaveq 0x0000000000400858 <+68>: retq End of assembler dump.
到此為止,我已經用了三篇來介紹x64體系三種常用平台在c/c++/objc編程的傳參方式。
上篇,通過lldb調試介紹mac平台下x64傳參;
中篇,通過windbg調試介紹windows平台下x64傳參;
下篇,通過gdb調試介紹gcc(類linux)平台下x64傳參,本篇對於mac,ios同樣適用。
預告:后面將要進入反匯編分析objc程序。