汇编(五)堆栈平衡和ESP、EBP寻址
前言
本文汇总五个重点,为了学起来更加方便我将他们按照递进关系重新排列。分别是:
函数、传参、ESP寻址、堆栈平衡、EBP寻址。
看完本文就能感受到我这么安排的用意。
环境
为了更加深入理解,先假设你接到了一项任务:用汇编语言实现一个两数相加求结果的功能,并且要多次使用。
函数
因为要多次使用该功能,所以我们要选择函数来解决。和c语言差不多,汇编的函数也是在一个“模块”中调用另一个“模块”。这只是笼统的介绍,下面演示一下实际操作:
构架模型
函数外部:
1、将第一个数存入寄存器;
2、将第二个数存入另一个寄存器;
函数内部:
3、将他们两个相加,返回值会覆盖第一个寄存器的数据。
4、将返回值复制到EAX寄存器(为了规范,返回值默认存入EAX)
编写代码:
1 | CODE |
F8单步执行,观察寄存器数值变化。
执行完结果应如图所示:
这里为了直观,进行了简写,将函数外部的赋值也加了进来,实际上只有3、4实在函数里的;
函数内部功能已经实现了,如果要调用这个函数使用CALL指令和JMP指令都可以,如果忘了这两个指令的区别,再去看一下我前面的博客。
1 | CODE |
相必很多人都发现了,假如要求二十个数的计算式,但是寄存器就这么几个,这种方法肯定就不适用了。
我们采用的这种方式,叫做寄存器传参,当然也有堆栈传参;
堆栈传参
ESP寻址
上面已经介绍了寄存器传参,接下来针对堆栈传参进行学习;
跳过模型构建直接来看我写好的代码:
1 | CODE |
运行结果如图所示,注意观察堆栈和寄存器:
上面所写的[ESP+4]等就是ESP寻址。
ESP的优点就是,使用起来很方便,很简单。但是缺点就是有时候会改变ESP的值,导致程序错误。
堆栈平衡
堆栈平衡:在函数执行前,堆栈应该是一致的,也就是说ESP的值是相同的。否 则会导致数据错误。
1、如果要返回父程序,则当我们在堆栈中进行堆栈操作的时候,一定要保证在RET这条指令之前,ESP指向的是我们压入栈中的地址。
2、如果通过堆栈传递参数了,那么在函数执行完毕后,要平衡参数导致的堆栈变化。
上一次提到堆栈平衡是在刚接触堆栈时,使用的MOV对堆栈操作,需要sub或者add来恢复ESP的值。之后引入了PUSH和POP指令以后,就默认堆栈平衡。
但是仔细看我们写出来的函数,F7单步执行观察可以发现执行完毕后压入堆栈中的1和3,也就是求和的两个数,还在里面。
虽然不影响程序,但是会占用更多地空间,浪费资源,这对一个程序员来说是最不能忍受的。
第一个解决方法,使用add指令:
1 | CODE |
同样,也可以使用RET返回父程序:
1 | CODE |
使用EBP寻址就可以解决这个问题;
EBP寻址
1 | CODE |
1、将EBP中的数据保存在ESP中,ESP向上移动。
2、这样我们就可以利用EBP进行寻址,从而避免了ESP数值收到影响。
3、结束时也要恢复EBP的值。
有些时候,我们不仅需要将数据压入栈中。比如在执行函数前我们需要对原有的寄存器数值进行备份。我还是示范一下吧,有一个坑,就是出栈要倒着来。
1 | CODE |
滴水的汇编差不多就剩下最后的JCC指令了,这就是个打基础的过程,学着学着猛的发现现在软件的汇编代码我多少能看懂一点了,虽然一整块的汇编还是搞不清他到底干了什么,但是随着不断做题不断联系,这都是很容易达到的。