首页 > 其他分享 >调用约定

调用约定

时间:2023-01-31 10:22:48浏览次数:39  
标签:调用 函数 约定 ebp 寄存器 堆栈 参数

调用约定

1. x86 体系

​ x86 体系下有四种调用约定

1.1 四种调用约定的区别

调用约定 __cdecl __stdcall __fastcall __thiscall
调用规范 int __cdecl function(int a,int b) int __stdcall function(int a,int b) int __fastcall function(int a,int b)
传参顺序 右至左 右至左 右至左 右至左
堆栈平衡 主调函数平衡堆栈 被调函数平衡堆栈 被调函数平衡堆栈 对参数个数不定的,调用者清理栈,否则函数自己清理栈
参数种类 参数靠堆栈传递,参数个数不固定 参数放在堆栈中 x86:从左到右的前两个参数放在ecx,edx中进行push,剩余参数从右至左依次压参
x64:从左到右的参数放在ecx,edc,r8,r9中进行push,剩余参数从右至左依次压参
如果参数个数确定,this指针通过ecx传递给被调用者;
如果参数个数不确定,this指针在所有参数压栈后被压入堆栈;
函数种类 c,c++库函数(默认) WINAPI,NTAPI,CALLBACK,PASCAL宏 C++调用类成员函数

1.2 汇编角度分析

1)__cdecl 函数名在符号表中被记录为_function

push   1
push   2
call   function
add    esp,8              ; 注意:这里调用者在恢复堆栈

; 函数汇编:
push   ebp                ; 保存ebp寄存器,该寄存器将用来保存堆栈的栈顶指针,可以在函数退出时恢复
mov    ebp,esp            ; 保存堆栈指针
mov    eax,[ebp + 8H]     ; 堆栈中ebp指向位置之前依次保存有ebp,cs:eip,a,b,ebp +8指向a
add    eax,[ebp + 0CH]    ; 堆栈中ebp + 12处保存了b
mov    esp,ebp            ; 恢复esp
pop    ebp
ret                       ; 注意,这里没有修改堆栈

2)__stdcall 编译时,这个函数的名字被翻译成_function@8

push 1          		; 第一个参数入栈
call function   		; 调用参数,注意此时自动把cs:eip入栈

; 函数汇编:												
push  ebp               ; 保存ebp寄存器,该寄存器将用来保存堆栈的栈顶指针,可以在函数退出时恢复
mov   ebp,esp           ; 保存堆栈指针
mov   eax,[ebp + 8H]    ; 堆栈中ebp指向位置之前依次保存有ebp,cs:eip,a,b,ebp +8指向a
add   eax,[ebp + 0CH]   ; 堆栈中ebp + 12处保存了b
mov   esp,ebp           ; 恢复esp
pop   ebp
ret   8

2. x64 体系

2.1 x64调用约定

​ 在64位下只有一种调用约定,类似于fastcall,要写其他的也不会出错,这主要是为了兼容以前定义的32的头文件

2.2 x86与x64的区别

x86到x64的两个重要修改

  1. 64 位寻址能力
  2. 一组通用展开 64 位寄存器(共16个)

2.3 传递参数

​ 使用寄存器(对前四个变量)和堆栈帧传递其他参数。

  1. 前4个参数使用 rcx rdx r8 r9传递

  2. 如果参数是浮点/双精度型,则它们在 XMM0L、XMM1L、XMM2L 和 XMM3L 中传递。

  3. 16 字节的参数由引用传递。

    image-20230131012806787不再像32位一样push ebp mov ebp,esp.而是只保存了rdi就退出

2.4 寄存器传参

  1. 1个参数,采用ecx传参

    image-20230131014320342

  2. 参数大于32位采用rcx传参

    image-20230131014550347

    函数里面把参数复制到了rsp+8

    image-20230131015305269

  3. 4个参数,采用ecx,edx,r8d,r9d

    image-20230131015454781

  4. 参数大于32位采用的是rcx,rdx,r8,r9寄存器

    image-20230131015803762

    在函数里,把参数复制到rsp+0x20 +0x18 +0x10 +0x8的位置

    image-20230131015813678

  5. 大于4个参数,传参多了一个rsp+0x20内存.加上call对esp+8的位置,实际在函数里这个内存地址是rsp+0x28。
    寄存器传参也要分配栈

    首先看一下没有调用函数时提升的栈空间是0x10

    image-20230131015842995

    调用一个没有参数的函数,栈空间提升了0x10. 这是给函数的返回地址和多给了8字节的栈空间。
    这8字节是给函数内部保存寄存器参数的值的。mov [rsp+8],rcx

    image-20230131020004241

2.5 易变寄存器和非异变寄存器操作

​ 异变寄存器:rax rcx rdx r8 r9 r10 r11 其余为非异变寄存器
​ push pop指令仅用来保存非易变寄存器

image-20230131101251859

2.6 汇编调用函数的问题

​ 即使函数只有一个参数,也得分配0x20的栈空间 sub rsp,0x20

