汇编语言(九)——程序模块

子程序结构

CALL和RET指令

主程序(调用程序)执行调用指令CALL调用子程序,子程序(被调用程序)执行返回指令RET返回主程序中CALL指令的下条指令处。

  1. CALL指令的功能:① 入栈返回地址;② 转移到目标地址。
  2. RET指令的功能:① 弹出返回地址;② 转移到返回地址。
  3. CALL和RET利用堆栈保存返回地址,保存到返回地址是CALL指令下一条指令的地址。
  4. 调用和返回由段内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 , 就作为下条要执行指令的地址 

子程序设计

编写方法与主程序一样,应有完整的注释。

  1. ret指令返回主程序,call指令调用子程序;
  2. 利用过程定义,获得子程序名和调用属性;
  3. 压入和弹出操作要成对使用,保持堆栈平衡;
  4. 子程序开始保护寄存器,返回前相应恢复;
  5. 安排在代码段的主程序之外;
  6. 子程序允许嵌套和递归。
过程定义伪指令

MASM利用过程定义伪指令获得子程序信息,根据存储模型等信息确定子程序的远近调用,并相应产生调用、返回指令。

; 过程名( procName )为符合语法的标识符
procName   proc   
        ...                 ; 过程体
procName   endp
子程序框架

过程定义 + 保护寄存器 + 堆栈平衡 + ret指令

; identifier 标识符
identifier  proc            ; 过程定义(子程序开始)
            push reg1       ; 保护寄存器
            push reg2
            ...             ; 子程序体
            pop reg2         恢复寄存器
            pop reg1
            ret             ; 子程序返回
identifier  endp            ; 过程(子程序)结束

参数传递

  1. 入口参数或输入参数:主程序提供给子程序一些数据;
  2. 出口参数或输出参数:子程序处理完数据返回给主程序的结果(返回值)。
  3. 参数内容:① 数据本身(传递数值);② 数据的存储地址(传递地址,传递引用)。
  4. 参数传递方法:① 通用寄存器;② 共享变量;③ 堆栈。
寄存器参数传递
  1. 最简单最常用的参数传递方法。
  2. 把参数存放于约定的寄存器(少量数据直接传递数值;大量数据只能传递地址)。
  3. 带有出口参数的寄存器不能保护和恢复。
  4. 带有入口参数的寄存器可以保护、也可以不保护,但最好保持一致。
共享变量传递参数
  1. 子程序和主程序使用同一个变量名存储数据,对应高级语言的全局变量。
  2. 如果变量定义和使用不在同一个程序模块中,需要利用PUBLIC、EXTREN声明。
  3. 共享变量传递参数时,子程序的通用性较差。
  4. 共享变量传递参数适合在多个程序段间、尤其在不同的程序模块间传递数据。
堆栈传递参数
  1. 主程序将入口参数压入堆栈,子程序从堆栈中取出参数。
  2. 采用堆栈传递参数常是程式化的,子程序设置EBP等于当前ESP,利用EBP相对寻址访问堆栈中的参数。
  3. 出口参数通常不使用堆栈传递。

程序模块

程序分段、子程序实现了程序的模块化,在开发大型应用程序时,常使用多个源程序文件、目标代码模块等组成完整的程序,形成多模块程序结构。
MASM支持的多模块方法:① 源文件包含;② 模块连接;③ 子程序库;④ 库文件包含。

源文件包含和模块连接

源文件包含

大型源程序可以合理的分方在若干个文本文件中。

  1. 各种常量定义、声明语句等组织在包含文件(*.inc);

  2. 常用的或有价值的宏定义存放在宏定义文件(*.mac);

  3. 常用的子程序形成汇编语言源文件(*.asm);

  4. 任何文本文件;

  5. 使用源文件包含伪指令INCLUDE将指定文件内容插入主题源程序文件(指令形式:include 文件名)。

     ; 文件名: eg.0508.asm 主程序
             include io32.inc        ; 包含 I/O 库声明
             include eg0508.inc      ; 包含数据段
             .code                   ; 代码段,主程序
             ... 
             exit 0                  ; 主程序结束
             include eg0508s.asm     ; 包含子程序代码
             end start
    
模块连接

将子程序单独编写成为一个源程序文件,子程序源文件汇编形成目标模块obj文件,连接时输入子程序目标模块文件名。

  1. 使用共用伪指令PUBLIC和外部伪指令EXTERN声明;
    定义标识符的模块使用指令:public 标识符 [, 标识符 …]
    调用标识符的模块使用指令:extern 标识符 : 类型 [, 标识符 : 类型 …]

  2. 子程序在代码段,与主程序文件采用相同的存储模型;

  3. 没有开始执行和结束执行点,但有汇编结束语句;

  4. 处理好子程序与主程序之间的参数传递问题。

     ; 文件名: 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
    

子程序库和库文件包含

子程序库

子程序库是子程序模块的几何,便于统一管理子程序。

  1. 编写存入库文件的子程序(遵循更加严格的子程序模块要求,遵循一致的规则)。

  2. 子程序文件编写完成、汇编形成目标模块obj文件;

  3. 利用库管理工具程序把子程序模块加入到子程序库。

  4. 在连接主程序时提供子程序库文件名。

     ; 操作过程
     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 文件名 ; 使用库文件中的子程序。

  1. 将子程序源文件汇编、模块文件加入子程序库;

     ; 操作过程
     ML /c /coff eg0508es.asm
     LIB32 /OUT:eg0508.lib eg0508es.obj
    
  2. 源文件中用库文件包含伪指令INCLUDELIB声明;

  3. 正常对主程序汇编、连接,无需在连接时输入库文件名。使编写主程序、子程序更加独立,子程序使用更方便。

     ; 操作过程
     MAKE32 eg0508e
    

宏汇编

  1. 宏(Macro)是具有宏名的一段汇编语句序列,需要先使用MACRO / / /ENDM伪指令进行定义,然后再程序中使用宏名(带参数)进行宏调用。

    ; 宏定义
    macroName   macro [parameter]       ; 形参 parameter
                ...                     ; 宏定义体
                endm
    
  2. 源程序进行汇编时,宏名被汇编程序用宏定义的代码序列替代,实现宏展开,这个过程就是宏汇编。

     ; 宏定义
     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
    
  3. 宏需要先定义后使用,不需要在任何段中,常书写与源程序开始位置,常用的宏定义可以单独写成一个宏定义文件。

  4. 宏定义中修改了寄存器内容,最好进行保护和恢复。

  5. 宏定义的参数灵活,允许嵌套和递归调用。

  6. 宏调用不需要控制的转移和返回,宏调用将相应的语句序列复制到宏指令的位置,宏展开被嵌入源程序成为一体。

宏与子程序-简化程序

宏是源程序级的简化,宏调用不需要返回,不减小目标程序,执行速度没有改变;子程序是源程序以及目标程序级的简化,子程序调用在执行时有CALL指令转向,RET指令返回,形成的目标代码较短,执行速度减慢。

宏与子程序-参数传递

宏通过形参、实参结合实现参数传递,使用软件方法,传递出错多为语法错误,易于发现。子程序利用寄存器、存储单元或堆栈等实现,运用硬件本#### 宏与子程序-选用原则
当程序较短或要求较快执行时,选用宏,宏常依附于源程序,适合全局性预处理;当程序段较长或为减小目标代码时,选用子程序,子程序更具有独立性,可以分别编写。

参考

中国大学MOOC汇编语言程序设计

  • 3
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值