调用约定和栈布局(更新中)

调用约定和栈布局

x86

x86平台下常用的有三种调用约定,__cdecl、__stdcall、__fastcall,默认情况下使用__cdecl。

__cdecl

__cdecl是c/c++使用的默认调用约定。在x86架构下,参数从右到左通过栈传递(右边参数先入栈),由调用者负责清除参数。
返回值通过eax寄存器传递。

下边是一个简单的c程序使用使用如下命令编译结果。

gcc -m32 -O0 -fno-stack-protector -fno-pie callspec_x86.c
int add(int a, int b) {
    int c;
    c = a + b;
    return c;
}

int main() {
    return add(2,3);
}
000004ed <add>:
 4ed:   55                      push   %ebp              # 保存帧指针
 4ee:   89 e5                   mov    %esp,%ebp
 4f0:   83 ec 10                sub    $0x10,%esp        # 分配局部变量空间
 4f3:   8b 55 08                mov    0x8(%ebp),%edx
 4f6:   8b 45 0c                mov    0xc(%ebp),%eax
 4f9:   01 d0                   add    %edx,%eax
 4fb:   89 45 fc                mov    %eax,-0x4(%ebp)
 4fe:   8b 45 fc                mov    -0x4(%ebp),%eax   # 传递返回值
 501:   c9                      leave
 502:   c3                      ret

00000503 <main>:
 503:   55                      push   %ebp
 504:   89 e5                   mov    %esp,%ebp
 506:   6a 03                   push   $0x3
 508:   6a 02                   push   $0x2              # 参数入栈
 50a:   e8 de ff ff ff          call   4ed <add>
 50f:   83 c4 08                add    $0x8,%esp         # 清楚参数
 512:   c9                      leave
 513:   c3                      ret

调用前栈布局,左图为50a即将执行时,右图为50a执行后,即call指令压入了一个返回地址。

在这里插入图片描述在这里插入图片描述

4ee执行后(左图)(帧指针保存和移动)以及4f0执行后(右图)(分配局部变量空间)

4ee指的是上边汇编代码的地址

在这里插入图片描述在这里插入图片描述

501执行后(右图)(离开当前函数帧)和502执行后(左图)(返回main函数)。leave指令作用如下

mov %ebp, %esp
pop %ebp

执行leave指令后栈指针和帧指针将指向main函数的函数帧,esp指向返回地址,后续执行ret指令则将返回地址出栈并赋值给eip,
实现返回main函数继续执行。

在这里插入图片描述在这里插入图片描述

x86-64

windows

  • 整数参数(包括指针)

    参数编号(从左边开始)对应寄存器
    参数1RCX
    参数2RDX
    参数3R8
    参数4R9
    返回值RAX

    超过4个参数时,剩下的参数从右往左依次入栈。

  • 浮点数参数

    参数编号(从左边开始)对应寄存器
    参数1xmm0
    参数2xmm1
    参数3xmm2
    参数4xmm3
    返回值xmm0

参考 x64 calling convention | Microsoft Docs

  • 编译器验证

测试使用的c代码如下:

int add(int a, int b, int c, int d) {
    int e;
    e = a + b + c + d;
    return e;
}

float add_float(float a, float b, float c, float d) {
    float e;
    e = a + b + c + d;
    return e;
}

int main() {
    add(1,2,3,4);
    add_float(1.0, 2.0, 3.0, 4.0);
    return 0;
}

编译后(使用MinGW-w64)的main函数(下边的r9d是r9寄存器的低32位,r8d同理)

参考 x64_cheatsheet.pdf (brown.edu)

00000000004015c8 <main>:
  4015c8:	55                   	push   %rbp
  4015c9:	48 89 e5             	mov    %rsp,%rbp
  4015cc:	48 83 ec 20          	sub    $0x20,%rsp
  4015d0:	e8 fb 00 00 00       	callq  4016d0 <__main>
  4015d5:	41 b9 04 00 00 00    	mov    $0x4,%r9d
  4015db:	41 b8 03 00 00 00    	mov    $0x3,%r8d
  4015e1:	ba 02 00 00 00       	mov    $0x2,%edx
  4015e6:	b9 01 00 00 00       	mov    $0x1,%ecx
  4015eb:	e8 60 ff ff ff       	callq  401550 <add>
  4015f0:	f3 0f 10 1d 08 2a 00 	movss  0x2a08(%rip),%xmm3        # 404000 <.rdata>
  4015f7:	00 
  4015f8:	f3 0f 10 15 04 2a 00 	movss  0x2a04(%rip),%xmm2        # 404004 <.rdata+0x4>
  4015ff:	00 
  401600:	f3 0f 10 0d 00 2a 00 	movss  0x2a00(%rip),%xmm1        # 404008 <.rdata+0x8>
  401607:	00 
  401608:	f3 0f 10 05 fc 29 00 	movss  0x29fc(%rip),%xmm0        # 40400c <.rdata+0xc>
  40160f:	00 
  401610:	e8 6f ff ff ff       	callq  401584 <add_float>
  401615:	b8 00 00 00 00       	mov    $0x0,%eax
  40161a:	48 83 c4 20          	add    $0x20,%rsp
  40161e:	5d                   	pop    %rbp
  40161f:	c3                   	retq   

