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

发布于:2025-07-19 ⋅ 阅读:(15) ⋅ 点赞:(0)

函数参数传递

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

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

堆栈传递

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

使用堆栈的好处是不用污染寄存器,而且可以传递的参数个数基本不限。但缺点是需要读写内存。众所周知,读写内存比读写寄存器要慢的多,这就使人想到用寄存器进行传递参数会大大提高效率。在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 局部变量
0x00000010 bp 地址 push ebp
0x00000014 调用地址(function + 4) call function
0x00000018 实参(1) push 1
0x0000001c 实参(2) push 2
0x00000020
0x00000024

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


网站公告

今日签到

点亮在社区的每一天
去签到