image-20230131101502366

2.7 通常不使用rbp寻址栈内存

​ 通常不使用rbp寻址栈内存,所以rsp在函数中尽量保持稳定(一次性分配参数和变量空间)

​ 如下图:连续调用4次函数,并没有像32位那样add esp,xxx,而是直接在函数main函数头部sub rsp,0x20 ,在尾部add sup,0x20
​ 对于调用的函数参数少于4个字节的情况下栈空间就够了。多余4个会分配更多空间。

image-20230131101619807

​ 像下面这样其中有一个函数的参数是5个会出现什么情况呢?
​ 可以看出现在是sub rsp,0x30 也就是说编译器并不是按照最多参数5个分配0x28而是对其了0x10。
​ 直接分配的0x30字节

image-20230131101632761

2.8 x64调用约定特性

  1. 前四个整型或指针类型参数由RCX,RDX,R8,R9依次传递,前四个浮点类型参数由XMM0,XMM1,XMM2,XMM3依次传递。
  2. 调用函数为前四个参数在调用栈上保留相应的空间,称作shadow space或spill slot。即使被调用方没有或小于4个参数,调用函数仍然保留那么多的栈空间,这有助于在某些特殊情况下简化调用约定。
  3. 除前四个参数以外的任何其他参数通过栈来传递,从右至左依次入栈。
  4. 由调用函数负责清理调用栈。
  5. 小于等于64位的整型或指针类型返回值由RAX传递。
  6. 浮点返回值由XMM0传递。
  7. 更大的返回值(比如结构体),由调用方在栈上分配空间,并有RCX持有该空间的指针并传递给被调用函数,因此整型参数使用的寄存器依次右移一格,实际只可以利用3个寄存器,其余参数入栈。函数调用结束后,RAX返回该空间的指针。

注意

  • 除RCX,RDX,R8,R9以外,RAX、R10、R11、XMM4 和 XMM5也是易变化的(volatile)寄存器。
  • RBX, RBP, RDI, RSI, R12, R14, R14, and R15寄存器则必须在使用时进行保护。
  • 在寄存器中,所有参数都是右对齐的。小于64位的参数并不进行高位零扩展,也就是高位是无法预测的垃圾数据。

标签:调用,函数,约定,ebp,寄存器,堆栈,参数
From: https://www.cnblogs.com/XiuzhuKirakira/p/17078098.html

相关文章

  • 【Qt】Qt与Js互相调用
    Qt与Js互相调用目前使用场景有:通过QWebEngineView,来加载某个url或html文件(需要包含特定js文件)。通过QWebChannel绑定到QWebEngineView上,qt可以调用js暴露的接口,js也可......
  • js 调用摄像头录像
    <!DOCTYPEhtml><htmllang="en"><head><metacharset="UTF-8"><metahttp-equiv="X-UA-Compatible"content="IE=edge"><metaname="viewport"conten......
  • 调用方法 不传参 求评委平均值
    packagecom.fqs.demo;importjava.util.Scanner;publicclassPingFen{publicstaticvoidmain(String[]args){//去掉最高分去掉最低分获取平均......
  • 下标引用函数调用和表达式求值
    前言:下标的用法非常非常的重要关乎到数组函数指针结构体等等一切,函数的调用也有很多细节表达式求值涉及优先级问题要注意1.下标引用、函数调用和结构成员注:一切下标又是从0......
  • 使用GDB调试python调用的C++共享库
    目录1.首先编写python调用C++的demo2.gdb调试python程序3.全速运行程序4.查看堆栈信息当我们用Python调用C++的库,并且C++库中存在段错误崩溃时,首先想到的还是用gdb......
  • python调用cpp 调试_python和C++联合调试
    python可以利用SO的方式去调用C++中的函数,但是需要一种调试方案来进行python和C++的联合调试,效果是直接在c++代码中打断点,然后python在进行c++so调用的时候,直接进入到断......
  • Linux的多线程下使用c/c++调用Python方法示例
    首先,所有python的函数都是用extern"C"定义的,因此对于C和C++,其使用是一样的。c语言调用python必须要有的API(不管有没有多线程):  PyRun_SimpleString//执行一段......
  • c++多线程调用python
    脚本语言是快速编写富有弹性的代码的重要方法之一,在 Unix 系统自动化管理中已经应用了多种脚本语言。现在,在许多应用开发中,也提供了脚本层,这大大方便用户实现通用任务自......
  • QT(c++) 线程 调用python问题
    1、背景简单说一下需求,Qt开发的上位机界面程序,需要调用Python编写的算法跑一个结果返回到界面上显示。2、度娘出一篇博客,按照步骤进行环境搭建和简单的代码测试......
  • C++子线程中调用python代码
    项目需要C++调用python的算法,由于python算法比较耗时,因此采用在C++里启动workingthread来调用python脚本,python代码里含有cv2.imread()等opencv的调用,在子线程里调用会卡......