将从edi开始的9个数量的地址全部改成0CCCCCCCCh。
多读几遍,你就读懂了,接下来验证一下:
执行该汇编代码之后,情况是这样的:
把刚才那块空间全部复制成cccccccc,现在可以解释上面的问题了:这块空间就是专门为main函数开辟的。
接下来执行的汇编语句(黄色箭头)易于理解,把0AC003H这个值存入到ecx寄存器中。
真正厉害的是接下来红色箭头指向的这一条汇编语句,请注意,在执行call指令的同时,
call指令会自动把下一条汇编代码的地址进行压栈! 如下图所示:
call指令在执行的同时就会做这件事情,把call指令的下一条指令的地址进行压栈!
那么这件事情到底有什么作用呢?
这里先把问题放这里
接下来我们按F11,
似乎此时发现了新大陆!
我们可能看不懂那些代码是什么意思,没关系,这不重要。
重要的是刚刚说的一句话:call指令在执行的时候会把它的下一条汇编指令的地址先进行压栈!
回到call指令那个地方
这里的调用指令,似乎可以理解成call指令调用内存区的已经建立好的函数。
再次点击F11,可以看到确确实实是在调用内存中已经建立好的函数。
在众多汇编代码中,真正重要的是这一句代码,与刚才的call指令形成一致,在ret就是return的意思,执行完这一句汇编代码之后,一定会出现的事情是:返回到call的下一条指令处。
点击F11,,可以看到真的回到了call的下一条指令的位置。
这就是代码的严谨的地方,不仅能出去调用函数,还会记住原来的位置并且回来。
接下来就是把a的值存入内存中:
看这两个地方,在执行了这条汇编代码之后,a被存入内存中了。
接下来就存b,然后接下来,就是在为Add函数的调用做准备工作了。
首先push ebp,对ebp寄存器进行压栈操作,为什么压栈前面已经讲过,压栈就是为了我们在开辟栈空间的时候,为了有效地记录栈空间的栈底地址而进行的操作。
与main函数的开辟如出一辙,接下来就是把esp的值给ebp,其实就相当于把ebp移动到esp的位置。
注意:在移动之前,进行的压栈操作,就是为了记录栈底空间的地址,以后调用函数结束后返回时可以找到该地址。
在main函数调用的时候也进行了压栈的操作。这些过程是相当严谨的。
接下来就是把esp的值-0CCh,就是为Add函数开辟了一块空间。
Add函数调用完成后,最重要的工作来了,如何销毁栈空间?
是这样销毁的:
pop有删除的意思,在这里是把edi弹出栈空间,然后再把edi的值赋给edi,总的来说就是弹出寄存器。
前面三个均是如此,但是最后一个弹出ebp,不知你是否还记得,我们在创建main函数和Add函数的时候,先是对ebp寄存器进行压栈的!
所以压栈的作用在这里就凸显出来了:
在弹出ebp寄存器的之后,会把ebp寄存器里面的值交给ebp。
也就是说:弹出ebp之后,ebp又记录了当时存在那个地方的值。
所以ebp就回到了之前存的栈底位置的地址。
这样Add函数的销毁就完成了。
因为一块函数栈帧空间,是由两个寄存器共同维护的。现在寄存器esp回去了,那么这块栈帧空间就会归还给操作系统。
同理,对于main函数也是如此。
总结:
每一次函数的调用,都会在栈区开辟一块空间,这块空间是为调用函数准备的,而在开辟的过程中,存在着许许多多的细节,动作,来保证整个过程的严谨性。
在创建栈帧的同时也考虑到调用完函数之后销毁的过程,整个逻辑是很清晰的。
阅读汇编代码,了解汇编指令在函数调用时发挥的作用对我们的帮助是很大的,相当于我们在修炼内功。