Linux(System V AMD64 ABI)

  • 整数参数(包括指针)

    整数参数编号对应寄存器
    参数1rdi
    参数2rsi
    参数3rdx
    参数4rcx
    参数5r8
    参数6r9
    返回值rax(64位), rax+rdx(128位)

    超过6个参数后通过栈传递。

  • 浮点数参数

    浮点数参数编号对应寄存器
    参数1xmm0
    参数2xmm1
    参数3xmm2
    参数4xmm3
    参数5xmm4
    参数6xmm5
    参数7xmm6
    参数8xmm7
    返回xmm0, xmm1

    超过8个参数后通过栈传递。

参考 x86 calling conventions - Wikipedia
x86-64-psABI-1.0.pdf

  • 编译器测试

测试c源代码同上一小节相同,使用如下命令编译

gcc call_convention.c -o call_convention

使用objdump查看main函数汇编代码

0000000000000660 <main>:
 660:   55                      push   %rbp
 661:   48 89 e5                mov    %rsp,%rbp
 664:   b9 04 00 00 00          mov    $0x4,%ecx
 669:   ba 03 00 00 00          mov    $0x3,%edx
 66e:   be 02 00 00 00          mov    $0x2,%esi
 673:   bf 01 00 00 00          mov    $0x1,%edi
 678:   e8 7d ff ff ff          callq  5fa <add>
 67d:   f3 0f 10 1d af 00 00    movss  0xaf(%rip),%xmm3        # 734 <_IO_stdin_used+0x4>
 684:   00
 685:   f3 0f 10 15 ab 00 00    movss  0xab(%rip),%xmm2        # 738 <_IO_stdin_used+0x8>
 68c:   00
 68d:   f3 0f 10 0d a7 00 00    movss  0xa7(%rip),%xmm1        # 73c <_IO_stdin_used+0xc>
 694:   00
 695:   f3 0f 10 05 a3 00 00    movss  0xa3(%rip),%xmm0        # 740 <_IO_stdin_used+0x10>
 69c:   00
 69d:   e8 82 ff ff ff          callq  624 <add_float>
 6a2:   b8 00 00 00 00          mov    $0x0,%eax
 6a7:   5d                      pop    %rbp
 6a8:   c3                      retq
 6a9:   0f 1f 80 00 00 00 00    nopl   0x0(%rax)

附录

如果不加-fno-pie编译则gcc会生成位置无关的代码(可以加载到任意位置执行),在x86架构下
会调用__x86.get_pc_thunk.ax函数将eip的值保存在eax中实现基于eip的偏移寻址,如下汇编代码所示。
在x86-64架构下,由于指令集支持pc偏移寻址,因此就不需要上述函数了。

gcc -m32 -O0 -fno-stack-protector  callspec_x86.c
000004ed <add>:
 4ed:   55                      push   %ebp
 4ee:   89 e5                   mov    %esp,%ebp
 4f0:   83 ec 10                sub    $0x10,%esp
 4f3:   e8 30 00 00 00          call   528 <__x86.get_pc_thunk.ax>
 4f8:   05 e4 1a 00 00          add    $0x1ae4,%eax
 4fd:   8b 55 08                mov    0x8(%ebp),%edx
 500:   8b 45 0c                mov    0xc(%ebp),%eax
 503:   01 d0                   add    %edx,%eax
 505:   89 45 fc                mov    %eax,-0x4(%ebp)
 508:   8b 45 fc                mov    -0x4(%ebp),%eax
 50b:   c9                      leave
 50c:   c3                      ret

0000050d <main>:
 50d:   55                      push   %ebp
 50e:   89 e5                   mov    %esp,%ebp
 510:   e8 13 00 00 00          call   528 <__x86.get_pc_thunk.ax>
 515:   05 c7 1a 00 00          add    $0x1ac7,%eax
 51a:   6a 03                   push   $0x3
 51c:   6a 02                   push   $0x2
 51e:   e8 ca ff ff ff          call   4ed <add>
 523:   83 c4 08                add    $0x8,%esp
 526:   c9                      leave
 527:   c3                      ret
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值