什么是栈

栈与普通数据结构所说的栈的概念是相似的,遵循后进先出原则。不同的是汇编中所说的栈是一个在内存中连续的保存数据的区域,也即是实际存在的内存区域,进栈和出栈遵循后进先出原则。

在x86架构中,栈是向下生长的,即栈顶指针小于栈底指针。

ESP

ESP是x86架构中用于保存当前栈顶位置的寄存器。更多详细内容请参阅参考资料[1]

下面的两对代码是相互等价的

入栈操作:

push eax
;修改栈顶指针
sub esp, 4 ; 由于是向下生长,所以esp - 4, 减去4是因为eax占4个字节
mov DWORD PTR SS:[ESP], eax ;放入esp指定的内存区域

出栈操作

pop eax
mov eax, dword ptr ss:[esp]
add esp, 4 ;理解同入栈,注意这两行代码顺序与入栈不同

清除栈顶数据

假如我们要清除栈顶的四个双字的数据,只需要修改ESP即可

add esp, 4 * 4  ; 一个双字占4个字节,共4个双字

EBP

栈的一个典型应用就是函数调用时的参数传递。ESP保存的是当前栈的栈顶指针,EBP保存的是当前stack frame的基址[2].

[3]所述,在可执行环境中函数经常以stack frame的形式来进行参数传递和函数局部变量的访问。stack frame的概念使得每一个子程序(在汇编中函数通常称为子程序)都能够拥有独立的栈空间。当函数被调用时,以当前esp所在位置为基址创建了stack frame,当前的esp就是stack frame的栈帧基址,在执行其他命令之前需要把栈基址保存在ebp当中。

值得注意的是栈帧的概念是逻辑上的概念,实际上并不存在。一个进程仍然只是拥有一个栈,只是为了方便子程序内部的使用而引入了栈帧的概念。

standard entry sequence

有关更多在子程序调用中如何使用栈帧概念进行子程序调用请参阅[3:1].

一般而言,在子程序中首先要执行下面一段代码:

push ebp    ;保存主调函数的栈帧基址
mov ebp, esp ;当前函数的栈帧基址
sub esp, X ;X表示函数中要用到的变量大小,用于分配空间

例如一个C程序的函数:

void MyFunction()
{
int a, b, c;
...
}

则对应汇编程序的进入代码为:

_MyFunction:
push ebp
mov ebp, esp
sub esp, 12 ;4 * 3, int 类型是dword

若对上面的代码有:

a = 10;
b = 5;
c = 2;

则对应的汇编为:

mov [ebp - 4], 10
mov [ebp - 8], 5
mov [ebp - 12],2

为什么保存ebp

为了更好的理解ebp,我们考虑下面带有参数的函数

vod MyFunction2(int x, int y, int z)
{
...
}

汇编代码如下:

_MyFunction2:
push ebp
mov ebp, esp
sub esp, 0; no local variables, most compilers will omit this line

当调用函数时MyFunction2(10,5, 2),在汇编中调用格式如下:

;通过栈进行参数传递
; 参数从右向左压入栈,这样第一个pop出来的数据即是第一个参数
push 2
push 5
push 10
call _MyFunction2

其中,call _MyFunction2等价于下列指令:

push eip + 2 ;return address is current address + size of two instructions
jmp _MyFunction2

进入到子程序之后就要执行entry sequence代码:

push ebp
mov ebp, esp
sub esp, X; X为局部变量需要的字节数目

此时在栈中的内容如下:

:    :
| 2 | [ebp + 16] (3rd function argument)
| 5 | [ebp + 12] (2nd argument)
| 10 | [ebp + 8] (1st argument)
| RA | [ebp + 4] (return address)
| FP | [ebp] (old ebp value)
| | [ebp - 4] (1st local variable)
: :
: :
| | [ebp - X] (esp - the current stack pointer. The use of push / pop is valid now)

就目前看来似乎并没有必要使用ebp,因为单单使用esp也能够解决问题,但是利用esp访问变量是不可靠的,因此需要ebp去访问变量,因此需要保存旧的ebp的值。

Standard Exit Sequence

standard exit sequence是用于撤销standard entry sequence的。

void MyFunction3(int x, int y, int z)
{
int a, b, c;
...
return;
}
_MyFunction3:
push ebp
mov ebp, esp
sub esp, 12 ; sizeof(a) + sizeof(b) + sizeof(c)
;x = [ebp + 8]
;y = [ebp + 12]
;z = [ebp + 16]
;a = [ebp - 4] = [esp + 8]
;b = [ebp - 8] = [esp + 4]
;c = [ebp - 12] = [esp]
mov esp, ebp ; 这一步是直接把栈顶指针指向保存返回地址的地方
; 直接消除了局部变量的影响
pop ebp
ret 12 ; sizeof(x) + sizeof(y) + sizeof(z)

参考资料


  1. x86 Disassembly/The Stack ↩︎

  2. What is between ESP and EBP? ↩︎

  3. x86 Disassembly/Functions and Stack Frames ↩︎ ↩︎

