2.12 汇编眼中的函数
为什么学习本节内容?因为我们要深刻理解汇编函数的形式以及函数调用的过程。
本节必须掌握的知识点:
掌握调用函数的两种形式
熟练运用函数调用
我们的程序是由成千上万行指令组合起来的,之前介绍了简单的指令,并没有把它们组合起来,接下来介绍把我们前面几节中介绍的汇编指令组合在一起实现具体的功能。在介绍实现具体功能之前先介绍一个知识点。这个知识点是函数。
2.12.1【函数】
相信大家对函数这个名词不会太陌生,汇编的世界中的函数是一系列指令的集合,为了完成某个重复使用的特定功能。我们用例子分析汇编眼中的函数是什么样子的。
例1:向寄存器中赋值
假如我们需要将1存到四个寄存器中,需要编写下面四行代码:
MOV EAX,1
MOV ECX,1
MOV EDX,1
MOV EBX,1
我们把以上四条指令看成一个整体,从某个角度来说,这四条指令就是一个函数,为了实现向寄存器中赋值的功能。
知道了函数是什么样子,具体怎么执行一个函数呢?执行函数有2种方式,执行函数也叫函数调用。
执行方式:
1、用JMP指令执行函数
2、用CALL指令执行函数
【JMP指令执行函数】
JMP指令是无条件跳转指令。
第一步:在DTDebug.exe软件中打开飞鸽软件,如图2-12-1所示。
第二步:输入例1中的函数,如图2-12-1所示。
看图2-12-2中,我们已经把函数写入到汇编窗口中,当前函数体开始的地址为0x77068E51,当前EIP寄存器存储的数据为0x77068E34,我们知道EIP寄存器是存储下一行将要执行指令的地址,我们怎么能让它直接运行到函数地址0x77068E51呢,那就是第三步要做的事情了。
第三步:JMP指令是无条件跳转指令,那么我们只需要用JMP指令跳到函数体开始的内存地址就可以了,输入JMP 0x77068E51,如图2-12-3所示。
第四步:按F8执行并观察数据变化,如图2-12-4所示。
看图2-12-4中所示,黑色定位光标显示在0x77068E51,EIP寄存器存储的数据由0x77068E34变为了0x77068E51说明已经跳转成功。
第五步:按F8执行并观察数据变化,看是否实现了函数想要做的事情,如图2-12-5所示。
图2-15-5中,已经实现了把0x00000001存入了相对应的寄存器中。
以上是JMP指令调用函数的步骤,对JMP指令调用函数的总结:我们调用函数时只要JMP函数体的地址,然后跳转到函数体开始的位置就可以了。
【CALL指令执行函数】
第一步:在DTDebug.exe软件中打开飞鸽软件,如图2-12-6所示
第二步:输入例1中的函数,如图2-12-7所示。
看图2-12-7中,汇编窗口中的黑色定位光标定位在0x77068E34,函数体开始的地址为0x77068E51,EIP存储的数据为0x77068E34。
第三步:用CALL指令调用函数,输入CALL 0x77068E51,当前汇编窗口中的黑色定位在0x77068E34,如图2-12-8所示。
第四步:按F7执行并观察数据变化。
看图2-12-9中所示,按F7执行后,汇编窗口中的黑色定位光标定位在了0x77068E51,也是函数体开始的内存地址,EIP存储的数据为0x77068E51,说明已经成功跳到函数体开始的内存地址,并将自己下一行指令的内存地址压入堆栈中。
第五步:按F8执行完函数,如图2-12-10所示。
看图2-12-10中,已经实现了把0x00000001存入了相对应的寄存器中。
如果我想接着从CALL指令的下一行开始执行怎么办哪?
可以用RETN指令,在执行CALL指令时已经将CALL指令下一行指令地址压入到了堆栈中。
第一步:输入RETN指令,如图2-12-11。
第二步:按F8执行并观察数据变化,如图2-12-12所示。
看图2-12-12,执行完RETN指令,可以总结CALL指令一共做了两件事:第一件事是修改EIP的值,第二件事是把它下一行的地址压入堆栈中。CALL始终把它下一行地址压入堆栈,这是一个相对值,保证了函数的可复用性。可以这样说,CALL就是为函数调用所生的。
为了更好的让大家理解函数体现是实现具体的功能,我们来编写一个例子
例:编写一个函数,实现任意两个整数相加。
实现:
第一步:在DTDebug.exe软件中打开飞鸽软件,如图2-12-13所示。
第二步:因为要实现任意2个整数相加,这里需要用到2个寄存器,假设这两个整数分别为1,2,这里1和2分别代表参数,则输入
MOV ECX 1
MOV EDX 2
这里是函数体,实现任意2个整数相加的函数
ADD ECX,EDX (ECX存储的数据+EDX存储的数据的值保存到ECX中)
MOV EAX,ECX (所得到的结果放到EAX中)
如图2-12-14所示
第三步:输入CALL指令,CALL 0x77068E51,如图2-12-15所示。
第四步:输入RETN指令,如图2-12-16。
以上是实现任意两个数的汇编函数编写,接下来我们验证是否能实现该功能。
验证:
第一步:按两次F8,将黑色光标定位到CALL指令那一行,当前ESP存储的数据为0x0019FFF0如图2-12-17所示。
看图2-12-17中,按两次F8执行完
MOV ECX,1
MOV EDX,2
把1移动到ECX寄存器的过程我们叫传递参数,把2移动到EDX寄存器的过程我们也是传递参数,
第二步:按F7执行,并观察数据变化,如图2-12-18所示。
图2-12-18汇编窗口中,黑色定位光标显示在了0x77068E51,该内存地址也是函数体开始的地方,且将自己下一行地址压入到堆栈中,ESP存储的数据由0x0019FFF0变为了0x0019FFEC,栈顶为0x0019FFEC存储的数据正是CALL指令下一行地址0x77068E43。
第三步:按F8两次运行到RETN指令处并观察数据变化,如图2-12-19。
图2-12-19汇编窗口中,黑色定位光标已经显示在了RETN指令那一行,寄存器窗口中,EAX也保存了ECX存储的数据加上EDX存储的数据,运行到这里,说明我们编写的程序正确实现了两个数相加的功能。
第四步:按F8执行并观察数据变化,如图2-12-20所示。
看图2-12-20汇编窗口中,F8执行完后,返回到了CALL指令下一行。
总结:在实现任意两个数相加函数时遇到了几个知识点,第一个是参数,第二个是调用函数,第三个是参数传递,第四个是返回值。
参数:看实现第二步中,
MOV ECX,1
MOV EDX,2
1和2就是例题的参数。
参数传递:看验证第一步中,执行完。
MOV ECX,1
MOV EDX,2
把1保存到ECX的过程叫参数传递,把2保存到EDX的过程也是参数传递。
调用函数:看验证第一步、第二步中,先将黑色定位光标定位到CALL指令那一行,CALL调用函数体开始的内存地址0x77068E51,按F7执行完后,执行到函数体开始的地方,整个过程叫函数调用。
返回值: 看验证第二步、第三步中,执行完。
ADD ECX,EDX
MOV EAX,ECX
把EDX存储的数据与ECX存储的数据相加的结果保存到ECX中,接着把ECX中保存的结果移动到EAX中。一般情况下EAX用来存储返回值的。
下一节介绍堆栈传参。
练习:
1、本节知识介绍了CALL指令能重复调用函数,那JMP指令可以重复调用函数吗?
2、编写一个函数,能够实现任意三个整数的加法运算。
3、如果要传递的参数比较多,比如10个,该如何实现呢?
---摘自本人拙著:编程达人内部教材《汇编、C语言基础教程》
1.《汇编如何给寄存器赋值》援引自互联网,旨在传递更多网络信息知识,仅代表作者本人观点,与本网站无关,侵删请联系页脚下方联系方式。
2.《汇编如何给寄存器赋值》仅供读者参考,本网站未对该内容进行证实,对其原创性、真实性、完整性、及时性不作任何保证。
3.文章转载时请保留本站内容来源地址,https://www.lu-xu.com/keji/3237135.html