函数调用的机器级表示

文章目录

  • 1.Call和ret指令
  • 2. 如何访问栈帧里面的数据
    • 为什么栈底放在上面,栈顶放在下面
    • X86中的寄存器
    • EBP、ESP寄存器
    • push 、pop 指令
    • mov 指令
    • 总结如何访问栈帧
  • 3. 如何切换栈帧
    • 函数调用时
    • 函数返回时
  • 4. 完整的函数调用过程
    • 1. 一个函数的栈帧内包含哪些内容
    • 2. 汇编代码实战
    • 3. 内容拓展
  • 5. 大总结

1.Call和ret指令

函数调用的机器级表示_第1张图片

除了main函数外,在栈底还会保存一些和硬件以及系统相关的其他的一些信息
调用函数的时候就压栈,
函数执行完成,return的时候就出栈
当前执行的函数永远都是在栈顶的位置!

函数调用的机器级表示_第2张图片

如何保证函数调用的时候能够过得去,还能回得来呢?

当CPU正在执行Call指令的时候,
IP寄存器应该是指向下一条mov指令
那么执行call指令的第一个作用就是把当前IP寄存器的值【mov指令的地址】给他压栈保存,把他压到栈顶的位置。
接下来会让IP寄存器指向新的位置【add函数的第一条指令】,同时add函数压栈
执行完add函数之后【return之前的指令】,add函数就会出栈,
接下来CPU执行add函数的return指令。
return指令的作用就是从函数函数栈中的顶部找到IP旧值,并且把他写到IP寄存器当中,这样的话,IP寄存器的值就会回到call指令的后一条指令

2. 如何访问栈帧里面的数据

函数调用的机器级表示_第3张图片

为什么栈底放在上面,栈顶放在下面

对应一个32的操作系统来说,系统会为每一个进程分配4GB的虚拟地址空间,也就是虚拟内存,地址的范围从0x0000 0000~0xFFFF FFFF,一共4GB,
高地址的1GB是操作系统的内核区
低地址的3GB可以由用户进程使用
那我们之前提到的函数调用栈的栈底在0xC000 0000这个位置,而栈顶是在地址变小的这个方向,
因此大多数情况下,会把栈底画在上面(高地址),栈顶画在下面(低地址)

在这3GB的用户区当中,除了用户栈(又名函数调用栈)之外,还会被划分成其他的区域,用于存储不同的数据

X86中的寄存器

函数调用的机器级表示_第4张图片
在X86的CPU里面,有这样一些常见的寄存器
其中EBP和ESP和我们的函数调用栈息息相关
注意在一个CPU内部,只有一个EBP和一个ESP

EBP、ESP寄存器

函数调用的机器级表示_第5张图片

push 、pop 指令

函数调用的机器级表示_第6张图片
函数调用的机器级表示_第7张图片函数调用的机器级表示_第8张图片函数调用的机器级表示_第9张图片

函数调用的机器级表示_第10张图片
函数调用的机器级表示_第11张图片

mov 指令

函数调用的机器级表示_第12张图片
函数调用的机器级表示_第13张图片
函数调用的机器级表示_第14张图片

总结如何访问栈帧

函数调用的机器级表示_第15张图片

3. 如何切换栈帧

函数调用的机器级表示_第16张图片

CPU内有有EBP和ESP两个寄存器
这两个寄存器标记了当前正在执行的函数他的栈帧的范围
当发生函数调用的时候,需要修改EBP和ESP的值
让他们指向新的被调用函数的底部和顶部
而且当被调用的函数执行完毕之后,
我们还需要把EBP和ESP重新让他指回上一层函数的栈帧

函数调用时

函数调用的机器级表示_第17张图片
函数调用的机器级表示_第18张图片函数调用的机器级表示_第19张图片

函数调用的机器级表示_第20张图片函数调用的机器级表示_第21张图片
函数调用的机器级表示_第22张图片

