《汇编语言》第十章 CALL和RET指令

本文详细介绍了汇编语言中的CALL和RET指令,包括它们在转移控制中的作用,如ret用于近转移,retf用于远转移,以及各种形式的call指令,如根据位移、内存地址和寄存器的转移。此外,还讨论了call和ret如何配合实现子程序机制,以及在模块化程序设计中的应用。同时,提到了mul乘法指令和参数、结果的传递问题,以及批量数据处理和寄存器冲突的解决方案。

# 第10章 CALL和RET指令

10.1 ret和retf

ret指令用栈中的数据,修改IP的内容,从而实现近转移。
retf指令用栈中的数据,修改CS和IP内容,从而实现远转移。

CPU执行ret指令时,进行下面两步操作:

  1. (IP)=((SS)*16+(SP))
  2. (SP)=(SP)+2

这是将栈顶的数据存入IP,然后将该数据进行出栈操作。

CPU执行retf指令时,进行下面四步操作:

  1. (IP)=((SS)*16+(SP))
  2. (SP)=(SP)+2
  3. (CS)=((SS)*16+(SP))
  4. (SP)=(SP)+2

通过上面可以看出,段地址存储在高位,故要先存储CS,之后在存储IP,这种操作不要忘记。

例子:

push cs
push ax
retf 

执行完毕后,程序指向 CS:[ax]的地址,先存入段地址,之后存入偏移地址,这种操作你应该理解。

10.2 call指令

CPU执行call指令时,进行两步操作:

  1. 将当前的IP或CS和IP压入栈中;
  2. 转移。

call指令不能实现短转移,除此之外,和jmp指令的原理相同。

10.3 根据位移进行转移的call指令

call 标号(将call指令的下一条指令压入栈后,转到标号处之行指令)

还记得CPU读取指令的顺序吗?读取一条指令进入指令缓冲区后,IP立刻指向下一个指令。所以,在缓冲区的call指令执行存储IP时,是指向下一条指令而不是本身指令,这个概念应该理解。
另外,call指令就相当于完成上面所讲的入栈操作。

CPU执行call指令时,进行如下的操作:

  1. (sp)=(sp)-2 |((SS)*16+(SP))=(IP)
  2. (IP)=(IP)+16位位移

16位位移由编译程序算出。

相当于:

push IP
jmp near ptr 标号

10.4 转移的目的地址在指令中的call指令

call far ptr 标号

相当于:

push CS
push IP
jmp far ptr 标号

10.5 转移地址在寄存器中的call指令

call 16位reg

注意,一个寄存器大小为16位,一个字,仅能代表一个地址,所以,这里自然就是偏移地址了。

push IP
jmp 16位 reg

10.6 转移地址在内存中的call指令

转移地址在内存中的call指令有两种格式:

  1. call word ptr 内存单元地址
  2. call dword ptr 内存单元地址

这个的区别一目了然。
前者针对偏移地址,而后者针对目的地址和偏移地址。

后者相当于:

push cs
push ip
jmp dword ptr 内存单元

其中根据栈的规则,必须先存放CS,这个概念是你要理解的。

举个例子:

mov sp,0h
mov ax,0123h
mov ds:[0],ax ;低位,这个是IP地址。
mov word ptr ds:[2],0 ;高位,这个是段地址。
call dword ptr ds:[0]

我们应该着重去理解其内存中有如何向内存中写入地址,很简单,就是手动写入,然后声明地址的开头,程序就会自动读取,就是这个样子。

10.7 call和ret的配合使用

我们可以通过call和ret指令,来实现子程序的机制

举个例子:

start:  mov ax,1
        mov cx,3
        call s
        mov bx,ax
        mov ax,4c00h
        int 21h
    s:  add ax,ax
        loop s
        ret

通过这个程序,可以看出,这里使用call 标号的指令,这里的标号可以直接使用段标记。

这里进入子程序s,然后算其三次方,结果存储在ax中,最后ret返回到call下一条指令。

