当前位置: 首页 > news >正文

汇编 Call 指令运行原理详解:从跳转机制到堆栈操作

函数参数传递

参数传递一般有三种方式:

  • 通过内存(一般是堆栈)传递
  • 整形参数可以通过寄存器传递
  • 浮点数参数可以通过浮点寄存器传递

堆栈传递

所谓通过堆栈传递参数,就是调用函数的一方,将参数逐个压入堆栈中,然后由函数从堆栈中取出使用。

使用堆栈的好处是不用污染寄存器,而且可以传递的参数个数基本不限。但缺点是需要读写内存。众所周知,读写内存比读写寄存器要慢的多,这就使人想到用寄存器进行传递参数会大大提高效率。在windows 内核中,大多使用快读调用协议,第一个参数使用 ecx 传递,第二个参数使用 edx 传递,其他的参数继续使用堆栈。

而普通的 C 调用方式则是全部使用堆栈来进行参数传递的。

x64平台下,通过寄存器传参。前4个参数通过rcx、rdx、r8、r9传参,多余的参数通过栈从右向左入栈。

函数的返回

一般编译器都是使用 ret 或者 ret n 指令来返回。最早和最常见的指令是 ret,这个指令总是从对战中弹出一个地址,然后让处理器跳转到这个地址。这条指令说起来非常的简单,不过他也带来了一些缺点。ret 先要弹出堆栈中记录的函数返回地址,然后才能平衡堆栈。但实际情况是,先将参数入栈,然后call 函数时候保存的函数返回地址。这个时候就有另一条指令 ret n,表示先调回函数返回地址,然后在将 esp + n(平衡堆栈)。例如:ret 8,实际会从堆栈中弹出一个返回地址,还有8个字节的堆栈大小,总共是 8+ 4(32位机器)。

三种常见的调用约定

C 调用方式(__cdecl

用堆栈来传递参数。将参数倒序要入堆栈,最后由调用者来平衡堆栈的方式,被称为 C 调用。

标砖调用方式(__stdcall

参数传递方式同上,最后由函数本身来平衡堆栈的方式被称为标准调用(stdcall)。标准调用是 windows API 常见的调用方式。

快速调用(__fastcall

Windows 内核 32 位版本中,最常见的是快速调用(fastcall)方式。其特点是,ecx传递第一个参数,edx传递第二个参数,其他的参数依然用堆栈传递。堆栈平衡方式和 stdcall 相同(被调用方平衡堆栈)。

__thiscall

C++ 类方法调用方式,ecx 为this 指针的值。构造函数有返回值,就是 this 指针。

调用栈示例

c 语言示例代码如下:

int function(int x, int y) {return x + y;
}
int main(){function(1, 2);
}

相关的汇编示例代码如下:

; ... ...
; function(1, 2); 
push 0x02h		; 将参数 2 放入堆栈
push 0x01h		; 将参数 1 放入堆栈
call function	; 将 call function 下一句代码的地址放入堆栈相当于 push function + 4
add esp, 0x8h	; 将上面入栈的两个参数给丢弃,平衡堆栈
; ......; funciton(int x, int y) 内部代码
push ebp		; 将 ebp 进行备份
mov	ebp, esp	; 将 esp 备份到 ebp
sub esp, xxxx	; 函数局部变量开辟空间
; ... ... 相关操作
mov esp, ebp	; 将当前栈顶还原回当前栈基
pop ebp			; 还原栈基到上一个栈帧的栈基
ret				; 相当于 pop eip 操作,将栈内保存的函数后一个字节的地址放到 eip(下一条指令)

堆栈示意图:

地址数据汇编代码
0x00000004局部变量
0x00000008局部变量
0x0000000c局部变量
0x00000010bp 地址push ebp
0x00000014调用地址(function + 4)call function
0x00000018实参(1)push 1
0x0000001c实参(2)push 2
0x00000020
0x00000024

更多精彩文章请访问:技术那些事,更多精彩等着您!

http://www.lryc.cn/news/590201.html

相关文章:

  • 基于 elements3 包装的 可展开 table 组件
  • uniapp各端通过webview实现互相通信
  • 并发编程-CountDownLatch
  • UniApp 多端人脸认证图片上传实现
  • 【PTA数据结构 | C语言版】前缀树的3个操作
  • 关于程序=数据结构+算法这句话最近的一些思考
  • 数字ic后端设计从入门到精通11(含fusion compiler, tcl教学)全定制设计入门
  • Java-数构链表
  • golang语法-----指针
  • 笔试——Day10
  • 简单易懂,什么是连续分配管理方式
  • Qt 将触摸事件转换为鼠标事件(Qt4和Qt5及以上版本)
  • Java线程创建与运行全解析
  • DuckDB 高效导入 IPv6 地址数据的实践与性能对比
  • #Datawhale组队学习#7月-强化学习Task1
  • java解析word文档
  • 使用JS编写一个购物车界面
  • C++ 面向对象
  • 第2章通用的高并发架构设计——2.3 高并发读场景方案2:本地缓存
  • 开源 python 应用 开发(七)数据可视化
  • Linux 定时器应用示例(修正版)
  • GIT版本回退
  • Python中可以反转的数据类型
  • GaussDB 数据库架构师修炼(五) 存储容量评估
  • 搜索框的显示与隐藏(展开与收起)
  • el-input 回显怎么用符号¥和变量拼接展示?
  • openEuler 22.03 LTS Rootless Docker 安装指南
  • MongoDB复杂查询 聚合框架
  • 洛谷 P11247 [GESP202409 六级] 算法学习-普及/提高-
  • pymongo库:简易方式存取数据