前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >(十)汇编语言——CALL和RET指令

(十)汇编语言——CALL和RET指令

作者头像
小点点
发布2023-02-01 17:12:08
8610
发布2023-02-01 17:12:08
举报
文章被收录于专栏:小点点小点点

(十)汇编语言——CALL和RET指令

文章目录

相信大家肯定在C语言里面接触过函数这个概念,或者是一些高级语言里面的方法,那么汇编语言有没有这样类似的概念呢,答案是当然的,接下来就让我们来介绍一下汇编的模块化程序设计。

CALL指令

这个CALL指令呢,我们是第一次接触,它主要的作用就是调用子程序,实质上就是进行流程转移,而且实现转移的方法和jmp指令的原理相似。使用的格式就是call 标号,这个就类似于,把当前位置保存起来,去执行其他地方的代码,执行完了之后再回到原处去执行。就类似于我们的中断。

功能

首先将当前的IP压入栈中,然后转移到标号处执行指令。相当于下面的语句。实现的是段间转移,如果我们要实现段内转移呢?我们继续讲解。

  1. (sp)=(sp)–2
  2. ((ss)*16+(sp))=(IP)
  3. (IP)=(IP)+16位位移
push IP
jmp near ptr 标号
寄存器

而如果转移地址在寄存器里面的话,就有一点不一样了,但是和我们之前介绍的 jmp 指令类似的,我们具体看看吧!就相当于下面的语句。

push IP
jmp 16位寄存器
内存

转移地址在内存中的call指令其实也是类似的,我们给出相应的语句,相信大家可以很清楚的理解到。

;call word ptr 内存单元地址
push IP
jmp word ptr 内存单元地址

;calld word ptr 内存单元地址
push CS
push IP
jmp dword ptr 内存单元地址

段间转移

我们如果想要实现段间转移的话,使用的指令就是call far ptr 标号,这个时候,我们就会把CS和IP同时入栈保存起来。具体步骤如下所示:

  • (sp)=(sp)–2
  • ((ss)×16+(sp))=(CS)
  • (sp)=(sp)–2
  • ((ss)×16+(sp))=(IP)
  • (CS)=标号所在的段地址
  • (IP)=标号所在的偏移地址
push CS
push IP
jmp far ptr 标号

“call 标号”类似”jmp near ptr 标号”,对应的机器指令中为相对于当前IP的转移位移,而不是转移的目的地址,实现段内转移。而指令“call far ptr 标号”实现的是段间转移!

返回指令

我们这里主要介绍ret指令和retf指令。

ret

用栈中的数据,修改IP的内容,从而实现近转移,相当于 pop IP。

retf

用栈中的数据,修改CS和IP的内容,从而实现远转移;

实例

这个程序就是计算一下ax的平方,但是值得注意的就是,这里我们用到了栈段,因为我们的call 指令和ret指令需要入栈和出栈操作。

assume cs:code, ss:stack
stack segment
	db 8 dup(0)
	db 8 dup(0)
stack ends
code segment

start :mov ax, stack
	mov ss,ax
	mov sp,16
	mov ax,1000
	call s
	mov ax, 4c00h
	int 21h

  s:add ax,ax
	ret

code ends
end start

MUL指令

mul指令就是我们的乘法指令,我们之前介绍过除法div指令,我们来简单回顾一下。

被除数

AX

DX和AX

除数

8位内存或寄存器

16位内存或寄存器

AL

AX

余数

AH

DX

而接下来我们介绍的乘法指令与这个类似,我们来看看吧!

被乘数

AL

AX

除数

8位内存或寄存器

16位内存或寄存器

结果

AX

DX(高位)和AX(低位)

我们来看一个例子吧!计算100乘10和100乘10000。

;100*10
mov al,100
mov bl,10
mul bl

;100*10000
mov ax,100
mov bx,10000
mul bx

模块化程序设计

我们知道,在程序设计中,模块化设计是十分重要的,那么,在汇编语言中有没有这要的设计呢?聪明的小伙伴们应该想到了,调用我们刚刚介绍的CALL 指令和RET指令即可。但是呢,需要我们去解决两个问题,那就是参数和返回值的问题,我们来看一下这两个问题我们如何来解决。 在这之前,我们来看一个问题,就是根据提供的N,计算N的3次方。我们可以考虑用循环去做,但是我们现在选择使用模块化程序设计的方法去解决,具体解决办法如下:

寄存器

把数据存储到寄存器里面是一个解决办法,我们来看看具体的操作。

  1. 首先把参数放到 bx 中,即(bx)=N;
  2. 然后子程序中用多个mul指令计算N3;
  3. 最后将结果放到dx和ax中:(dx:ax)=N3;
assume cs:code
data segment
	dw 1,2,3,4,5,6,7,8
	dd 0,0,0,0,0,0,0,0
