欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

CALL和RET指令---汇编学习笔记

程序员文章站 2024-03-23 22:36:40
...

CALL和RET指令

call和ret指令都是转移指令,它们都修改IP,或同时修改CS和IP。它们经常被共同用来实现子程序的设计


10.1 ret和retf

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

CPU执行ret指令时,进行下面2步操作(相当于pop IP):

  1. (IP)=((ss)16+(sp))
  2. (sp)=(sp)+2

CPU执行retf指令时,进行下面4步操作(相当于pop IP AND pop CS):

  1. (IP)=((ss)16+(sp))
  2. (sp)=(sp)+2
  3. (CS)=((ss)16+(sp))
  4. (sp)=(sp)+2

检测点 10.1
补全程序,实现从内存1000:0000处开始执行指令。

assume cs:code
stack segment
    db 16 dup(0)
stack ends

code segment
    start:
        mov ax,stack
        mov ss,ax
        mov sp,16
        ;补全下面一条指令
        mov ax,1000h

        push ax
        ;补全下面一条指令
        mov ax,0h
        push ax
        retf
code ends
end start

实验结果如下:
CALL和RET指令---汇编学习笔记


10.2 call指令

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

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

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

指令格式:call 标号(将当前的IP压栈后,转到标号处执行指令)

CPU执行此种格式的call指令时,进行如下操作(相当于执行了push IP AND jmp near ptr 标号):

  1. (sp)=(sp)2
  2. ((ss)16+(sp))=(IP)
  3. (IP)=(IP)+16

检测点 10.2
下面的程序执行后,ax中的数值为多少?

内存地址 机器码 汇编指令 ax
1000:0 b8 00 00 mov ax,0 ax=0000h
1000:3 e8 01 00 call s ip=0006h
1000:6 40 inc ax 没有执行
1000:7 58 s:pop ax ax=0006h

发现,push的IP是执行指令后的IP


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

call far ptr 标号实现的是段间转移

CPU执行此种格式的call指令时,进行如下操作(相当于执行push CS AND push IP AND jmp far ptr 标号):

  1. (sp)=(sp)2
  2. ((ss)16+(sp))=(CS)
  3. (sp)=(sp)2
  4. ((ss)16+(sp))=(IP)
  5. (CS)=
  6. (IP)=

检测点 10.3
下面的程序执行后,ax中的数值为多少?

内存地址 机器码 汇编指令 ax
1000:0 b8 00 00 mov ax,0 ax=0000h
1000:3 9A 09 00 00 10 call far ptr s cs=1000h,ip=0008h
1000:8 40 inc ax 没有执行
1000:9 58 s: pop ax ax=0008h
1000:10 pop ax ax=1000h

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

指令格式:call 16为reg
功能:

  1. (sp)=(sp)2
  2. ((ss)16+(sp))=(IP)
  3. (IP)=(16reg)

相当于执行了push IP AND jmp 16位reg

检测点 10.4
下面的程序执行后,ax中的数值为多少?

内存地址 机器码 汇编指令 ax
1000:0 b8 06 00 mov ax,6 ax=0006h
1000:2 ff d0 call ax ip=0005h
1000:5 40 inc ax 没有执行
1000:6 8b ec mov bp,sp bp=sp
1000:8 03 36 00 add ax,[bp] ax=000Ah

注意,最后一条指令是add指令。


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

转移地址在内存中的call指令有两种格式
(1)call word ptr 内存单元地址,相当于push IP AND jmp word ptr 内存单元地址
(2)call dword ptr 内存单元地址,相当于push CS AND push IP AND jmp dword ptr 内存单元地址

检测点 10.5
(1)下面的程序执行后,ax中的数值为多少?(注意:用call指令的原理来分析,不要在Debug中单步跟踪来验证你的结论。对于此程序,在Debug中单步跟踪的结果,不能代表CPU的实际执行结果)

assume cs:code
stack segment
    dw 8 dup(0)
