x86和x86_64调用约定

x86调用约定

首先介绍什么是调用约定,调用约定也称函数调用约定,是和体系结构,操作系统密切相关的一种约定,所以在此我们不能离开体系架构,因此在这里主要介绍在c语言环境下,x86中linux上的调用约定。

在高级语言中我们会有函数,到了汇编中对应的就是过程,所以调用约定主要是解决三个问题

1 ,参数以什么样的顺序被存储,是从左到右,还是从右到左?

2,参数是存储在哪里,是在寄存器上还是在栈上?

3,栈由谁来恢复?

所以这篇文章主要解决这上面三个问题

1.1调用约定分类

因为编译器种类的原因,调用约定也有许多种,比如微软的,苹果公司的,基本上一个编译器对应一种调用约定,大概有5种调用约定

1._cdecl

所谓的C调用规则。按从右至左的顺序压参数入栈,由调用者把参数弹出栈。切记:对于传送参数的内存栈是由调用者来维护的。
返回值在EAX中因此,对于象printf这样变参数的函数必须用这种规则。编译器在编译的时候对这种调用规则的函数生成修饰名的饿时候,仅在输出函数名前加上一个下划线前缀,格式为_functionname。 
2.__stdcall 
按从右至左的顺序压参数入栈,由被调用者把参数弹出栈。_stdcall是Pascal程序的缺省调用方式,通常用于Win32 Api中,切记:函数自己在退出时清空堆栈,返回值在EAX中。  
__stdcall调用约定在输出函数名前加上一个下划线前缀,后面加上一个“@”符号和其参数的字节数,格式为_functionname@number。如函数int func(int a, double b)的修饰名是_func@12。


3.__fastcall

__fastcall调用的主要特点就是快,因为它是通过寄存器来传送参数的(实际上,它用ECX和EDX传送前两个双字(DWORD)或更小的参数,剩下的参数仍旧自右向左压栈传送,被调用的函数在返回前清理传送参数的内存栈)。__fastcall调用约定在输出函数名前加上一个“@”符号,后面也是一个“@”符号和其参数的字节数,格式为@functionname@number。
这个和__stdcall很象,唯一差别就是头两个参数通过寄存器传送。注意通过寄存器传送的两个参数是从左向右的,即第一个参数进ECX,第2个进EDX,其他参数是从右向左的入stack。返回仍然通过EAX.

4.__pascal

这种规则从左向右传递参数,通过EAX返回,堆栈由被调用者清除

5.__thiscall

仅仅应用于"C++"成员函数。this指针存放于CX寄存器,参数从右到左压。thiscall不是关键词,因此不能被程序员指定,调用约定可以通过工程设置:Setting...\C/C++ \Code Generation项进行选择,缺省状态为__cdecl。

1.2 __cdecl调用约定

这是linux 下gcc默认的调用约定,因此在此详细介绍_cdecl调用约定,为此首先在这里介绍栈帧,栈帧就是函数的活动记录,栈里面存放了局部变量,函数参数,以及保存的寄存器信息,x86架构使用两个栈指针来记录函数的调用记录,即所谓的ebp和esp,其中ebp指向了栈基址,esp指向了栈顶,在x86下一般是向下生长的满栈,

规则1:参数全部存储在栈中

因为x86的历史原因,x86架构下32位程序只有8个通用寄存器,这让x86使用寄存器是捉襟见肘,所以干脆__cdecl调用约定把参数全部入栈,这也是历史遗留原因,如下图为c语言程序

当调用add函数时先把参数入栈,即指令 pushl $6, pushl  $5 ,如下图所示为汇编代码

规则2 : 参数从右到左入栈

这就是所谓的逆序入栈,这对于可变参数函数是有很大作用,逆序入栈后,栈顶指针始终指向第一个参数,所以我们约定可变参数函数的第一个参数为参数个数,因而可变参数函数能很容易知道调用函数传递了多少个参数,还是上图的例子,先入栈参数6,然后入栈参数5。

规则3:栈由调用者恢复

什么是调用者,就是指函数调用者,因为函数调用方很清楚知道向栈中传递了多少个参数,所以栈由函数调用方来恢复再合适不过了,一般在函数参数是以push $1 或者push %eax等入栈,在入栈之后esp=esp-4*参数个数,所以在调用完函数后一般要做把esp加上一个偏移量,如add  $8,%esp,下图为反汇编代码

x86_64调用约定

x86_64之后就不像x86有那么多调用约定,在linux中使用System V AMD64调用约定,x86_64比x86多了8个通用寄存器,为rax,rbx,rcx,rdx,rsp,rbp,rsi,rdi,r8,r9,r10,r11,r12,r13,r14,r15,这些寄存器都是64位,所以在参数传递上拥有更大的操作空间,

规则1:参数传递

前六个参数传递到寄存器中,从rdi,rsi,rcx,rdx,r8,r9依次传递,剩余参数从右到左的顺序入栈,

规则2:参数传递顺序

和_cdecl调用约定一样,System V AMD64调用约定采用从右到左的顺序参数入栈,右边的的参数具有高地址,左边参数地址小。

规则3:栈由调用者恢复

当函数调用完成后,调用者通过add  $参数个数*参数类型大小 %esp 的方式入栈

下图为调用具有八个参数的函数例子,c源程序如下图

汇编后程序如下图

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值