博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
x86 x64下调用约定浅析
阅读量:7141 次
发布时间:2019-06-29

本文共 2394 字,大约阅读时间需要 7 分钟。

 

x86平台下调用约定  

  我们都知道x86平台下常用的有三种调用约定,__cdecl、__stdcall、__fastcall。我们分别对这三种调用约定进行分析。

  __cdecl

  __cdecl是C/C++的默认调用约定,如果不显示声明调用约定的情况下,就是该调用约定。下面我们来从汇编层次来熟悉这种调用约定。

我写了一个函数,如下:

1 int __cdecl TestCdecl(int a, int b, int c, int d, int e)2 {3     return a + b + c + d + e;4 }

  对了,给大家说一下,VS如何进入反汇编。我们在调试界面下,按住Alt+8键就可以进入反汇编窗口了。

  可以看出__cdecl参数从右至左入栈,然后由调用者(caller)清理栈区。

  __stdcall

  __stdcall是Windows API默认调用约定,微软的WINAPI、CALLBACK等宏都是这个调用约定,我还是写了个例子来看一下。

1 int __stdcall TestStdcall(int a, int b, int c, int d, int e)2 {3     return a + b + c + d + e;4 }

  我们继续来看一下反汇编。

  可以看到,main函数并没有对子函数的清栈过程,__stdcall参数跟__cdecl入栈方式相同,从右至左入栈,被调用者(callee)清理栈区。

  __fastcall

  __fastcall是32位下一种特殊的调用约定,我慢还是通过代码和反汇编看看它到底特殊在哪。

1 int __fastcall TestFastcall(int a, int b, int c, int d, int e)2 {3     return a + b + c + d + e;4 }

  我们可以看到,__fastcall参数还是从右至左入栈,但是不同的是前两个参数被分别放进了ecx、edx寄存器,如果少于或等于两个参数,会先将参数放入寄存器。同样由被调用者(callee)清理栈区。

 x64平台下调用约定

  我们再来看一下64位情况下调用约定。同样我们测试声明为__cdecl的调用约定。反汇编结果如下:

  我们看到这个入栈方式怎么跟32位下fastcall的入栈很像。这时候查一下CSDN

发现有如下一句话:在设计调用约定时,x64 体系结构利用机会清除了现有 Win32 调用约定(如 __stdcall、__cdecl、__fastcall、_thiscall 等)的混乱。在 Win64 中,只有一个本机调用约定和 __cdecl 之类的修饰符被编译器忽略。除此之外,减少调用约定行为还为可调试性带来了好处。

  原来64位平台下只有一种变形的__fastcall的调用约定,前4参数则先放入ecx、edx、r8、r9寄存器,更多的参数放入栈区。这个时候我们要注意的是,在64位下,系统还是为前4个参数预留了栈区空间(每个栈空间大小为8字节,共32字节大小),然后将基存器的值放入所预留的栈区空间。为什么系统要多此一举呢?我们都知道寄存器传递参数速度要远大于栈区传值,而将寄存器中的值再放入栈区预留空间,这是为了防止在传递参数的过程中,寄存器需要接收其他的值而导致参数无法传递,或者其他值无法接收的情况。

  另外我们还看到CSDN上说64位下__fastcall由调用者(caller)清理栈区空间。但是我们为什么没有看见main()函数清理子函数栈空间的过程呢?

  这是由于64位平台下栈区空间开辟问题导致。我们还在CSDN上看到这样一句话:与通过 PUSH 和 POP 指令在堆栈中显式添加和移除参数的 x86 编译器不同,x64 代码生成器会预留足够的堆栈空间,以调用最大目标函数(参数方法)所使用的任何内容。随后,在调用子函数时,它重复使用相同的堆栈区域来设置参数。

  这句话什么意思呢?它的意思就是我们在64位下一开始系统会为main()函数开辟一个很大的栈区,但是main()函数并未消耗掉这么大的栈区空间,这时候怎么办呢?子函数就会还继续利用main()函数的栈区空间,所以main()函数并不用对子函数栈区空间进行清理。

参数列表

  这时候我们再来看一下一些特殊的问题,在C/C++下有一个可变长参数列表的概念。我们来看一下在这种情况下,调用约定又有什么变化。

x86下参数列表

  按照惯例先看代码

1 int __fastcall Test(unsigned int n, ...) 2 { 3     int sum = 0; 4     va_list args; 5     va_start(args, n); 6     while (n>0) 7     { 8         //通过va_arg(args,int)依次获取参数的值   9         sum += va_arg(args, int);10         n--;11     }12     va_end(args);13     return sum;14 }

  再来看看反汇编:

  我们可以看到,这时候__fastcall已经退化为__cdecl了,其实其他调用约定也一样,在32位平台下全部会退化为__cdecl。

x64平台下调用约定

  我们再将函数编译为64位,反编译如下:

  这个还是64位下默认的特殊的__fastcall调用约定。至此调用约定相关问题已基本说完了,小弟不才,有什么不对的地方还请指正。

转载于:https://www.cnblogs.com/Toring/p/6650043.html

你可能感兴趣的文章
Spring Web Services 3.0.4.RELEASE和2.4.3.RELEASE发布
查看>>
如何自动搞定全站图片的alt属性?
查看>>
配置一次,到处运行:将配置与运行时解耦
查看>>
突发热点事件下微博高可用注册中心vintage的设计\u0026实践
查看>>
Elixir 1.3带来新的语言功能、API和改进后的工具
查看>>
用Elm语言降低失败的风险
查看>>
抓住热门话题一对一直播,如何在风浪四起的直播市场劈风斩浪? ...
查看>>
手把手教你用owncloud搭建属于自己的云盘
查看>>
epoll+socket实现 socket并发 linux服务器
查看>>
阿里巴巴人事再调整,将打通淘宝、天猫两个消费场景 ...
查看>>
Kubernetes + CRI + Kata + Firecracker
查看>>
菜鸟成都未来园区启动,无人车首次进入园区调拨运输环节 ...
查看>>
算法不扎实的程序员,每个都很慌
查看>>
4个需要避免的常见Kubernetes监控陷阱
查看>>
规划一个智能工厂应避免的十个坑
查看>>
Linux 虚拟网络设备详解之 Bridge 网桥
查看>>
LaTeX的简单使用方法
查看>>
IO流
查看>>
第二十四章:页面导航(四)
查看>>
数字对讲系统开发札记(前端linux c 后端 c#)
查看>>