zr.*_*zr. 12 c linux windows x86 calling-convention
对于以下C代码:
struct _AStruct {
int a;
int b;
float c;
float d;
int e;
};
typedef struct _AStruct AStruct;
AStruct test_callee5();
void test_caller5();
void test_caller5() {
AStruct g = test_callee5();
AStruct h = test_callee5();
}
Run Code Online (Sandbox Code Playgroud)
我得到Win32的以下反汇编:
_test_caller5:
00000000: lea eax,[esp-14h]
00000004: sub esp,14h
00000007: push eax
00000008: call _test_callee5
0000000D: lea ecx,[esp+4]
00000011: push ecx
00000012: call _test_callee5
00000017: add esp,1Ch
0000001A: ret
Run Code Online (Sandbox Code Playgroud)
而对于Linux32:
00000000 <test_caller5>:
0: push %ebp
1: mov %esp,%ebp
3: sub $0x38,%esp
6: lea 0xffffffec(%ebp),%eax
9: mov %eax,(%esp)
c: call d <test_caller5+0xd>
11: sub $0x4,%esp ;;;;;;;;;; Note this extra sub ;;;;;;;;;;;;
14: lea 0xffffffd8(%ebp),%eax
17: mov %eax,(%esp)
1a: call 1b <test_caller5+0x1b>
1f: sub $0x4,%esp ;;;;;;;;;; Note this extra sub ;;;;;;;;;;;;
22: leave
23: ret
Run Code Online (Sandbox Code Playgroud)
我试图了解调用后调用者的行为方式的差异.为什么Linux32中的调用者会执行这些额外的子操作?
我假设两个目标都遵循cdecl调用约定.cdecl不定义返回结构的函数的调用约定吗?!
编辑:
我添加了被调用者的实现.当然,你可以看到Linux32被调用者弹出它的参数,而Win32被调用者不会:
AStruct test_callee5()
{
AStruct S={0};
return S;
}
Run Code Online (Sandbox Code Playgroud)
Win32反汇编:
test_callee5:
00000000: mov eax,dword ptr [esp+4]
00000004: xor ecx,ecx
00000006: mov dword ptr [eax],0
0000000C: mov dword ptr [eax+4],ecx
0000000F: mov dword ptr [eax+8],ecx
00000012: mov dword ptr [eax+0Ch],ecx
00000015: mov dword ptr [eax+10h],ecx
00000018: ret
Run Code Online (Sandbox Code Playgroud)
Linux32反汇编:
00000000 <test_callee5>:
0: push %ebp
1: mov %esp,%ebp
3: sub $0x20,%esp
6: mov 0x8(%ebp),%edx
9: movl $0x0,0xffffffec(%ebp)
10: movl $0x0,0xfffffff0(%ebp)
17: movl $0x0,0xfffffff4(%ebp)
1e: movl $0x0,0xfffffff8(%ebp)
25: movl $0x0,0xfffffffc(%ebp)
2c: mov 0xffffffec(%ebp),%eax
2f: mov %eax,(%edx)
31: mov 0xfffffff0(%ebp),%eax
34: mov %eax,0x4(%edx)
37: mov 0xfffffff4(%ebp),%eax
3a: mov %eax,0x8(%edx)
3d: mov 0xfffffff8(%ebp),%eax
40: mov %eax,0xc(%edx)
43: mov 0xfffffffc(%ebp),%eax
46: mov %eax,0x10(%edx)
49: mov %edx,%eax
4b: leave
4c: ret $0x4 ;;;;;;;;;;;;;; Note this ;;;;;;;;;;;;;;
Run Code Online (Sandbox Code Playgroud)
为什么Linux32中的调用者会执行这些额外的子操作?
原因是使用由编译器注入的隐藏指针(命名返回值优化)来按值返回结构.在SystemV的ABI,第41页,在"函数返回结构或联合"一节中,它说:
被调用的函数必须在返回之前从堆栈中删除此地址.
这就是为什么你得到一个ret $0x4结束test_callee5(),它是为了遵守ABI.
现在关于sub $0x4, %esp每个test_callee5()调用站点之后的存在,它是上述规则的副作用,结合C编译器生成的优化代码.由于本地存储堆栈空间完全预先保留:
3: sub $0x38,%esp
Run Code Online (Sandbox Code Playgroud)
没有必要按下/弹出隐藏的指针,它只是写在预留空间的底部(指向esp),mov %eax,(%esp)在第9行和第17行使用.由于堆栈指针没有递减,所以sub $0x4,%esp在那里否定的效果ret $0x4,并保持堆栈指针不变.
在Win32上(我猜是使用MSVC编译器),没有这样的ABI规则,使用简单ret(如cdecl中预期的那样),隐藏指针在第7行和第11行被压入堆栈.虽然,这些插槽在之后没有被释放调用,作为优化,但仅在被调用者退出之前,使用add esp,1Ch,释放隐藏的指针堆栈槽(2*0x4字节)和本地AStruct结构(0x14字节).
cdecl不定义返回结构的函数的调用约定吗?!
不幸的是,它没有,它随C编译器和操作系统而变化
| 归档时间: |
|
| 查看次数: |
3184 次 |
| 最近记录: |