x86汇编之栈与子程序调用的更多相关文章

  1. 对X86汇编的理解与入门

    本文描述基本的32位X86汇编语言的一个子集,其中涉及汇编语言的最核心部分,包括寄存器结构,数据表示,基本的操作指令(包括数据传送指令.逻辑计算指令.算数运算指令),以及函数的调用规则.个人认为:在理 ...

  2. 寄存器理解 及 X86汇编入门

    本文整理自多材料源,感谢原址分享,请查看末尾Url I, 汇编语言分类: 汇编语言和CPU息息相关,但是不能把汇编语言完全等同于CPU的机器指令.不同架构的CPU指令并不相同,如x86,powerpc ...

  3. X86汇编概要

    来自:https://www.cnblogs.com/jiftle/p/8453106.html 本文翻译自:http://www.cs.virginia.edu/~evans/cs216/guide ...

  4. x86汇编之十(使用字符串)

    x86汇编之十(使用字符串) 转自网络,出处不详 一.传送字符串 Intel提供了完整的字符串传送指令,就像是MOV指令一样. 1.MOVS指令 1)movs指令格式 把字符串从一个位内存位置传送到另 ...

  5. x64汇编第三讲,64位调用约定与函数传参.

    目录 x64汇编第三讲,64位调用约定与函数传参. 一丶复习X86传参 二丶x64汇编 2.1汇编详解 x64汇编第三讲,64位调用约定与函数传参. 一丶复习X86传参 在x86下我们汇编的传参如下: ...

  6. x86汇编指令脚本虚拟机

    简介 这是一个可以直接解释执行从ida pro里面提取出来的x86汇编代码的虚拟机. 非常精简,整体架构上不能跟那些成熟的虚拟机相比,主要目标是够用.能用.轻量就行,如果觉得代码架构设计的不是很好的话 ...

  7. X86汇编——计算斐波那契数列程序(详细注释和流程图说明)

    X86汇编实现斐波那契数列 程序说明: 输入斐波那契数列的项数, 然后依次输出斐波那契数列, 输入的项数小于256且为数字, 计算的项数不能超过2^16次方, 输入失败是 不会回显数字 因为存结果是A ...

  8. C# inline-asm / 嵌入x86汇编

    C#可不可以嵌入汇编 可以 在我眼中C#作为一个介于中上层语言是不可能不可以 置入汇编代码的 为什么会被我认为中上层语言呢 从C#保留指针就可以看出 我知 道有很多人一定不会相信C#可以使用汇编代码 ...

  9. x86汇编利用int 16h中断实现伪多线程输入

    x86汇编利用int 16h中断实现伪多线程输入 我们都知道,如果想让一个程序,同时又干这个,又干那个,最好的办法就是多线程.这个在高级语言里面已经用烂了. 但是,DOS是只有单线程的.我如果想让程序 ...

随机推荐

  1. 查看oracle是否锁表以及解决方法

    Oracle数据库操作中,我们有时会用到锁表查询以及解锁和kill进程等操作,那么这些操作是怎么实现的呢?本文我们主要就介绍一下这部分内容.(1)锁表查询的代码有以下的形式: select count ...

  2. hdu3038加权(扩展)并查集

    题目链接:http://icpc.njust.edu.cn/Problem/Hdu/3038/ 参考博客: https://blog.csdn.net/weixin_44580710/article/ ...

  3. 第三周java实验报告

        实验三 Java基本程序设计(2) 实验时间 2018-9-13 第一部分:理论知识回顾 第一章 再次了解了java“白皮书”的关键术语,java的常见术语,对于大多数“白皮书”的关键术语依然 ...

  4. Nginx.pid打开失败以及失效的解决方案

    在启动nginx的时候报了如下的错误: 其意思是没有该文件或者是目录,通过查看之后发现确实没有该目录   cd /var/run/nginx 于是重新创建了这个文件,使用如下命令:   mkdir / ...

  5. ASP.NET Core 核心特性--宿主、启动、中间件

    宿主 public class Program { public static void Main(string[] args) { CreateHostBuilder(args).Build().R ...

  6. 贪心-最大相容区间-Maximum Number of Events That Can Be Attended

    2020-02-16 16:24:19 问题描述: 问题求解: 看起来就像是sort + 贪心,但是具体如何做呢? 实际上本题是最大相容区间的变种题,在最大相容区间里,我们按照结束时间对interva ...

  7. 基于Doc2vec训练句子向量

    目录 一.Doc2vec原理 二.代码实现 三.总结   一.Doc2vec原理 前文总结了Word2vec训练词向量的细节,讲解了一个词是如何通过word2vec模型训练出唯一的向量来表示的.那接着 ...

  8. 在Ngnix中配置支持Websocket

    使用SignalR实现Websocket实时数据传输时,前后端各自实现编码后,无法将Websocket调试通过.沮丧之时,负责配置网络代理的同事说,网络访问这块使用了Ngnix代理设置,可能是造成We ...

  9. Python库的安装方式

    Python库的安装方式 1.Python库的自定义安装——找到相应网站,下载安装 示例:pywin32库安装 .exe,直接双击,自动识别安装目录 安装就可以了. 载入成功 2.Python库的工具 ...

  10. 【Net】ABP框架学习之正面硬钢

    前言 本文介绍另一种学习ABP框架的方法,该方法为正面硬钢学习法... 我们不去官网下载模板,直接引用DLL,直接使用. WebApi项目创建 首先创建一个WebApi项目,结构如下. 然后Nuget ...