汇编语言入门七:函数调用(一) 您所在的位置:网站首页 调用函数语句 汇编语言入门七:函数调用(一)

汇编语言入门七:函数调用(一)

#汇编语言入门七:函数调用(一)| 来源: 网络整理| 查看: 265

最近忙了一阵,好几天没更了,不好意思,我来晚了。

转入正题,当在汇编中进行函数调用,是一种什么样的体验?

想象

想象你在计算一个非常复杂的数学题,在算到一半的时候,你需要一个数据,而这个数据需要套用一个比较复杂的公式才能算出来,怎么办?

你不得不把手中的事情停下来,先去套公式、代入数值然后...最后,算出结果来了。

这时候你继续开始攻克这个困难题目的剩下部分。

用脑子想

刚刚说的这个过程,可能有点小问题,尤其是对脑子不太好使的人来说。想象你做题目做到一半的时候,记忆力已经有点不好使了,中间突然停下来去算一个复杂的公式,然后回来,诶?我刚刚算到哪了?我刚刚想到哪了?我刚刚算了些什么结果?

在你工作切换的时候,很容易回头来就忘记了刚刚做的部分事情。这时候,为了保证你套完复杂的公式,把结果拿回来继续算题目的时候不会出差错,你需要把刚才计算题目过程中的关键信息写在纸上。

用CPU想

刚刚去套用一个复杂的公式计算某个数据的情景,就类似在计算机里进行函数调用的情景。

程序需要一个结果,这个结果需要通过一个比较复杂的过程进行计算。这时候,编程人员会考虑将这个独立的复杂过程提取为单独的函数。

而在发生函数调用的时候,CPU就像是先暂停当前所做的事情,转去做那个复杂的计算,算完了之后又跳回来继续整个计算。就像你做题的过程中去套了一个公式计算数据一样。

但是在去套用公式之前,你需要做一些准备。首先,默默记下现在这个题目算到哪一步了,一会套完公式回来接着做;默默记下现在计算出来的一些结果,一会可能还会用到;套用公式需要些什么数据,先记下来,代公式的时候直接代入计算,算出来的结果也需要记在脑子里,回头需要使用。

在CPU里面,也需要这几个过程。

第一个,记下自己现在做事情做到哪里了,一会儿套完公式回来接着做,这也就是CPU在进行函数调用时的现场保存操作,CPU也需要记下自己当前执行到哪里了。

默默记下一些在套用公式的时候需要用到的数据,然后去套公式了。这也就是程序中在调用函数的时候进行参数传递的过程。

然后开始执行函数,等函数执行完了,就需要把结果记下来,回去继续刚才要用到数据的那个地方继续算。这也就是函数调用后返回的动作,这个记下的结果就是返回值。

开撸

说了那么多故事,那么函数调用要干些啥应该就说清楚了。总结一下大概就这么几个事:

保存现场(一会好回来接着做)传递参数(可选,套公式的时候需要些什么数据)返回(把计算结果带回来,接着刚才的事)

到这里,我们先来一个事例代码,就着代码去发现函数调用中的套路:

global main eax_plus_1s: add eax, 1 ret ebx_plus_1s: add ebx, 1 ret main: mov eax, 0 mov ebx, 0 call eax_plus_1s call eax_plus_1s call ebx_plus_1s add eax, ebx ret

首先,运行程序,得到结果:3。

上面的代码其实也比较简单,先从主干main这个地方梳理:

让eax和ebx的值都为0调用eax_plus_1s,再调用eax_plus_1s调用ebx_plus_1s执行eax = eax + ebx

上述的两个函数也非常简单,分别就是给eax和ebx加了1。所以,这个程序其实也就是换了个花样给寄存器增加1而已,纯粹演示。

这里出现了一个陌生指令call,这个指令是函数调用专用的指令,从程序的行为上看应该是让程序的执行流程发生跳转。前面说到了跳转指令jmp,这里是call,这两个指令都能让CPU的eip寄存器发生突然变化,然后程序就一下子跳到别的地方去了。但是这两个有区别:

很简单,jmp跳过去了就不知道怎么回来了,而通过call这种方式跳过去后,是可以通过ret指令直接回来的

那这是怎么做到的呢?

其实,在call指令执行的时候,CPU进行跳转之前还要做一个事情,就是把eip保存起来,然后往目标处跳。当遇到ret指令的时候,就把上一次call保存起来的eip恢复回来,我们知道eip直接决定了CPU会执行哪里的代码,当eip恢复的时候,就意味着程序又会到之前的位置了。

一个程序免不了有很多次call,那这些eip的值都是保存到哪里的呢?

有一个地方叫做“栈(stack)”,是程序启动之前,由操作系统指定的一片内存区域,每一次函数调用后的返回地址都存放在栈里面

好了,我们到这里,就明白了函数调用大概是怎么回事了。总结起来就是:

本质上也是跳转,但是跳到目标位置之前,需要保存“现在在哪里”的这个信息,也就是eip整个过程由一条指令call完成后面可以用ret指令跳转回来call指令保存eip的地方叫做栈,在内存里,ret指令执行的时候是直接取出栈中保存的eip值,并恢复回去达到返回的效果何为栈?

前面说到call指令会先保存eip的值到栈里面,然后就跳转到目标函数中去了。

这都好说,但是,如果是我在函数里面调用了一个函数,在这个函数里面又调用了一个函数,这个eip是怎么保存来保证每一次都能正确的跳回来呢?

好的,这个问题才是关键,这也说到了栈这样一个东西,我们先来设想一些场景,结合实际代码理解一下CPU所对应的栈。

首先,这个栈和数据结构中的栈是不一样的。数据结构中的栈是通过编程语言来形成程序执行逻辑上的栈。而这里的栈,是CPU内硬件实现的栈。当然了,两者在逻辑上都差不多的。

在这里,先回想一下数据结构中基于数组实现的栈。里面最关键的就是需要一个栈顶指针(或者是一个索引、下标),每次放东西入栈,就将指针后移,每一次从栈中取出东西来,就将指针前移。

到这里,我们先从逻辑上分析下CPU在发生函数调用的过程中是如何使用栈的。

假设现在程序处在一个叫做level1的位置,并调用了函数A,在调用的跳转发生之前,会将当前的eip保存起来,这时候,栈里面就是这样的:

----------


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有