调用约定

Posted yunshouhu

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了调用约定相关的知识,希望对你有一定的参考价值。

8086 CPU寄存器

均为16位。

寄存器名AXBXCXDXSPBPSIDIDSESSSCSIP
作用通用寄存器栈区段地址寄存器   数据地址寄存器 栈区偏移地址寄存器段地址寄存器偏移地址寄存器

内存寻址

  • 两个寄存器操作只能相加,不能相减,例如

mov ebx, [esi+eax] ; 有效

mov ebx, [esi - eax] ; 无效,不能做减法

  • 最多只有两个寄存器参与地址计算

调用约定(Calling Convention)

调用约定是极为重要的部分。它关系到子函数调用的规则:如何调用及如何返回

有很多种调用约定,以C语言调用约定为例:

C语言调用约定(C Calling Convention)

  • 强依赖硬件提供的支持
  • 基于push, pop, call, ret等指令
  • 参数通过栈传递,子过程用到的变量也放在栈上

调用约定通常可分为两个方面:调用方规则(Caller Rules)和被调方规则(Callee Ruler)。

调用方规则(Caller Rules)

C语言调用约定规定,在子过程(函数)调用之前,调用方应该:

  • 保存应由调用方保存的寄存器(caller-saved registers):EAX, ECX, EDX。

这几个寄存器可能会被被调用方(callee)修改,所以先保存它们,以便调用结 束后恢复栈的状态。

  • 将需要传给子过程的参数入栈(push onto stack)

参数按逆序 push 入栈(最后一个参数先入栈)。由于栈是向下生长的,第一个参数 会被存储在最低地址(这个特性使得变长参数列表成为可能)。此处存疑

  • 使用 call 指令,调用子过程(函数)

call 先将返回地址 push 到栈上,然后开始执行子过程代码。子过程代码需要遵守的 callee rules。

子过程返回后(call 执行结束之后),被调用方会将返回值放到 EAX 寄存器,调用方 可以从中读取。为恢复机器状态,调用方需要做:

 

  1. 从栈上删除传递的参数

    栈恢复到准备发起调用之前的状态。

  2. 恢复由调用方保存的寄存器(EAXECXEDX)—— 从栈上 pop 出来

    调用方可以认为,除这三个之外,其他寄存器的值没有被修改过。

 

被调方规则(Callee Ruler)

  1. 将寄存器 EBP 的值入栈,然后 copy ESP to EBP

    push ebp
    mov  ebp, esp
    
  2. 在栈上为局部变量分配空间

    栈自顶向下生长,故随着变量的分配,栈顶指针不断减小。

  3. 保存应有被调用方保存(callee-saved)的寄存器 —— 将他们压入栈。包括 EBXEDIESI

以上工作完成,就可以执行子过程的代码了。当子过程返回后,必须做以下工作:

  1. 将返回值保存在 EAX

  2. 恢复应由被调用方保存的寄存器(EDIESI) —— 从栈上 pop 出来

  3. 释放局部变量

  4. 恢复调用方 base pointer EBP —— 从栈上 pop 出来

  5. 最后,执行 ret,返回给调用方 (caller)

http://arthurchiao.art/blog/x86-asm-guide-zh/

http://www.cs.virginia.edu/~evans/cs216/guides/x86.html

 

以上是关于调用约定的主要内容,如果未能解决你的问题,请参考以下文章

java 循环变量

2018/10/03-函数调用约定cdeclstdcallfastcall- 《恶意代码分析实战》

DLL 调用约定错误

x86_64 调用约定和堆栈帧

为啥 Cdecl 调用在“标准”P/Invoke 约定中经常不匹配?

为啥 Cdecl 调用在“标准”P/Invoke 约定中经常不匹配?