body, table{font-family: 微软雅黑; font-size: 13.5pt} table{border-collapse: collapse; border: solid gray; border-width: 2px 0 2px 0;} th{border: 1px solid gray; padding: 4px; background-color: #DDD;} td{border: 1px solid gray; padding: 4px;} tr:nth-child(2n){background-color: #f8f8f8;}

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 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

call 和 ret 指令的更多相关文章

  1. 汇编语言---call和ret指令

    汇编语言--call和ret指令 call和ret指令 call和ret指令都是转移指令,它们都修改IP,或同时修改CS和IP. 它们经常被共同用来实现子程序的设计. ret和retf ret指令用栈 ...

  2. 汇编学习笔记(7)call和ret指令

    ret和retf CPU执行ret指令时进行以下两步操作: (IP)=((ss)*16+(sp)) (sp)=(sp)+2 这相当于pop IP CPU执行retf指令时进行以下四步操作: (IP)= ...

  3. [汇编学习笔记][第十章 CALL和RET指令]

    第十章 CALL和RET指令 call和ret指令都是转移指令,它们都修改CS和IP.经常被共同用于实现子程序的设计.这一章,我们讲解call和ret指令的原理 10.1 ret和retf ret指令 ...

  4. 汇编-10.0-CALL和RET指令

    call和ret指令都是转移指令,他们都是修改IP,或同时修改CS和IP.它们常被共同用来实现子程序设计. 1.ret和retf ret指令用栈中的数据,修改IP的内容,从而实现近转移: retf指令 ...

  5. 第十章 Call 和 Ret 指令

    引言 想想程序之间的加载返回过程. call 和 ret 指令都是转移指令,它们都修改 IP,或同时修改 CS 和 IP. call 和 ret 经常被共同用来实现自程序的设计. 这一章,我们讲解 c ...

  6. 汇编语言笔记 CALL和RET指令

    转载地址:http://www.cnblogs.com/dennisOne ☞模块化程序设计 模块化程序设计 汇编语言通过call和ret指令实现了模块化程序设计.可以实现多个相互联系.功能独立的子程 ...

  7. 自己总结:汇编CALL和RET指令

    ret指令,相当于 pop IP:修改IP的内容,从而实现近转移 retf指令,相当于 pop IP pop CS:修改CS和IP的内容,从而实现远转移 -------------- CPU执行cal ...

  8. call和ret指令

    call和ret都是用来修改ip或cs:ip,可以用来实现子程序的设计:   1.ret和retf ret    ->修改ip的内容,从而实现近转移: retf    ->同时修改cs和i ...

  9. 王爽汇编第十章,call和ret指令

    目录 王爽汇编第十章,call和ret指令 call和ret指令概述: ret和retf ret指令 retf指令 call 和 ret 的配合使用 call指令详解 call原理 call指令所有写 ...

随机推荐

  1. Netty入门之客户端与服务端通信(二)

    Netty入门之客户端与服务端通信(二) 一.简介 在上一篇博文中笔者写了关于Netty入门级的Hello World程序.书接上回,本博文是关于客户端与服务端的通信,感觉也没什么好说的了,直接上代码 ...

  2. swift4.0 正则表达式判断手机号

    p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 15.0px Menlo; color: #ffffff; background-color: #282b3 ...

  3. linux socket编程:简易客户端与服务端

    什么是socket? socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,都可以用“打开open –> 读写write/read –> 关闭close”模式来 ...

  4. Nginx 反向代理获取真实IP问题

    一.前言 前文 Nginx 解决WebApi跨域二次请求以及Vue单页面问题 当中虽然解决了跨域问题带来的二次请求,但也产生了一个新的问题,就是如果需要获取用户IP的时候,获取的IP地址总是本机地址. ...

  5. 关于javascript代码优化的8点建议

    前面的话 本文将详细介绍JS编程风格的几个要点 松耦合 当修改一个组件而不需要更改其他组件时,就做到了松耦合 1.将JS从CSS中抽离:不要使用CSS表达式 //不好的做法 .box{width: e ...

  6. Python进阶内容(二)--- 装饰器

    谈装饰器前,需要明白一件事,Python 中的函数和 Java.C++不太一样,Python 中的函数可以像普通变量一样当做参数传递给另外一个函数,例如: def foo(): print(" ...

  7. 8086cpu中的标志寄存器与比较指令

    在8086CPU中有一个特殊的寄存器--标志寄存器,该寄存器不同于其他寄存器,普通寄存器是用来存放数据的读取整个寄存器具有一定的含义,但是标志寄存器是每一位都有固定的含义,记录在运算中产生的信息,标志 ...

  8. 迭代器中next()的用法

    >>> g = (x ** 2 for x in range(10)) >>> next(g) 0 >>> next(g) 1 >>& ...

  9. flask-session组件

    简介 flask-session是flask框架的session组件,由于原来flask内置session使用签名cookie保存,该组件则将支持session保存到多个地方,如: redis mem ...

  10. Vue 组件之 Router

    Vue 组件之 Router Vue 开发单页应用的时候,免不了使用Vue组件.在单页应用上如何进行组件切换? 结构如下图所示: 主页面包含Foo组件与Bar组件,在主页面中可以进行Foo与 Bar的 ...