寄存器的保护与恢复
由于计算机的硬件 资源只有一套,当子程序修改了寄存器的内容后,返回到调用它的程序时,这些寄存器的内容也就不会是调用子程序前的内容。这样,子程序修改寄存器内容就可能 变成了调用它的副作用,这种副作用常常会导致调用程序的出错。为此,在编写子程序时,除了能对作为入口和出口参数的寄存器进行修改外,对其它寄存器的修改 对调用程序来说都要是透明的,也就是说,在调用子程序指令的前后,除了作为入口和出口参数的寄存器内容可以不同外,其它寄存器的内容要保持不变。有时,也 要求作为入口参数的寄存器内容保持不变。
在子程序中,保存和恢复寄存器内容的主要方法是:在子程序的开始把它所用到的寄存器压进栈,在返回前,再把它们弹出栈。这样编写的好处是该子程序可以被任何其它程序来调用。在调用指令前,不需要保存寄存器,在调用指令后,也无需恢复寄存器。
利用堆栈来保存和恢复寄存器内容方法的一般形式如下:
|
XXXXX |
PROC |
||
PUSH |
REG1 |
|||
… |
||||
PUSH |
REGn |
;把子程序要使用的寄存器压栈,REGi代表某个寄存器 |
||
… … … |
;子程序的处理功能语句 |
|||
POP |
REGn |
;把前面压栈的寄存器弹出,注意它们的次序 |
||
… |
||||
POP |
REG1 |
|||
RET |
||||
XXXXX |
ENDP |
例7.2就是一个在子程序中利用堆栈来保存和恢复寄存器内容的例子。利用堆栈来实现此项功能时,应注意以下几点:
、用堆栈保存和恢复寄存器的内容,要注意堆栈“先进后出”的操作特点;
、通常情况下不保护入口参数寄存器的内容,当然,也可以根据事先的约定而对它们加以保护;
、如果用寄存器带回子程序的处理结果,那么,这些寄存器就一定不能加以保护;
、整个子程序的执行几乎肯定要改变标志位,可用PUSHF和POPF来保护和恢复标志位,但一般在子程序中不保护标志位,除非有此特殊需要;
汇编入门(13讲)
时间:2009-5-18 19:08:52 点击:12
核心提示:7.5 子程序的完全定义在7.1节所给出的子程序定义格式是一个最基本的、最简单的定义格式,它不能为子程序提供更简洁的调用方式。在宏汇编MASM 6.11系统中,为微机汇编语言的子程序提供了更加丰富的定义方式。虽然子程序的这种定义方式显得稍微有点复杂,但它不仅为子程序的调用带来了极大的方 便,而且其调用...
7.5 子程序的完全定义
在7.1节所给出的子程序定义格式是一个最基本的、最简单的定义格式,它不能为子程序提供更简洁的调用方式。在宏汇编MASM 6.11系统中,为微机汇编语言的子程序提供了更加丰富的定义方式。
虽然子程序的这种定义方式显得稍微有点复杂,但它不仅为子程序的调用带来了极大的方便,而且其调用方式与高级语言中子程序的调用方式相一致,这就大大地降低了程序员熟练掌握它的难度。
7.5.1 子程序完全定义格式
|
子程序名 |
PROC |
[distance] [langtype] [visibility] [<prologuearg>] |
[USES 寄存器列表] [,参数[:数据类型]]... |
|||
[LOCAL varlist] |
|||
子程序的程序体 |
|||
子程序名 |
ENDP |
定义子程序时,可使用参数表来直接指明其所要的参数,但程序员必须先用.MODEL伪指令,或使用<langtype>参数来说明本子程序所使用的程序设计语言类型。
程序员在定义子程 序时,最好能象在高级语言(如:C/C )定义过程那样,先说明该子程序的原型(用伪指令PROTO),这样,在调用时,系统可以自动进行类型检查,也 可以使用更方便的调用伪指令INVOKE来调用该子程序。有关子程序的原型说明伪指令和调用伪指令在随后第7.5.8和7.5.9小节中加以介绍。
子程序通常用RET指令来结束其执行,也可用指令“RET n”来指明在结束子程序执行后从堆栈弹出n个字节。有关返回指令请参阅7.2.2节中的叙述。
汇编程序在处理子程序时能自动产生“起始”代码(PROLOGUE Code)和“结束”代码(EPILOGUE code)。这两段特殊的代码分别完成:在调用子程序时,能把传递给子程序的参数压栈,在子程序结束时能把先前压栈的参数弹出。有了这两段代码,程序员在调用子程序时就不用自行考虑子程序的参数传递问题。
若子程序用指令RETN、RETF或IRETF作为子程序的结束指令,那么,汇编程序将不生成“结束”代码。
程序员可以用自己定义宏来替代缺省的“起始”和“结束”的代码段。这种替代方法是使用伪指令:OPTION PROLOGUE和OPTION EPILOGUE。
若子程序没有参数、局部变量,没使用USES子句,也不会产生新的段或段组,那么,子程序是可以嵌套定义的。程序员也可以使用返回指令RETN和RETF来避免子程序的嵌套。
在子程序内部,可以在指令之前使用伪指令LOCAL来说明其局部变量,有关规定在随后的第7.5.10节中有详细的说明。
下面就来介绍该定义格式中各个说明项的作用。
7.5.2 子程序的位距
子程序的位距(Distance)有:Near、Far、Near16、Far16、Near32和Far32。
子程序位距描述符 告诉汇编程序该子程序是在本段之内(Near),还是在本段之外(Far)。Near和Far描述符表示使用当前的段规模(Segment Size),Near16、Far16、Near32和Far32描述符是告诉汇编程序忽略当前的段规模,而使用指定16位或32位的段规模。
若选用类型Near或Far,那么,汇编程序将根据当前段的规模来决定选用16位,还是32位的Near或Far。
若程序员不指定该选项,那么汇编程序将根据当前的存储模式(由.MODEL来决定)和处理机类型来决定子程序类型。若不使用伪指令.MODEL,那么,Near是缺省的类型。
7.5.3 子程序的语言类型
子程序语言类型 (Language Type)可以是任何一种有效的程序设计语句类型,由它来告诉汇编程序将使用什么样的标识符的命名风格、子程序的调用和返回约定。该语言类型说明可使汇编 语言程序与其它语言程序达到共享的目的。所有有效的语言类型及其书写规定如表7.1所列。
表7.1 语言类型及其书写规定
|
C |
SYSCALL |
STDCALL |
Basic |
Fortran |
Pascal |
前导下划线 |
X |
|
X |
|
|
|
字母大写化 |
|
|
|
X |
X |
X |
参数从左到右 |
|
|
|
X |
X |
X |
参数从右到左 |
X |
X |
X |
|
|
|
调用程序清空堆栈 |
X |
|
* |
|
|
|
保存指针寄存器BP |
|
|
|
X |
X |
X |
使用VARARG参数 |
X |
X |
X |
|
|
|
*若使用:VARARG参数,则调用程序清空堆栈,否则,被调用的子程序清空堆栈。
程序员可用另外三种方法来设置程序的语言类型:.MODEL、OPTION LANGTYPE:和命令行选项/Gx。若在程序和命令行中都说明了语言类型,那么,前者的说明优先。
另外,程序员也可用命令行选项/H来限定标识符的最大长度。
例如:
Pascal语言风格:OPTION LANGUAGE:PASCAL、/Gc
C语言风格:OPTION LANGUAGE:C、/Gd
7.5.4 子程序的可见性
子程序的可见性(Visibility)决定该子程序对其它模块是否可用。它共有三个属性值:PRIVATE、PUBLIC和EXPORT。
PUBLIC属性是子程序标准的缺省属性,但该缺省属性可以用伪指令OPTION PROC来修改。EXPORT属性意味着该子程序是一个“远”的、具有PUBLIC属性的子程序,并要求连接程序在生成可执行文件时把其入口地址放入导出入口地址表中。
例如:
OPTION PROC : PRIVATE ;说明子程序的可见性为:PRIVATE
OPTION PROC : EXPORT ;说明子程序的可见性为:EXPORT
7.5.5 子程序的起始和结束操作
当程序员想用自己定义的宏来替代缺省的“起始”和“结束”的代码段时,可用下列说明语句来实现:
OPTION PROLOGUE : MacroName1
OPTION EPILOGUE : MacroName2
PROLOGUE和EPILOGUE分别指定MacroName1和MacroName2为“起始”和“结束”代码段的宏名。
汇编程序对用户定义的宏MacroName1和MacroName2的形式有较严格的规定,要求宏的定义形式如下:
MacroName MACRO ProcName, flags, argbytes, localbytes, <reglist>, userparms:VARARG
该宏定义的每个参数都有详细的说明,感兴趣的读者可看有关技术资料或MASM 6.11中的帮助,详细的说明在此从略,但建议使用缺省的宏。
如果想取消当前指定的宏名,而恢复使用缺省的“起始”和“结束”代码段的宏名,那么,可用下列说明语句,即指定二个缺省的宏名PrologueDef和EpilogueDef。
OPTION PROLOGUE : PrologueDef
OPTION EPILOGUE : EpilogueDef
若程序员不要汇编程序自动产生“起始”和“结束”代码,则可用NONE来代替说明语句中的宏名,即:
OPTION PROLOGUE : NONE
OPTION EPILOGUE : NONE