汇编语言(九)——程序模块
子程序结构
CALL和RET指令
主程序(调用程序)执行调用指令CALL调用子程序,子程序(被调用程序)执行返回指令RET返回主程序中CALL指令的下条指令处。
- CALL指令的功能:① 入栈返回地址;② 转移到目标地址。
- RET指令的功能:① 弹出返回地址;② 转移到返回地址。
- CALL和RET利用堆栈保存返回地址,保存到返回地址是CALL指令下一条指令的地址。
- 调用和返回由段内near和段间far的区别,但CALL和RET指令助记符没有区别。
子程序调用指令CALL
CALL指令用于主程序中实现子程序的调用,分为段内调用(近调用near)和段间调用(远调用far),目标地址支持相对寻址、直接寻址或间接寻址(指令寻址)。
功能1:将下条指令的地址压入堆栈顶部;
功能2:转移到目标地址。
指令:call dest ; dest为目标地址。
call label ; 调用标号指定的子程序
call reg32/reg16 ; 调用寄存器指定地址的子程序
call mem48/mem32/mem16 ; 调用存储单元指定的子程序
; 段内调用
call label
next: ...
; 相当于如下两条指令
push next ; 入栈返回地址: esp = esp - 4, ss:[esp] = eip
jmp label ; 转移目标地址: eip = eip + 偏移量
子程序返回指令RET
RET指令在子程序结束时实现返回主程序,分为段内调用(近调用near)和段间调用(远调用far),目标地址支持相对寻址、直接寻址或间接寻址(指令寻址)。
功能1:从当前堆栈顶部弹出内容作为返回地址;
功能2:转移到返回地址。
指令:ret dest或ret ; dest为返回地址。
ret ; 无参数返回:出栈返回地址, esp
ret i16 ; 有参数返回:出栈返回地址, esp = esp + i16
; 段内返回
ret ; 栈顶数据出栈到指令寄存器 eip
; eip = ss:[eip], esp = esp + 4
; 数据进入 eip , 就作为下条要执行指令的地址
子程序设计
编写方法与主程序一样,应有完整的注释。
- ret指令返回主程序,call指令调用子程序;
- 利用过程定义,获得子程序名和调用属性;
- 压入和弹出操作要成对使用,保持堆栈平衡;
- 子程序开始保护寄存器,返回前相应恢复;
- 安排在代码段的主程序之外;
- 子程序允许嵌套和递归。
过程定义伪指令
MASM利用过程定义伪指令获得子程序信息,根据存储模型等信息确定子程序的远近调用,并相应产生调用、返回指令。
; 过程名( procName )为符合语法的标识符
procName proc
... ; 过程体
procName endp
子程序框架
过程定义 + 保护寄存器 + 堆栈平衡 + ret指令
; identifier 标识符
identifier proc ; 过程定义(子程序开始)
push reg1 ; 保护寄存器
push reg2
... ; 子程序体
pop reg2 恢复寄存器
pop reg1
ret ; 子程序返回
identifier endp ; 过程(子程序)结束
参数传递
- 入口参数或输入参数:主程序提供给子程序一些数据;
- 出口参数或输出参数:子程序处理完数据返回给主程序的结果(返回值)。
- 参数内容:① 数据本身(传递数值);② 数据的存储地址(传递地址,传递引用)。
- 参数传递方法:① 通用寄存器;② 共享变量;③ 堆栈。
寄存器参数传递
- 最简单最常用的参数传递方法。
- 把参数存放于约定的寄存器(少量数据直接传递数值;大量数据只能传递地址)。
- 带有出口参数的寄存器不能保护和恢复。
- 带有入口参数的寄存器可以保护、也可以不保护,但最好保持一致。
共享变量传递参数
- 子程序和主程序使用同一个变量名存储数据,对应高级语言的全局变量。
- 如果变量定义和使用不在同一个程序模块中,需要利用PUBLIC、EXTREN声明。
- 共享变量传递参数时,子程序的通用性较差。
- 共享变量传递参数适合在多个程序段间、尤其在不同的程序模块间传递数据。
堆栈传递参数
- 主程序将入口参数压入堆栈,子程序从堆栈中取出参数。
- 采用堆栈传递参数常是程式化的,子程序设置EBP等于当前ESP,利用EBP相对寻址访问堆栈中的参数。
- 出口参数通常不使用堆栈传递。
程序模块
程序分段、子程序实现了程序的模块化,在开发大型应用程序时,常使用多个源程序文件、目标代码模块等组成完整的程序,形成多模块程序结构。
MASM支持的多模块方法:① 源文件包含;② 模块连接;③ 子程序库;④ 库文件包含。
源文件包含和模块连接
源文件包含
大型源程序可以合理的分方在若干个文本文件中。
-
各种常量定义、声明语句等组织在包含文件(*.inc);
-
常用的或有价值的宏定义存放在宏定义文件(*.mac);
-
常用的子程序形成汇编语言源文件(*.asm);
-
任何文本文件;
-
使用源文件包含伪指令INCLUDE将指定文件内容插入主题源程序文件(指令形式:include 文件名)。
; 文件名: eg.0508.asm 主程序 include io32.inc ; 包含 I/O 库声明 include eg0508.inc ; 包含数据段 .code ; 代码段,主程序 ... exit 0 ; 主程序结束 include eg0508s.asm ; 包含子程序代码 end start
模块连接
将子程序单独编写成为一个源程序文件,子程序源文件汇编形成目标模块obj文件,连接时输入子程序目标模块文件名。
-
使用共用伪指令PUBLIC和外部伪指令EXTERN声明;
定义标识符的模块使用指令:public 标识符 [, 标识符 …]
调用标识符的模块使用指令:extern 标识符 : 类型 [, 标识符 : 类型 …] -
子程序在代码段,与主程序文件采用相同的存储模型;
-
没有开始执行和结束执行点,但有汇编结束语句;
-
处理好子程序与主程序之间的参数传递问题。
; 文件名: eg.0508es.asm 子程序 public rdhd, dphd, write ; 子程序共用 extern temp: dword ; 外部变量 rdhd proc ; 十六进制输入子程序 ... end ; 汇编结束 ; 文件名: eg.0508e.asm 主程序 ... temp dword ? ; 共享变量定义 extern rdhd: near, dphd: near, write:near ; 外部子程序 public temp ; 变量共用 .code ; 代码段,主程序 ... end start ; 操作过程 ML /c /coff eg0508e.asm ML /c /coff eg0508es.asm LINK32 /subsystem:console eg0508e.obj eg0508es.obj
子程序库和库文件包含
子程序库
子程序库是子程序模块的几何,便于统一管理子程序。
-
编写存入库文件的子程序(遵循更加严格的子程序模块要求,遵循一致的规则)。
-
子程序文件编写完成、汇编形成目标模块obj文件;
-
利用库管理工具程序把子程序模块加入到子程序库。
-
在连接主程序时提供子程序库文件名。
; 操作过程 ML /c /coff eg0508e.asm ML /c /coff eg0508es.asm LIB32 /OUT:eg0508.lib eg0508es.obj LINK32 /subsystem:console eg0508e.obj eg0508.lib
库文件包含
要使用已存入库文件的子程序,在主程序源文件中用库文件包含伪指令INCLUDELIB声明。
指令:includelib 文件名 ; 使用库文件中的子程序。
-
将子程序源文件汇编、模块文件加入子程序库;
; 操作过程 ML /c /coff eg0508es.asm LIB32 /OUT:eg0508.lib eg0508es.obj
-
源文件中用库文件包含伪指令INCLUDELIB声明;
-
正常对主程序汇编、连接,无需在连接时输入库文件名。使编写主程序、子程序更加独立,子程序使用更方便。
; 操作过程 MAKE32 eg0508e
宏汇编
-
宏(Macro)是具有宏名的一段汇编语句序列,需要先使用MACRO / / /ENDM伪指令进行定义,然后再程序中使用宏名(带参数)进行宏调用。
; 宏定义 macroName macro [parameter] ; 形参 parameter ... ; 宏定义体 endm
-
源程序进行汇编时,宏名被汇编程序用宏定义的代码序列替代,实现宏展开,这个过程就是宏汇编。
; 宏定义 WriteString macro msg push eax ; 保护寄存器 lea eax, msg ; msg 参数的偏移地址送给 eax call dispmsg pop eax endm ; 宏调用(宏指令) WriteString msg ; 宏展开 push eax lea eax, msg call dispmsg pop eax
-
宏需要先定义后使用,不需要在任何段中,常书写与源程序开始位置,常用的宏定义可以单独写成一个宏定义文件。
-
宏定义中修改了寄存器内容,最好进行保护和恢复。
-
宏定义的参数灵活,允许嵌套和递归调用。
-
宏调用不需要控制的转移和返回,宏调用将相应的语句序列复制到宏指令的位置,宏展开被嵌入源程序成为一体。
宏与子程序-简化程序
宏是源程序级的简化,宏调用不需要返回,不减小目标程序,执行速度没有改变;子程序是源程序以及目标程序级的简化,子程序调用在执行时有CALL指令转向,RET指令返回,形成的目标代码较短,执行速度减慢。
宏与子程序-参数传递
宏通过形参、实参结合实现参数传递,使用软件方法,传递出错多为语法错误,易于发现。子程序利用寄存器、存储单元或堆栈等实现,运用硬件本#### 宏与子程序-选用原则
当程序较短或要求较快执行时,选用宏,宏常依附于源程序,适合全局性预处理;当程序段较长或为减小目标代码时,选用子程序,子程序更具有独立性,可以分别编写。