这些逻辑顺序其实是很好理解的。

10.8 mul指令

介绍一下mul指令,mul是乘法指令,使用mul指令时,注意以下两点:

  1. 两个相乘的数,要么都是8位,要么都是16位。如果时8位,一个默认放在AL中,另一个放在8位reg或内存字节单元中;如果是16位,一个默认在AX中,另一个放在16位reg或内存字单元中。
  2. 结果:如果是8位乘法,结果默认放在AX中;如果是16位乘法,结果高位默认放在DX中,低位放在AX中。

格式如下:
mul reg
mul 内存单元

内存单元可以用不同的寻址方式给出,比如:
mul byte ptr ds:[0]

10.9 模块化程序设计

从上面我们看到,call和ret指令共同支持了汇编语言编程中的模块化设计。在实际编程中,程序的模块化是比不可少的。因为实现的问题比较复杂,对现实问题进行分析时,把它转化为相互联系,不同层次的子问题,是必须解决的办法,而利用call和ret指令则是很好的解决方法

10.10 参数和结果传递的问题

这里设计子程序传递参数时存在的两个问题:

  1. 将参数N存储在什么地方?
  2. 计算得到的值,存储在什么地方?

用寄存器来存储参数和结果是最常使用的方法。

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

        cube: mov 

往内存中写,其实就是用mov指令,这里还有点“恐惧”,其实就是这点样子,你随便指明个内存单元,然后下标的形式来表示出相对地址,后面是要存储的数据。

其中,si有数据,所以指向栈界。而di无数据,指向栈顶。

在建立相关内存空间时,利用 关键字 dd,dw之类的就能很好的建立出你所想要建立的内存。

而往内存中写入结果,则利用[di]就行,[di].2这种相对地址的给出办法,你不用在去改变di的值,在最后地址改变两个字,即4位,这种程序的执行方式其实很好理解的。

10.11 批量数据的传递

寄存器的数量终究是有限的,当数据量过大,显然不能放在寄存器中存取,这是,你可以利用[di],[si]这种来表示内存地址,直接在内存中进行修改。

对于内存地址的修改,我们似乎不再那么恐惧,我们可以使用[di]的形式来指向内存,然后对di加法运算之类的来更好的。我们需要明确的就是开始让(di)==0,这种思路就会很好理解的。

举个例子:将data中的字符串转化为大写

assume cs:code

data segment
    db'conversation'
data ends

code segment
    start:  mov ax,data
            mob 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
    end start

从代码中可以看出,其实际上使用[si]来表示出相关内存的,直接在内存中进行有关运算。当处理好一个字节之后,inc si,让si继续增加来指向下一个字节,这种逻辑是你需要明确的。

”运算;增加地址;循环到运算“,这种步骤是你需要明确的。

10.12 寄存器冲突问题

如jcxz和loop指令都会用到寄存器cx,这样如果在一起使用,很可能会发生冲突,这是你需要明确的。

我们一个常用的解决办法就是将你的数据压入栈中,等之后再推出来,这种操作是需要你明确的。

举个例子:要程序处理的字符串以0位结尾符,小写换大写,这个字符串可以如下定义:

data segment
db 'conversation',0
db 'conversation',0
db 'conversation',0
segment ends

code segment

    strat:  mov ax,data
            mov ds,ax
            mov bx,0

            mov cx,4
        s:  mov si,bx
            call capital
            add bx,5
            loop s

            mov ax,4c00h
            int 21h

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

其中哪些地方是你需要处理的,一定要注意好程序的入栈和出栈顺序

还有要确保所有内容都出栈时才使用ret,否则可能导致数据提取的不对。

该程序是双循环的架构,里面处理的是一行中的内容,这个逻辑是你要明确的!

编写子程序的标准框架

子程序开始:  子程序中使用的寄存器入栈
            子程序内容
            子程序中使用的寄存器出栈
            返回(ret,retf)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值