stack ends
code segment
    start:
        mov ax,stack                ;ax=stack
        mov ss,ax                   ;ss=ax
        mov sp,16                   ;sp=16
        mov ds,ax                   ;ds=ax
        mov ax,0                    ;ax=0
        call word ptr ds:[0EH]      ;push ip AND jmp ds:[0eh]
        inc ax                      ;上面的应该是这条指令的ip,jmp ds:[0eh]应该
                                    ;转跳这条指令,那么ax=1

        inc ax                      ;ax=2
        inc ax                      ;ax=3
        mov ax,4c00h
        int 21h
code ends
end start

这真的很奇怪的程序,会懵!

(2)下面的程序执行后,ax和bx中的数值为多少?

assume cs:code
data segment
    dw 8 dup(0)
data ends
code segment
    start:
        mov ax,data                     ;ax=data
        mov ss,ax                       ;ss=ax
        mov sp,16                       ;sp=16
        mov word ptr ss:[0],offset s    ;ss:[0]=s的地址
        mov ss:[2],cs                   ;ss:[2]=cs
        call dword ptr ss:[0]           ;call (cs):(s的地址)
        nop                             ;ss:[0ch]=这条指令的地址
                                        ;ss:[0eh]=cs
    s:
        mov ax,offset s                 ;ax=s的地址
        sub ax,ss:[0ch]                 ;ax=ax-ss:[0ch] = 1
        mov bx,cs                       ;bx=cs
        sub bx,ss:[0eh]                 ;bx=bx-cs=0
        mov ax,4c00h
        int 21h
code ends
end start

实验结果如下:
CALL和RET指令---汇编学习笔记


10.7 call和ret的配合使用

现在来看一下,如何将它们配合使用来实现子程序的机制

问题 10.1
下面程序返回前,bx中的值是多少?

assume cs:code
code segment
    start:
        mov ax,1        ;1.ax=1
        mov cx,3        ;2.cx=3
        call s          ;3.push 下一条指令的IP,jmp s处
        mov bx,ax       ;6.(bx)=8
        mov ax,4c00h
        int 21h
    s:
        add ax,ax       
        loop s          ;4.ax=2^3次方,后结束这个loop
        ret             ;5.pop ip,就返回到6处
code ends
end start

具有子程序的源程序框架如下:

assume cs:code
code segment
    main: 
            ..
            ..
            call sub1    ;调用子程序sub1
            ..
            mov ax,4c00h
            int 21h

    sub1:
            ..           ;子程序sub1开始
            call sub2    ;调用子程序sub2
            ..
            ret          ;sub1子程序返回
    sub2:
            ..           ;子程序sub2开始
            ..
            ret          ;sub2子程序返回
code ends
end main

10.8 mul指令

mul是乘法指令,使用mul做乘法的时候注意以下两点
(1)两个相乘的数:两个相乘的数,要么都是8位,要么都是16位。如果是8位,一个默认放在AL中,另一个放在8位reg或内存字节单元中;如果是16位,一个默认在AX中,另一个放在16位reg或内存单元中。
(2)结果:如果是8位乘法,结果默认放在AX中;如果是16位乘法,结果高位默认在DX中存放,低位在AX中存放。

格式如下:

mul reg
mul 内存单元

示例程序:计算100*10000

mov ax,100
mov bx,100000
mul bx

结果:(ax)=4240H,(dx)=000FH


10.9 模块化程序设计

call与ret指令共同支持了汇编语言编程中的模块化设计。在实际编程中,程序的模块化是必不可少的。因为现实的问题比较复杂,对现实问题进行分析时,把它转化成互相联系、不同层次的子问题,是必须的解决方法,我们可以用简捷的方法,实现多个相互联系、功能独立的子程序来解决一个复杂的问题


10.10 参数和结果传递的问题

子程序一般都要根据提供的参数处理一定的事务,处理后,将结果(返回值)提供给调用者

比如,设计一个子程序,可以根据提供的N,来计算N的3次方。这里面就有两个问题:

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

显然,可以用寄存器来存储,可以将参数放在bx中;因为子程序中要计算N^3,可以使用多个mul指令,为了方便,可将结果放在dx和ax中。子程序如下。