data ends

code segment
start:mov ax,data
	mov ds,ax
	mov si,0     ;指向第一组单词
	mov di,16    ;指向第二组单词
	
	mov cx,8
  s:mov bx,[si]
	call cube
	mov [di],ax
	mov [di].2,dx
	add si,2
	add di,4
	loop s        ;循环处理
	
	mov ax,4c00h
	int 21h

cube:mov ax,bx
	mul bx
	mul bx
	ret

code ends
end start

这样处理当然是没有问题的,但是会面临一个问题,就是我们的寄存器只有那么多,如果需要传递的东西比较多,寄存器不够用了怎么办,确实这是我们会遇到的问题。既然寄存器比较少,我们就换一个方法,使用内存单元去传递信息。

内存单元

为了避免寄存器不够的尴尬,于是我们现在使用内存单元去传递数据。我们的做法就是先将批量数据放到内存中,然后将它们所在内存空间的首地址放在寄存器中,传递给需要的子程序。对于具有批量数据的返回结果,也可用同样的方法。

比如我们看这个例子,将data段中的字符串转化为大写。

data segment
	db 'conversation'
data ends

我们来看一下具体的代码段怎么去写。

code segment
start:mov ax,data
	  mov ds,ax
	  mov si,0
  	  mov cx,12;循环次数
	  call capital
	  mov ax,4c00h
	  int 21h
capital: and byte ptr [si],11011111b;变大写
	  inc si
	  loop capital
	  ret
code ends

目前这样基本上是没有什么问题了。但是我们还要介绍另外一种方法,那就是通过我们的栈来实现传递参数。

接下来我们就使用栈来进行参数传递,主要的原理就是由调用者将需要传递给子程序的参数压入栈中,子程序从栈中取得参数。同样的,我们介绍一个例子,那就是计算(a–b)3,a、b为word型数据。我们来看一下代码段部分。

code segment
start:
	mov ax,1
	push ax
	mov ax,3
	push ax
	;首先将一些数据入栈
	call difcube
	;调用子程序
	mov ax,4c00h
	int 21h
	
difcube:
	push bp
	;先在栈中保存BP的值
	mov bp,sp
	mov ax,[bp+4]
	sub ax,[bp+6]
	mov bp,ax
	mul bp
	mul bp
	;获得参数并计算,返回到AX中
	pop bp
	;恢复之前的BP值
	ret 4
	;放弃入栈的参数
code ends

好啦,这就是关于用栈来传递参数的方法,我们就介绍到这。

寄存器冲突问题

接下来我们将来解决有关寄存器冲突的问题,具体来说就是避免在子程序里面使用的寄存器与主程序里面使用的寄存器冲突了,导致程序无法运行。好啦,接下来我们就会来介绍如何解决这个问题。给大家一个提示,那就是使用我们经常使用的栈来保存数据。

方法

前面说了,我们是使用栈去解决这个问题,那么就让我们来卡看具体如何使用吧。 子程序开始:

  1. 子程序中使用的寄存器入栈
  2. 子程序内容
  3. 子程序使用的寄存器出栈
  4. 返回(ret、retf)
capital:
	push cx
	push si
change:
	mov cl,[si]
	mov ch,0
	jcxz ok
	and byte ptr [si],11011111b
	inc si
	jmp short change
ok:
	pop si
	pop cx
	ret

大家看这个地方,原理就是用栈先去保存数据,然后在程序结束的时候再把原来的数据出栈。 子程序中使用的寄存器入栈 2. 子程序内容 3. 子程序使用的寄存器出栈 4. 返回(ret、retf)

capital:
	push cx
	push si
change:
	mov cl,[si]
	mov ch,0
	jcxz ok
	and byte ptr [si],11011111b
	inc si
	jmp short change
ok:
	pop si
	pop cx
	ret

大家看这个地方,原理就是用栈先去保存数据,然后在程序结束的时候再把原来的数据出栈。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2023-01-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • (十)汇编语言——CALL和RET指令
    • 文章目录
      • CALL指令
        • 功能
        • 段间转移
      • 返回指令
        • ret
        • retf
      • 实例
        • MUL指令
          • 模块化程序设计
            • 寄存器
            • 内存单元
          • 寄存器冲突问题
            • 方法
        相关产品与服务
        数据保险箱
        数据保险箱(Cloud Data Coffer Service,CDCS)为您提供更高安全系数的企业核心数据存储服务。您可以通过自定义过期天数的方法删除数据,避免误删带来的损害,还可以将数据跨地域存储,防止一些不可抗因素导致的数据丢失。数据保险箱支持通过控制台、API 等多样化方式快速简单接入,实现海量数据的存储管理。您可以使用数据保险箱对文件数据进行上传、下载,最终实现数据的安全存储和提取。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档