• call 和 ret 指令


    call 和 ret 指令都是转移指令,他们都修改 IP , 或同时修改 CS 和 IP 。经常被用来实现子程序设计。
    ret 指令用栈中的数据( 弹出一个数据 ),修改 IP 内容实现近转移
    retf 指令用栈中数据( 弹出两个数据 ),修 改 CS 和 IP  的内容,实现远转移
    ; CPU 执行 ret 指令
    1、(IP)=((SS)*16+(SP))
    2、(sp)=(sp)+2

    相当于:
    pop IP
    ; CPU 执行 retf 指令
    1、(IP)=((SS)*16+(SP))
    2、(SP)=(SP)+2
    3、(CS)=((SS)*16+(SP))
    4、(SP)=(SP)+2

    相当于:
    pop IP
    pop CS

    ;程序执行 ret 指令后, (IP)=0,CS:IP指向代码段第一条指令
    assume cs:code
    stack segment
            db 16 dup (0)
    stack ends
    code segment
            mov ax , 4c00h
            int 21h
    start:  mov ax , stack
            mov ss , ax
            mov sp , 16
            mov ax , 0
            push ax
            mov bx , 0
            ret         ; ip 被设置为0,指令转移到mov ax , 4c00
    code ends
    end start

    assume cs:code
    code segment
            mov ax , 4c00h
            int 21h
    start:   
            mov ax , 0
            push ax
            mov bx , 0
            ret      
    code ends
    end start        一样的效果
    ;程序执行 reft 后,CS:IP 指向代码段第一条指令
    assume cs:code
    stack segment
            db 16 dup (0)
    stack ends
    code segment
            mov ax , 4c00h
            int 21h
    start:  mov ax , stack
            mov ss , ax
            mov sp , 16
            mov ax , 0
            push cs
            push ax
            mov bx , 0
            retf
    code ends
    end start

    assume cs:code
    code segment
            mov ax , 4c00h
            int 21h
    start:
            mov ax , 0
            push cs
            push ax
            mov bx , 0
            retf
    code ends
    end start







    call 指令
    1、将当前的 IP 或 CS 和 IP 压入栈中;
    2、转移
      call 指令不能实现短转移,除此之外,call指令实现转移的方法和 jmp 指令的原理相同。






    依据位移进行转移的 call 指令
    指令:call 标号    (将当前 IP 压栈后,转到标号处执行指令)
    CPU 执行这种指令进行如下操作
    1、(SP)=(SP)-2                   ((SS)*16+(SP))=(IP)
    2、(IP)=(IP)+16 位位移

    CPU 执行 “call  标号”时,相当于: push IP         jmp near ptr 标号

    16位位移=“标号”处的地址-call 指令后的第一个字节的地址;范围 -32768~32767 ;16位位移是编译程序在编译的时候算出来的。

    内存地址 机器码 汇编指令
    076A:0
    076A:3
    076A:6
    076A:7
    B8 00 00
    E8 01 00
    40
    58
           mov ax , 0
           call s   ; 执行时 call 0007
           inc ax
    s:    pop ax
    当 IP=3的时候指向 call s ;执行这条指令的时候 IP 偏移到 6,也就是下一条指令;call s == push IP   jmp near ptr 标号   此时栈中只有一个数据6,最后pop ax , 所以ax=6;






    转移的目的地址在指令中的 call 指令
    call far ptr 标号” 实现的是段间转移。 
    1、(SP)=(SP)-2
          ((SS)*16+(SP))=(CS) 
          (SP)=(SP)-2
          ((SS)*16+(SP))=(IP)
    2、 (CS)=标号所在段的段地址
           (IP)=标号在段中的偏移地址

    相当于进行:
    push CS
    push IP
    jmp far ptr 标号

    内存地址 机器码 汇编指令
    076A:0
    076A:3
    076A:8
    076A:9
    076A:A
    B8 00 00
    9A 09 00 6A 07
    40
    58
    5B
             mov ax , 0
             call far ptr s     ; call 076A:0009
             inc ax
    s:      pop ax
            pop bx 
    执行call far ptr s 的时候,cpu把下一条指令的位置 CS:IP =076A:0008 压入栈中保存,然后计算到标号 s 的偏移,转到标号处执行;此时出栈 ax=IP=0008,bx=CS=076A;
     //初始栈空间内容
     //执行完call指令后的栈空间
      //call 指令
      //最后寄存器结果






    转移地址在寄存器中的 call 指令
    指令格式:call 16 位寄存器
    功能:(SP)=(SP)-2
               ((SS)*16+(SP))=(IP)
               (IP)=(16位寄存器)
    相当于进行:
    push IP
    jmp 16位寄存器

    内存地址 机器码 汇编指令
    076A:0
    076A:3
    076A:5
    076A:6
    076A:8
    076A :  B
    B8 00 00
    FF D0 
    40
    8B EC
    03 46 00
    58
             mov ax , 6
             call ax        
             inc ax
    s:      mov bp , sp
             add ax , [bp]   ; ax=000B , 这里取的是ss:bp内存里的值,因为sp==bp,所以是堆栈顶数据5,5+6=B
             pop ax             ; ax=0005

    call ax ; 指令执行时当前 IP 偏移到 inc ax 即:IP=5;进栈;指令跳转到IP=6处执行,ax=6+栈顶元素=000B;出栈,ax =5







    转移地址在内存中的 call 指令
    1、call word ptr 内存单元地址
    相当于:
    push IP
    jmp word ptr 内存单元地址

    mov sp , 10h
    mov ax , 0123h
    mov ds:[0] , ax
    call word ptr ds:[0]   ;执行这条指令,IP进栈,栈中存放000D
    执行后:(IP)=0123H,(SP)=0EH
    2、call dword ptr 内存单元地址
    相当于:
    push CS
    push IP

    jmp dword ptr 内存单元地址

    mov sp , 10h
    mov ax , 0123h
    mov ds:[0] , ax
    mov word ptr ds:[2] , 0
    call dword ptr ds:[0]
    执行后:(CS)=0 , (IP)=0123h,(sp)=0Ch






    call 和 ret 的配合使用
    assume cs:codesg
    codesg segment
    start:  mov ax , 1
            mov cx , 3
            call s        ; 栈中进栈(IP),执行到这行指令的时候,IP指向mov bx , ax
            mov bx , ax     ;(bx)=8Z
            mov ax , 4c00h
            int 21h
    s:        add ax , ax
            loop s
            ret           ;从栈中取一个数当做ip(刚好指向mov bx , ax)
    codesg ends
    end






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

    指令格式:
    mul reg
    mul 内存单元

    内存单元可以用不同的寻址方式给出,eg:
    mul byte ptr ds:[0]
    含义:(ax)=(al)*((ds)*16+0)
    mul word ptr [bx+si+8]
    含义:(ax)=(ax)((ds)*16+(bx)+(si)+8) ; 结果的低16位
         (dx)=(ax)((ds)*16+(bx)+(si)+8) ; 结果的高16位

    ; 计算 100*10  
    ; 两个数都小于255,可以做8位乘法
    mov al , 100
    mov bl , 10
    mul bl

    结果:(ax)=1000(03E8H)
    ; 计算 100*10000
    ; 10000大于255,必须做16位乘法
    mov ax , 100
    mov bx , 10000
    mul bx
    结果:(ax)=4240H , (dx)=000FH ; F4240H=1000000






    模块化程序设计
      call 和 ret 指令支持汇编语言编程中的模块化设计。





    参数和结果传递的问题
      子程序一般要根据提供的参数处理一定的事务,处理后将结果(返回值)提供给调用者。即:如何存储子程序需要的参数和产生的返回值。
    assume cs:codesg , ds:datasg
    datasg segment
            dw 1,2,3,4,5,6,7,8       ;(16个字节(00))
            dd 0,0,0,0,0,0,0,0       ;(32个字节(00) )
    datasg ends
    codesg segment
    start:  mov ax , datasg
            mov ds , ax
            mov si , 0            ;偏移0指向第一个数1
            mov di , 16         ;偏移16指向存放结果内存单元的首地址
            mov cx , 8           ; 8个数,进行8次循环
    s:      mov bx , [si]  
            call cube
            mov [di] , ax             ;把结果的低16位放到低位两个字节(00 01)
            mov [di].2 , dx          ;把结果的高16位放到高位的两个字节(02 03)
            add si , 2                   ;偏移2个单位取到下一个数2
    ; ds:si 指向下一个 word 单元
            add di , 4                  ;偏移到下一个存储结果的内存地址(04 05 06 07)
    ; ds:di 指向下一个 dword 单元
            loop s

            mov ax , 4c00h
            int 21h

    cube:        mov ax , bx   ; 对拿到的数进行立方运算
            mul bx
            mul bx
            ret
    codesg ends
    end start

    // 程序初始状态


    // 程序运行两次,也就是得出1的三次方(00 00 00 01)和2的三次方(00 00 00 08)
    1的三次方等于 0001,低16位存放1(01 00),高16位存放0;

    // 改写程序的初始第一个值为9
    9的三次方等于 2D9 , 低16位存放2D9 (D9 02), 高16位存放0(00 00)

    //第一个数是9运行一次循环的内存情况

    //第一个数是100(64H),100^3=F4240H,低位4240H放在低16位(40 42),F放在高16位(0F 00)

     




    批量数据的传递
      如果子程序要传递多个参数,寄存器有限?
      可以将批量数据放到内存中,然后将他们的内存空间首地址放在寄存器中,传递给需要的子程序。对于具有批量数据的返回结果,也可以用同样的方法。
      
    assume cs:codesg , ds:datasg
    datasg segment
            db 'conversation'
    datasg ends
    codesg segment
    start:        mov ax , datasg
            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
    codesg ends
    end start





    除了用寄存器传递参数外,还有一种通用的方法就是用栈来传递参数。






    寄存器冲突的问题
    ;改进上面的程序,字符串后面加一个0表示结束,就可以不用cx

    assume cs:codesg , ds:datasg
    datasg segment
            db 'conversation' , 0
    datasg ends
    codesg segment
    start:        mov ax , datasg
            mov ds , ax
            mov si , 0

            call capital
            mov ax , 4c00h
            int 21h
    capital:mov cl , [si]      ; 当读到最后一个的时候为 0 
            mov ch , 0
            jcxz ok                ; 读到最后 (cx)=0
            and byte ptr [si] , 11011111b
            inc si
            jmp short capital
    ok:        ret                  ; 出栈,设置cs:ip指向 mov ax , 4c00h
    codesg ends
    end start






    ◆编写调用子程序的程序的时候不必关心子程序到底使用了哪些寄存器;
    ◆编写子程序的时候不必关心调用者使用了哪些寄存器
    ◆不会发生寄存器冲突
    ;编写程序将 data 段中的字符串全部转化为大写
    assume cs:code , ds:data
    data segment
            db 'word' , 0
            db 'unix' , 0
            db 'wind' , 0
            db 'good' , 0
    data ends
    code segment
    start:            mov ax, data
                         mov ds , ax
                         mov bx , 0
                         mov cx , 4                                             ; 设置循环次数4

    s:                   mov si , bx 
                         call capital                                            ;  调用子程序处理字符串
                         add bx , 5                                             ;  偏移量加 5 ,处理下一个字符串
                         loop s

                         mov ax , 4c00h
                         int 21h

    capital:         push cx                                                 ; 执行子程序防止有冲突寄存器,先压栈保存主程序寄存器值
                         push si 

    change:        mov cl , [si]
                          mov ch , 0
                          jcxz ok                                                   ; 判断(cx)是否等于0,如果等于0表示读完一个字符串
                          and byte ptr [si] , 11011111b
                          inc si
                          jmp short change
    ok:                 pop si                                                    ; 结束了一次子程序的调用,恢复主程序的相应寄存器的值
                          pop cx
                          ret                                                         ; 返回call下一行指令,执行下一次循环
    code ends
    end start





  • 相关阅读:
    Y460/Y470 Nvidia optirum solution : switch off the nvidia card and solve screen flash problem
    matlab中提示错误使用* BLAS loading error解决方法
    学习笔记 第十四周 第一篇
    学习笔记 第十三周 第一篇
    学习笔记 第十二周 第一篇
    学习笔记 第十一周 第一篇
    学习笔记 第十周 第一篇
    学习笔记 第九周 第一篇
    学习笔记 第八周 第二篇(修改版)
    学习笔记 第八周 第二篇
  • 原文地址:https://www.cnblogs.com/meihao1203/p/7896132.html
Copyright © 2020-2023  润新知