汇编语言子程序结构

Posted BkbK-

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了汇编语言子程序结构相关的知识,希望对你有一定的参考价值。

子程序结构

文章目录

一、子程序指令

(1)子程序调用指令CALL

CALL指令用在主程序中,实现子程序的调用子程序和主程序可以在同一个代码段内,也可以在不同段内。因而,与无条件转移指令 JMP 类似,子程序调用指令 CALL 也可以分成段内调用(近调用)和段间调用(远调用),同时, CALL指令的目标地址也可以采用相对寻址、直接寻址或间接寻址。

1.子程序调用指令CALL的功能

CALL指令用在主程序中,实现子程序的调用

  • 功能1:将下条指令的地址压入堆栈(顶部)
  • 功能2:转移到目标地址

CALL 指令实际上就是入栈指令 PUSH 和转移指令 JMP 的组合

2.子程序调用指令CALL的使用方法

  • 调用标号指定的子程序

    CALL label ;调用标号指定的子程序
    
  • 调用寄存器指定地址的子程序

    CALL reg32/reg16 ;调用寄存器指定地址的子程序
    
  • 调用存储单元指定地址的子程序

    CALL mem48/mem32/mem16 ;调用存储单元指定地址的子程序
    

3.子程序调用指令CALL的分类

CALL分成段内调用(近调用)和段间调用(远调用)目标地址支持相对寻址、直接寻址或间接寻址

(2)子程序返回指令RET

RET指令用在子程序结束,实现返回主程序

  • 功能1:从当前堆栈顶部弹出内容作为返回地址
  • 功能2:转移到返回地址
RET 

无参数返回:出栈返回地址

RET i16 

有参数返回:出栈返回地址,ESP←ESP+i16

(3)过程定义伪指令

过程名为符合语法的标识符

过程名 proc	;过程定义
	...		;过程体
过程名 endp	;过程结束

MASM会根据存储模型等信息确定子程序的远近调用,并相应产生调用、返回指令

  • 子程序框架

    标识符 proc ;过程定义(子程序开始)
    push ...1 ;保护寄存器
    push ...2
    … ;子程序体
    pop ...2 ;恢复寄存器
    pop ...1
    ret ;子程序返回
    标识符 endp ;过程(子程序)结束
    
    • RET指令返回主程序,CALL指令调用子程序
    • 利用过程定义,获得子程序名和调用属性
    • 压入和弹出操作要成对使用,保持堆栈平衡
    • 子程序开始保护寄存器,返回前相应恢复
    • 安排在代码段的主程序之外
    • 子程序允许嵌套和递归

二、参数传递

  • 主程序与子程序间通过参数传递建立联系
    ►入口参数(输入参数):主程序→子程序
    ►出口参数(输出参数):子程序→主程序
  • 参数的具体内容
    ►数据本身(传递数值)
    ►数据的存储地址(传递地址,传递引用)

(1)寄存器传递参数

  • 最简单和常用的参数传递方法
  • 把参数存于约定的寄存器
    ►少量数据直接传递数值
    ►大量数据只能传递地址
  • 带有出口参数的寄存器不能保护和恢复
  • 带有入口参数的寄存器可以保护、也可以不保护,但最好能够保持一致

十六进制显示:

INCLUDE io32.inc

	.data;数据段
regd byte 'EAX=',8 dup(0),'H',0
	.code

start:;代码段,主程序
		mov eax,1234abcdh ;假设一个数据
		xor ebx,ebx ;相对寻址访问字符串
		mov ecx,8 ;8位十六进制数
again: 	rol eax,4 ;高4位循环移位进入低4位
		push eax;也可以用mov edx,eax
		call htoasc ;调用子程序htoasc
		mov regd+4[ebx],al ;保存转换后的ASCII码
		pop eax ;也可以用moveax,edx
		inc ebx
		loop again
		mov eax,offset regd
		call dispmsg ;显示
		exit 0