;说明:计算N的3次方
;参数:(bx)=N
;结果:(dx:ax)=N^3
cube:
    mov ax,bx
    mul bx
    mul bx
    ret

10.11 批量数据的传递

如果有两个参数,那么可以用两个寄存器来存储,可是如果需要传递的数据有3个、4个或更多直至N个,该怎样存储呢?

这种时候,我们将批量数据放在内存中,然后将它们所在的内存空间的首地址放在寄存器中,传递给需要的子程序。对于具有批量数据的放回结果,也可用同样的方法

例如:编程,将data段中的字符串转化为大写。

assume cs:code
    db 'conversation'
data ends
code segment
    start:
        mov ax,data
        mov ds,ax
        mov si,0          ;ds:si指向字符串所在空间的首地址
        mov cx,12         ;cx存放字符串的长度
        call capital
        mov ax,4c00h
        int 21h
    capital:
        and byte ptr [si],11011111b
        inc si
        loop capital
        ret
code ends
end start

10.12 寄存器冲突的问题

寄存器出现冲突,可以使用栈来解决。这个小节比较细,自己看书!

以后,我们编写子程序的标准框架如下:

子程序开始:
    子程序中使用的寄存器入栈
    子程序内容
    子程序中使用的寄存器出栈
    返回(ret、retf)

实验10 编写子程序

1. 显示字符串
问题:
显示字符串是现实工作中经常要用到的功能,应该编写一个通用的子程序来实现这个功能。我们应该提供灵活的调用接口,使调用者可以决定显示的位置(行、列)、内容和颜色。
子程序描述
名称: show_str
功能:在指定的位置,用指定的颜色,显示一个用0结束的字符串。
参数:
(dh)=(0 24(dl)=(0 79))
(cl)=ds:si
返回:
应用举例:在屏幕的8行3列,用绿色显示data段中的字符串。

assume cs:code
data segment
    db 'Welcome to masm!',0
data ends

code segment
    start:
        mov dh,8
        mov dl,3
        mov cl,2
        mov ax,data
        mov ds,ax
        mov si,0
        call show_str

        mov ax,4c00h
        int 21h

    show_str:
        push ax
        push bx
        push cx
        push es     
        push si
        ;屏幕是B8000~BFFFFF这段
        ;开始位置应该为dh*80*2+dl*2且MAX=4160<2^15
        mov ax,0B800h
        mov es,ax
        ;计算开始位置
        ;计算行
        mov ah,0
        mov al,dh
        mov bh,160
        ;乘8位,dx不变
        mul bh
        ;计算具体开始位置
        mov bh,0
        mov bl,dl
        add ax,bx
        add ax,bx
        ;得到开始位置
        mov bx,ax

        ;开始显示
        mov ah,cl
    color:
        mov cl,ds:[si]
        mov ch,0
        jcxz ok
        mov al,ds:[si]
        mov es:[bx],al
        mov es:[bx+1],ah
        add bx,2
        inc si
        jmp short color

    ok:
        pop si
        pop es
        pop cx
        pop bx
        pop ax
        ret

code ends
end start

实验结果:
CALL和RET指令---汇编学习笔记

2. 解决除法溢出的问题
问题:
前面讲过,div指令可以做除法。当进行8位除法的时候,用al存储结果的商,ah存储结果的余数;进行16位除法的时候,用ax存储结果的商,dx存储结果的余数。可是,现在有一个问题,如果结果的商大于al或ax所能存储的最大值,那么将如何?

比如:

mov bh,1
mov ax,1000
div bh

此时,al放不下1000,这就是除法溢出

子程序描述
**名称:**divdw
功能:进行不会产生溢出的除法运算,被除数为dword型,除数为word型,结果为dword型。
参数:
(ax)=dword16
(dx)=dword16
(cx)=
返回:
(dx)=16
(ax)=16
(cx)=
应用举例:计算1000000/10(F4240H/0AH)

assume cs:code,ss:stack