当CPU执行call这条指令的时候
IP寄存器应该指向下一条指令【mov】,
这时候需要push IP,把IP的值压入栈顶,也就是保存外层函数的下一条应该执行指令的地址。
同时,IP指向add函数的第一条指令
发现,add函数的第一条指令是push ebp,此时的EBP指向的还是上一层函数的栈底,这条指令会把上一层函数的栈底压栈。方便之后返回的时候重新指向上层函数的栈底
add函数的第二条指令是mov ebp,esp。意思是把ESP栈顶的值复制到EBP当中,换句话说就是让EBP指向当前这个函数【add函数】的基地址【0XA00F 0010】
我们发现每一个函数的栈帧,他的底部一定是存储了它上一层函数的基地址【add函数他的栈帧底部存储了caller函数的基地址0xA00F 0030】
这样做的好处就是,当一层函数执行完毕,要返回到上一层函数的之前,我们总能在当前函数的栈帧底部找到上一层函数的基地址,这样我们就可以恢复EBP寄存器的值。


接下来我们可以给这个新的函数栈帧进行一系列逻辑操作

函数返回时

函数调用的机器级表示_第23张图片函数调用的机器级表示_第24张图片
函数调用的机器级表示_第25张图片

函数调用的机器级表示_第26张图片函数调用的机器级表示_第27张图片

  1. 把EBP的值复制一份给ESP。换句话说就是让ESP和EBP都指向当前栈帧的底部
  2. pop ebp,把当前ESP所指的栈顶元素出栈,写入EBP中,也就是让EBP的值重新指回了栈帧底部0xA00F 0030这个位置。同时ESP+4,指向0xA00F 0014这个位置

return指令,让程序的执行流回到Call指令的后面这条指令,继续往后执行

4. 完整的函数调用过程

1. 一个函数的栈帧内包含哪些内容

函数调用的机器级表示_第28张图片函数调用的机器级表示_第29张图片

2. 汇编代码实战

函数调用的机器级表示_第30张图片函数调用的机器级表示_第31张图片
函数调用的机器级表示_第32张图片

在调用含参的方法之前,要把外层函数的两个参数放入外层函数栈帧的顶部位置

注意:mov指令不支持两个操作数都来自主存,所以要把temp2的值传到距离栈顶的倒数第二个位置,必须先把temp2放入当一个寄存器,再从寄存器中将数据写入到esp+4

函数调用的机器级表示_第33张图片

执行call add函数,IP指向下一条指令【mov】,这时先将IP的值压入栈中,然后IP指向add函数的第一条指令。

函数调用的机器级表示_第34张图片
函数调用的机器级表示_第35张图片add函数中如何访问到上层函数的两个参数?

caller函数中,已经把两个参数值存入到靠近栈顶的两个位置,访问这两个参数只需要用【ebp+8】访问第一个参数,【ebp+12】访问第二个参数

函数调用的机器级表示_第36张图片函数调用的机器级表示_第37张图片函数调用的机器级表示_第38张图片
函数调用的机器级表示_第39张图片函数调用的机器级表示_第40张图片

caller函数给上一层函数返回值的时候,把这个返回值写入到eax寄存器中,这样,上一层函数只需要从eax寄存器中取这个值就可以获得函数的返回结果了。

函数调用的机器级表示_第41张图片

假设在发生函数调用的时候,这个调用者他有一些运算结果被存在了一些寄存器当中,比如edx、ecx、eax。那么当函数调用发生的时候,被调用者也有可能使用到这几个寄存器,那就有可能吧调用者的这几个中间结果给覆盖掉,就有可能导致数据的丢失。那如何解决这个问题呢?

办法:如果有必要的话,在发起函数调用之前,我把我需要的这些寄存器值压栈保存,在函数调用返回之后,再把这些寄存器值从栈恢复到寄存器,这样就可以保证寄存器中的数据不会丢失

3. 内容拓展

函数调用的机器级表示_第42张图片
函数调用的机器级表示_第43张图片

5. 大总结

函数调用的机器级表示_第44张图片

你可能感兴趣的:(计算机组成原理,函数调用的机器级表示)