;代码段,子程序
htoasc proc
		and al,0fh ;只取AL的低4位
		or al,30h ;AL高4位变成3
		cmp al,39h ;是0~9,还是A~F
		jbe htoend
		add al,7 ;是A~F,ASCII码再加上7
htoend: ret ;子程序返回
htoasc endp

end start

(2)共享变量传递参数

共享变量对应高级语言的全局变量

  • 子程序和主程序使用同一个变量名存取数据
  • 如果变量定义和使用不在同一个程序模块中,需要利用PUBLICEXTREN声明
  • 共享变量传递参数,子程序的通用性较差
  • 特别适合在多个程序段间、尤其在不同的程序模块间传递数据

二进制输入子程序:

;代码段,子程序:输入32位二进制数
rdbd proc ;出口参数:共享变量TEMP
		push eax ;寄存器保护
		push ebx
		push ecx
rdbd1: 	xor ebx,ebx ;EBX用于存放二进制结果
		mov ecx,32 ;限制输入字符的个数
rdbd2: 	call readc ;输入一个字符
		cmp al,'0' ;检测键入字符是否合法
		jb rderr ;不合法则转到出错处理
		cmp al,'1'
		ja rderr
		sub al,'0' ;对输入的字符进行转化
		shl ebx,1 ;EBX的值乘以2(左移1位)
		or bl,al ;BL和AL相加(或)
		loop rdbd2 ;循环输入字符
		mov temp,ebx ;把二进制结果存放TEMP返回
		call dispcrlf ;分行
		pop ecx
		pop ebx
		pop eax
		ret
rderr: mov eax,offset errmsg ;显示错误信息
		call dispmsg
		jmp rdbd1 ;重新输入
errmsg byte 0dh,0ah,'Input error, enter again: ',0
rdbd endp

(3)堆栈传递参数

  • 主程序将入口参数压入堆栈
    ►子程序从堆栈中取出参数
  • 采用堆栈传递参数常是程式化的
    ►子程序设置EBP等于当前ESP
    ►利用EBP相对寻址访问堆栈中的参数
  • 出口参数通常不使用堆栈传递

采用堆栈传递参数可以程式化
• 子程序设置EBP等于当前ESP
• 利用EBP相对寻址访问堆栈中的参数

计算有符号数平均值子程序:

INCLUDE io32.inc
.data
;数据段
array dword 675,354,-34,198,267,0,9,2371,-67,4257
.code
;代码段,主程序
		push lengthof array ;压入数据个数(4个字节)
		push offset array ;压入数组地址(4个字节)
		call mean ;调用求平均值子程序
		add esp,8 ;主程序平衡堆栈(弹出8个字节)
		call dispsid ;显示平均值EAX(出口参数)
;代码段,子程序:计算32位有符号数平均值
mean proc ;入口参数:顺序压入个数和地址
		push ebp ;出口参数:EAX=平均值
		mov ebp,esp
		push ebx ;保护寄存器
		push ecx
		push edx
		mov ebx,[ebp+8] ;EBX=取出的数组地址
		mov ecx,[ebp+12] ;ECX=取出的数据个数
		xor eax,eax ;EAX保存和值
		xor edx,edx ;EDX=指向数组元素
mean1: add eax,[ebx+edx*4] ;求和
		add edx,1 ;指向下一个数据
		cmp edx,ecx ;比较个数
		jb mean1 ;增量计数循环
		cdq ;将累加和EAX符号扩展到EDX
		;将EAX最高位填入EDX所有位
		idiv ecx ;有符号数除法,EAX=平均值
		pop edx ;恢复寄存器
		pop ecx
		pop ebx
		pop ebp
		ret 8;子程序平衡堆栈
mean endp

以上是关于汇编语言子程序结构的主要内容,如果未能解决你的问题,请参考以下文章

汇编语言中断及外部设备操作篇--06

shell 参数与逻辑结构语句

02程序结构

汇编语言-其他转移指令CALL

《C程序设计语言》笔记 (十三) 参考手册5

GNU ARM 汇编基础笔记