stack segment
    dw 8 dup(0)
stack ends

code segment
    start:
        mov ax,4240h
        mov dx,000fh
        mov cx,0ah
        call divdw

        mov ax,4c00h
        int 21h

    divdw:
        push bx
        push es
        ;高
        mov es,dx
        ;低
        mov bx,ax
        ;X/N = int(H/N)*65536+[rem(H/N)*65536+L]/N
        ;int(H/N)
        ;rem(H/N)
        mov ax,es
        mov dx,0
        div cx
        ;int(H/N)*65536,ax存储商,es存储高位结果
        mov es,ax
        ;rem(H/N)*65536+L,dx存储余数,dx存储高位,bx是低位
        mov ax,bx
        ;[rem(H/N)*65536+L]/N,得到低位结果,用ax存储
        div cx
        ;余数是dx
        mov cx,dx
        ;此时得出2个结果了,低位结果在AX中,高位结果在es中
        mov dx,es
        pop es
        pop bx
        ret

code ends
end start

实验结果:
CALL和RET指令---汇编学习笔记

3. 数值显示
问题:
编程,将data段中的数据以十进制的形式显示出来。
子程序描述:
名称: dtoc
功能:将word型数据转变为表示十进制数的字符串,字符串以0为结尾符。
参数:
(ax)=word
ds:si
返回:
应用举例:编程,将数据12666以十进制的形式在屏幕的8行3列,用绿色显示出来。在显示时,我们调用本次实验中的第一个子程序show_str

assume cs:code,ds:data

data segment
    db 10 dup (0)
data ends

code segment
    start:
        mov ax,12666
        mov bx,data
        mov ds,bx
        mov si,0
        call dtoc

        mov dh,8
        mov dl,3
        mov cl,2
        call show_str

        mov ax,4c00h
        int 21h

    dtoc:
        push ax
        push bx
        push cx
        push dx
        push si
        push di
        ;初始化
        mov bx,10
        mov dx,0
        mov di,0
        ;求余数
    s:
        mov cx,ax
        jcxz rev
        div bx
        ;ax是商,dx是余数
        add dl,30h
        mov ds:[si],dl
        inc si
        ;dx需要置0
        mov dx,0
        jmp short s

    rev:
        push cx
        ;逆序,循环si/2次结束
        mov dx,0
        mov ax,si
        mov bx,2
        div bx
        mov cx,ax
        ;si是长度,下标是从0开始,所以需要-1
        dec si

    rev_loop:
        ;前缀
        mov al,ds:[di]
        ;后缀
        mov ah,ds:[si]
        ;交换
        mov ds:[si],al
        mov ds:[di],ah
        ;前缀自增
        inc di
        ;后缀自减
        dec si

        loop rev_loop
        pop cx
        jmp ok

    ok:
        pop di
        pop si
        pop dx
        pop cx
        pop bx
        pop ax
        ret

    show_str:
        push ax
        push bx
        push cx
        push es
        push si
        ;屏幕是B8000~BFFFFF这段
        ;开始位置应该为dh*80*2+dl*2且MAX=4160<2^15
        mov ax,0B800h
        mov es,ax
        ;计算开始位置
        ;计算行
        mov ah,0
        mov al,dh
        mov bh,160
        ;乘8位,dx不变
        mul bh
        ;计算具体开始位置
        mov bh,0
        mov bl,dl
        add ax,bx
        add ax,bx
        ;得到开始位置
        mov bx,ax

        ;开始显示
        mov ah,cl
    color:
        mov cl,ds:[si]
        mov ch,0
        jcxz ok1
        mov al,ds:[si]
        mov es:[bx],al
        mov es:[bx+1],ah
        add bx,2
        inc si
        jmp short color

    ok1:
        pop si
        pop es
        pop cx
        pop bx
        pop ax
        ret

code ends
end start

这题做了一天,出错地方在div bx那条语句,如果直接执行div bx,会出现死循环,可是debug又不出现死循环。最后将dx置0即可

课程设计

改天做,这里放置超链接!