汇编基础知识

时间:2021-11-30 13:26:30
 

一、汇编基础知识

    1.机器语言(二进制语言)

       1.1 机器语言是机器指令的集合

    2.汇编语言的产生

    

    3.汇编语言的组成

        1、汇编指令(机器码的助记符)
        2、伪指令   (由编译器执行)
        3、其它符号(由编译器识别)

    4.存储器

      4.1 CPU 是计算机的核心部件.它控制整个计算机的运作并进行运算,
      要想让一个CPU 工作,就必须向它提供指令和数据。
         - 指令告诉CPU怎么运算,数据就是告诉CPU运算什么
       
      指令和数据在存储器中存放,也就是平时所说的内存。

      4.2 离开了内存,性能再好的CPU也无法工作。

      4.3 磁盘不同于内存,磁盘上的数据或程序如果不读到内存中,
         就无法被CPU 使用。
          硬盘 -> 内存  -> CPU

    5.指令和数据

       5.1 指令和数据是应用上的概念。

       5.2 在内存或磁盘上,指令和数据没有任何区别,都是二进制信息。
       
       5.3 二进制数据
           1000    1001   1101    1000  
        ─>   8      9      D       8    H (数据)

           hex     十六进制   123456789ABCDEF       0xff   8D8H
           binary  二进制
           
           1111      2^0 + 2^1 + 2^2 + 2^3  = 15 (8+4+2+1)   0~15
                      1     2     4     8
           
                      2^4-1    
           总公式:   2^n - 1         4字节  2^32 - 1 = 4G
                      
            
           练习: 1011    8+2+1 = 11     
           
           1000100111011000  
                ─> MOV AX,BX (程序)

            十进制D  [一般习惯都不加]
            二进制B
            八进制Q     111    
            十六进制H      

    6.存储单元(Byte字节)

        1KB=1024B
        1MB=1024KB  1024*1024
        1GB=1024MB   
        1TB=1024GB    

    7.CPU对存储器和读写

        CPU要想进行数据的读写,必须和外部器件(标准的说法是芯片)
        进行三类信息的交互:
            存储单元的地址        (地址信息)(硬盘地址,内存地址,显卡显存地址)
            器件的选择,读或写命令(控制信息)
            读或写的数据          (数据信息)

             地址总线    传输 地址信息
             数据总线    传输 数据信息
             控制总线    传输 控制信息
            
        CPU是通过地址总线来指定存储单元的。
        地址总线上能传送多少个不同的信息,CPU就可以对多少个存储单元进行寻址。
    
        CPU          系统           软件程序
        64              64              64           =>真正的64位的运算速度       
        

    8. 地址总线

           一个CPU有N根地址总线,则可以说这个CPU的地址总线的宽度为N。
           
           这样的CPU最多可以寻找2的N次方个内存单元。
               2^n
               1Byte = 8 bit       2^8 - 1 = 256 - 1  (0~255)           
               
               64位    8字节        
           

    9.  数据总线(高速公路)

            CPU与内存或其它器件之间的数据传送是通过数据总线来进行的。

            数据总线的宽度决定了CPU和外界的数据传送速度。(传输量)

    10. 控制总线

        CPU对外部器件的控制是通过控制总线来进行的。在这里控制总线是个总称,
        控制总线是一些不同控制线的集合。
        
        有多少根控制总线,就意味着CPU提供了对外部器件的多少种控制。    
    

    11.内存地址空间的概念

    

二、寄存器(CPU工作原理)

     2.1CPU概述

        1.1 一个典型的CPU由运算器、控制器、寄存器等器件组成,这些器件靠内部总线相连。
        
        1.2 内部总线实现CPU内部各个器件之间的联系。
        
        1.3 外部总线实现CPU和主板上其它器件的联系。
    

     2.2 寄存器的概述

       2.11 8086CPU有14个寄存器 它们的名称为:
           AX、BX、CX、DX、SI、DI、SP、BP、
           IP、CS、SS、DS、ES、PSW。

           这些寄存器以后会陆续介绍
           
        2.12 通用寄存器
           2.2.1 8086CPU所有的寄存器都是16位的,可以存放两个字节(即一个字)。
                word
           
           2.2.2 AX、BX、CX、DX 通常用来存放一般性数据被称为通用寄存器。

           2.2.3 8086上一代CPU中的寄存器都是8位的;
                为保证兼容性,这四个寄存器都可以分为两个独立的8位寄存器使用。
                AX可以分为AH和AL;
                BX可以分为BH和BL;
                CX可以分为CH和CL;
                DX可以分为DH和DL。
                

        2.3 字在寄存器中存储

              word = 2Byte  

        2.4 几条汇编指令

            汇编指令不区分大小写    
               AX = 8226H    BX = 8226H
               ADD AX,BX      => AX = AX + BX
                              1 044C =  8226H + 8226H      044c (溢出部分将舍弃)
                          
            注:AL的溢出不会进入AH中
             

        2.5 检测点2.1

          (1)
             mov ax,62627       AX=F4A3H
             mov ah,31H         AX=31A3H
             mov al,23H         AX=3123H
             add ax,ax          AX=6246H
             mov bx,826CH       BX=826CH
             mov cx,ax          CX=6246H
             mov bx,ax          AX=826CH
             add ax,bx          AX=04D8H              ax = ax + bx
             mov al,bh          AX=0482H
             mov ah,bl          AX=6C82H
             add ah,ah          AX=D882H
             add al,6           AX=D888H             
             add al,al          AX=D810H
             mov ax,cx          AX=6246H
            
         (2)    
              mov ax,2       AX=2
              add ax,ax      AX=4
              add ax,ax      AX=8
              add ax,ax      AX=16              
            
        

        2.6 物理地址

            CPU访问内存单元时要给出内存单元的地址。所有的内存单元构成的存储空间是
             一个一维的线性空间。
           
             每一个内存单元在这个空间中都有唯一的地址,这个唯一的地址称为物理地址。        
               

        2.7 16位结构的CPU

             概括的讲,16位结构描述了一个CPU具有以下几个方面特征:
                - 运算器一次最多可以处理16位的数据。
                - 寄存器的最大宽度为16位。
                - 寄存器和运算器之间的通路是16位的。
        
            2^10  = 1M = 1024K     64K  
            

        2.8 8086 CPU给出物理地址的方法

            2.8.1 8086有20位地址总线,可传送20位地址,寻址能力为1M。
            
            2.8.2 8086内部为16位结构,它只能传送16位的地址,
                  表现出的寻址能力却只有64K。    
            
            2.8.3 8086CPU采用一种在内部用两个16位地址合成的方法来形成
                  一个20位的物理地址。
            
            2.8.4 地址加法器合成物理地址的方法: (血汗工厂)
                   物理地址 = 段地址×16 + 偏移地址
                    20          16+4=20        16   
                         1111 1111 1111 1111
                         1111 1111 1111 1111 0000        
                        
             2.8.5 由段地址X16引发的讨论
                (1)一个数据的二进制形式左移1位,相当于该数据乘以2;
                (2)一个数据的二进制形式左移N位,相当于该数据乘以2的N次方;
                (3)地址加法器如何完成段地址×16的运算?
                         以二进制形式存放的段地址左移4位。

                     0111        7
                     1110        14     7*2      乘以2相当于左移1位                
                0111 0000        112    7*16     乘以16相当于左移4位
                
                  一个数据的八进制形式左移1位,相当于该数据乘以8
                  一个数据的十进制形式左移1位,相当于该数据乘以10
                  一个数据的十六进制形式左移1位,相当于该数据乘以16
                

        2.9 段的概念

            错误认识:
                内存被划分成了一个一个的段,每一个段有一个段地址。         
            其实:
                内存并没有分段,段的划分来自于CPU,由于8086CPU用
                “(段地址×16)+偏移地址=物理地址”的方式给出内存单元的物理地址,
                使得我们可以用分段的方式来管理内存。
        
             以后,在编程时可以根据需要,将若干地址连续的内存单元看作一个段,
             用段地址×16定位段的起始地址(基础地址),用偏移地址定位段中的内存单元。                       
              (1)段地址×16 必然是 16的倍数,所以一个段的起始地址也一定是16的倍数;
            
              (2)偏移地址为16位,16 位地址的寻址能力为 64K,所以一个段的长度最大为64K。

         检测点2.2
               物理地址 = 段地址*16 + 偏移地址  
            偏移量:0 ~ FFFFH
           (1) MAX : 0001H * 16 + FFFFH = 0010H + FFFFH =  1000FH
                 MIN : 0001H * 16 + 0     = 0010H  + 0  =    10H
                 10H ~ 1000FH

            (2) 物理地址: 20000H    ( y = a*16 + b )  (y = 20000H  0<= b <= FFFFH  求a的范围?)
                          偏移量为0     偏移量位FFFFH
                 段地址     2000H        1001H
                
                 最小为:1001H      最大为:2000H
                                 
                 

         2.10 段寄存器

              2.10.1 段寄存器就是提供段地址的。
                     8086CPU有4个段寄存器:
                       CS、DS、SS、ES
                       Code segment        Data segment
                       Stack segment       Extract segment
                       
                      当8086CPU要访问内存时,由这4个段寄存
                      器提供内存单元的段地址。
            

       2.11 CS和IP寄存器 (8086都是16位)

              CS和IP是8086CPU中最关键的寄存器,它们指示了CPU当前要读取指令的地址。
                    - CS为代码段寄存器;
                    - IP为指令指针寄存器
        
            2.12 8086 PC工作的过程
                 2.12.1 8086 PC读取和执行指令相关流程
              
                 2.12.2 8086 PC工作过的的描述
                     (1)从CS:IP指向内存单元读取指令,读取的指令进入指令缓冲器;
                     (2)IP = IP + 所读取指令的长度,从而指向下一条指令;
                     (3)执行指令。 转到步骤 (1),重复这个过程。
                
                      在 8086CPU 加电启动或复位后( 即 CPU刚开始工作时)CS和IP被设置
                      为CS=FFFFH,IP=0000H,即在8086PC机刚启动时,CPU从内存FFFF0H单元
                      中读取指令执行,FFFF0H单元中的指令是8086PC机开机后执行的第一条指令。

                      改变CS和IP的值就可以控制CPU的执行的指令
        
                 2.12.3 CPU根据什么将内存中的信息看作指令?
                          CPU将CS:IP指向的内存单元中的内容看作指令。

            2.13 修改CS和IP的指令
                 在CPU中,程序员能够用指令读写的部件只有寄存器,
                 程序员可以通过改变寄存器中的内容实现对CPU的控制。
                
                 CPU从何处执行指令是由CS、IP中的内容决定的,程序员可以通
                 过改变CS、IP中的内容来控制CPU执行目标指令。
                
                 我们如何改变CS、IP的值呢?   
                     (1)同时修改CS、IP的内容:
                         jmp 段地址:偏移地址
                            jmp 2AE3:3       =>2AE3H * 16 + 3 = 2AE33H
                            jmp 3:0B16       =>3*16   + 0B16  = 0B46H      
                         功能:用指令中给出的段地址修改CS,偏移地址修改IP。
                        
                     (2) 仅修改IP的内容:
                            jmp 某一合法寄存器
                                jmp ax   (类似于 mov IP,ax)
                                jmp bx
                         功能:用寄存器中的值修改IP。

              2.14 问题分析
                   (1)mov ax,6622H
                    (2) jmp 1000:3        
                    (3) mov ax,0000
                    (4) mov bx,ax
                    (5) jmp bx
                    (6)    mov ax,0123H
                    (7) 转到第(3)步执行                     
             
               2.15 代码段
                  对于8086PC机,在编程时,可以根据需要,将一组内存单元定义为一个段。
                  
                  可以将长度为 N( N≤64KB )的一组代码,存在一组地址连续、起始地址为 16的倍数的内存单元中,
                  这段内存是用来存放代码的,从而定义了一个代码段。

                 如何使得代码段中的指令被执行呢?
                 将一段内存当作代码段,仅仅是我们在编程时的一种安排,CPU 并不会由于这种安排,
                 就自动地将我们定义得代码段中的指令当作指令来执行。
                 CPU 只认被 CS:IP 指向的内存单元中的内容为指令。
                 所以要将CS:IP指向所定义的代码段中的第一条指令的首地址。
                 CS = 123BH,IP = 0000H。
    
               2.16 检测点3
                 修改4次:
                 第一次在CPU读取"mov ax,bx"后
                 第二次在CPU读取"sub ax,ax"后
                 第三次在CPU读取"jmp ax"后
                 第四次在CPU执行完"jmp ax"后;
                 IP最后的值为0
                
                
              2.17 实验1
                 r 查看和修改寄存器的值            
                   r   查看寄存器的值
                   r ax(cs ip等)修改寄存器ax(cs ip等)的值                   
                
                 d 查看内存中内容
                    d  1000:0  1f    (1f表示范围)
                
                 e 修改内存的内容
                   e 1000:0  30 31 32 33 34 35 36 37   (一次性修改)
                   e 1000:0  'a' 'b' 'c'
                   
                   e 1000:0                             (询问式修改)
                   
                 u  以汇编指令的形式查看机器码
                    u   1000:0   1f     (1f表示范围)
                
                 写入指令:
                    方式1: e 1000:0  b8 01 00 b9 02 00 01 08
                    方式2: a 1000:0                          以汇编指令的形式写入
                
                 t  执行下一条指令(执行之前可以通过r命令修改cs:ip的值)
                
                

三、寄存器(内存访问)

     1.1    内存中字的存储

        20000                     0x4E 20        
        a) Little - Endian (小端)就是低位字节放在内存的低地址端,高位字节放在内存的高地址端
        b) Big - Endian    (大端)就是高位字节放在内存的低地址端,低位字节放在内存的高地址端
               网络字节顺序(也就是大端模式)
               
                int a = 20000;  0x00004e20
                int* p = &a;
                printf("p=0x%p\n",p);   

            p                        
          0x004CFE44            20
          0x004CFE45            4e
          0x004CFE46            56
          0x004CFE47            43
        注意:字节单元:一个内存单元存放一个字节。字单元:存放一个字型数据(16)
              的内存单元。由两个地址连续的内存单元组成
                

    1.2 DS和[address]

        1.2.1 内存单元地址
              - CPU要读取一个内存单元的时候,必须先给出这个内存单元的地址;
              - 在8086PC中,内存地址由段地址和偏移地址组成。
              - 8086CPU中有一个 DS寄存器,通常用来存放要访问的数据的段地址。       
                              
        1.2.2 mov指令传送功能     
            (1)将数据直接送入寄存器;
            (2)将一个寄存器中的内容送入另一个寄存器中。
            (3)mov 指令 还可以将一个内存单元中的内容送入一个寄存器,
                 或者将寄存器中的数据写入内存单元
        
        1.2.3 字的传送(练习)        
           (1)
            1000:0  10000H   1123H
            ax = 1123H    

            1000:2  10002H   6622H
            bx = 6622H
            
            1000:1  10001H   2211H
            cx = 2211H
            
            1000:1  10001H   2211H
            add bx,[1] => add bx,2211H
            bx = bx + 2211H = 6622H + 2211H = 8833H
            
            1000:2  10002H   6622H
            add cx,[2] = add cx,6622H
            cx = cx + 6622H = 2211H + 6622H = 8833H
        
         1.2.4 mov、add、sub指令
 
         1.2.5 数据段              
                将一段内存当作数据段,是我们在编程时的一种安排,
                我们可以在具体操作的时候 ,用 ds 存放数据段的段地址,
                再根据需要,用相关指令访问数据段中的具体单元。
        
         1.2.6 检测点3.1
            (1)
               ds = 1
            
            MOV AX,[0000]            
            1:0    1*16 + 0 = 10H    AX = 2662H
                        
            MOV BX,[0001]
            1:1    1*16 + 1 = 11H    BX = E626H

            MOV AX,BX       AX = E626H
            MOV AX,[0000]   AX = 2662H
            
            MOV BX,[0002]  
            1:2   1*16 + 2 = 12H     BX = D6E6H
            
            ADD AX,BX    AX = FD48H
            
            ADD AX,[0004]
            1:4  14H      AX = FD48H + 2ECCH = 2C14H 舍去溢出部分
        
            mov ax,0        ax = 0

            mov al,[0002]         
            1:2  12H       al = e6H   ax = 00e6H
                            
            mov bx,0       bx = 0
            
            mov bl,[000c]  bl = 26H  BX = 0026H
            
            add al,bl    al = 0cH    ax =  000CH
        
          
           (2)
             MOV AX,6622H   CS=2000H  IP=0    DS=1000H AX=6622H BX=0
             JMP 0FF0:0100  CS=0FF0   IP=100H DS=1000H AX=6622H BX=0
             MOV AX,2000H   CS=0FF0   IP=103H DS=1000H AX=2000H BX=0
             MOV DS,AX      CS=0FF0   IP=105H DS=2000H AX=2000H BX=0
             MOV AX,[0008]  CS=0FF0   IP=108H DS=2000H AX=C389H BX=0
             MOV AX,[0002]  CS=0FF0   IP=10BH DS=2000H AX=EA66H BX=0
            
        

    1.3 栈

       1.3.1  栈是一种具有特殊的访问方式的存储空间。它的特殊性就在于,
               最后进入这个空间的数据,最先出去。
        
       1.3.2 栈有两个基本的操作:入栈和出栈。
                入栈:将一个新的元素放到栈顶;
                出栈:从栈顶取出一个元素。
                
              栈顶的元素总是最后入栈,需要出栈时,又最先被从栈中取出。
              栈的操作规则:LIFO(Last In First Out,后进先出)
    
          1.3.3 CPU提供的栈机制
          (1) 8086CPU提供入栈和出栈指令: (最基本的)
                 PUSH(入栈)
                 POP  (出栈)
        
               push ax:将寄存器ax中的数据送入栈中;
               pop ax :从栈顶取出数据送入ax。
                  
               8086CPU的入栈和出栈操作都是以字为单位进行的。
    
          (2) 8086CPU中,有两个寄存器:
                段寄存器SS    存放栈顶的段地址
                寄存器SP      存放栈顶的偏移地址
            任意时刻,SS:SP指向栈顶元素。
    
        1.3.4  push 指令的执行过程
          push ax
            (1)SP=SP–2;
            (2)将ax中的内容送入SS:SP指向的内存单元处,
                 SS:SP此时指向新栈顶。
         
              任意时刻,SS:SP 指向栈顶元素,当栈为空的时候,栈中没有元素,
              也就不存在栈顶元素,所以SS:SP 只能指向栈的最底部单元下面的单元,
              .该单元的偏移地址为栈最底部的字单元的偏移地址+2,栈最底部字单元
              的地址为1000:000E,所以栈空时,SP=0010H。

       1.3.4 pop 指令的执行过程
            (1)    pop ax
                    (1)将SS:SP指向的内存单元处的数据送入ax中;
                    (2)SP = SP+2,SS:SP指向当前栈顶下面的单元,
                       以当前栈顶下面的单元为新的栈顶。
                
                注意:
                 出栈后,SS:SP指向新的栈顶 1000EH,pop操作前的栈顶元素,
                 1000CH 处的2266H 依然存在 ,但是,它已不在栈中。
                
                 当再次执行push等入栈指令后,SS:SP移至1000CH,并在里面写入新的数据,
                 它将被覆盖。

       1.3.5 栈顶超界
             (1)  当栈满的时候再使用push指令入栈,
                  栈空的时候再使用pop指令出栈,
                  都将发生栈顶超界问题。

             (2)  栈顶超界是危险的。

            
             (3) 8086CPU的工作机理,只考虑当前的情况:
                  当前栈顶在何处;
                  当前要执行的指令是哪一条。
            
       1.3.6 栈与内存
            栈空间当然也是内存空间的一部分,它只是一段可以以一种特殊的方式进行访问的内存空间。

       1.3.7 push、pop指令     
            push和pop指令的格式(1)
                push 寄存器:将一个寄存器中的数据入栈
                pop寄存器:出栈,用一个寄存器接收出栈的数据    
                例如:push ax
                      pop bx

            push和pop指令的格式(2)
                push 段寄存器:将一个段寄存器中的数据入栈
                pop段寄存器:出栈,用一个段寄存器接收出栈的数据    
                例如:push ds
                      pop es

            push和pop指令的格式(3)
                push内存单元:将一个内存单元处的字入栈(栈操作都是以字为单位)
                pop 内存单元:出栈,用一个内存字单元接收出栈的数据
                例如:push [0]
                      pop [2]


       1.3.9 问题分析
           (1) 3.7         10000H ~ 1000FH   16字节
             mov ax,1000H
             mov ss,ax
             mov sp,0010H
             push ax
             push bx
             push ds

           (2) 3.8  10000H ~ 1000FH  ss:1000H  sp=?
            
             mov ax,1000H
             mov ss,ax
             mov sp,0010H
             mov ax,001AH
             mov bx,001BH
             push ax
             push bx
             sub ax,ax
             sub bx,bx
             pop bx
             pop ax
                 
            结论:
              从上面的程序我们看到,用栈来暂存以后需要恢复的寄存器中的内容时 ,
              出栈的顺序要和入栈的顺序相反,因为最后入栈的寄存器的内容在栈顶 ,
              所以在恢复时,要最先出栈。
              
          (3) 3.9
             mov ax,1000H
             mov ss,ax
             mov sp,0010H
             mov ax,001AH
             mov bx,001BH
             push ax
             push bx
             sub ax,ax
             sub bx,bx
             pop ax
             pop bx

        (4)3.10
           栈内存示意:
           10000H  66  
           10001H  22
           10002H      <- sp    

             mov ax,1000H
             mov ss,ax
             mov sp,0002H
             mov ax,2266H
             push ax

      1.3.11 栈段
          我们可以将长度为 N(N ≤64K )的一组地址连续、起始地址为16的倍数的内存单元,
          当作栈来用,从而定义了一个栈段。
          怎么算出来的
            
      1.3.13 段的综述
          我们可以将一段内存定义为一个段,用一个段地址指示段,
          用偏移地址访问段内的单元。这完全是我们自己的安排。
            
          我们可以用一个段存放数据,将它定义为“数据段”;
          我们可以用一个段存放代码,将它定义为“代码段”;
          我们可以用一个段当作栈,将它定义为“栈段”;
          
          我们可以这样安排,但若要让CPU按照我们的安排来访问这些段,就要:
            - 对于数据段,将它的段地址放在 DS中,用mov、add、sub等访
              问内存单元的指令时,CPU就将我们定义的数据段中的内容当作
              数据段来访问;
              
            - 对于代码段,将它的段地址放在 CS中,将段中第一条指令的偏移地址放在IP中,
              这样CPU就将执行我们定义的代码段中的指令;
          
            - 对于栈段,将它的段地址放在SS中,将栈顶单元的偏移地置放在 SP 中,
              这样CPU在需要进行栈操作的时候,比如执行 push、pop 指令等,就将我们
              定义的栈段当作栈空间来用。
 
         总结:
            一段内存,可以既是代码的存储空间,又是数据的存储空间,还可以是栈空间,
            也可以什么也不是。关键在于CPU中寄存器的设置,即:CS、IP、SS、SP、DS的指向。

        
        检测点3.2:
            (1)
             把2000H安排为栈段
            
             mov ax,2000H
             mov ss,ax
             mov sp,0010H
            
            
           (2)
             把1000H安排为栈段
            
             mov ax,1000H
             mov ss,ax
             mov sp,0H
            
            
        实验任务:        
             mov ax,[0]    ;ax = C0EAH
             add ax,[2]    ;ax = C0FCH
             mov bx,[4]    ;bx = 31F0H
             mov bx,[6]    ;bx = 30F0H
           
             push ax       ;sp = 00FEH  修改的内存单元地址是2200:00FE 内容为C0FCH
             push bx       ;sp = 00FCH,修改的内存单元地址是2200:00FC 内容为6021H
             pop  ax       ;sp = 00FCH, ax = 6021H
             pop  bx       ;sp = 00FEH, bx = COFCH

             push [4]      ;sp = 00FEH ,修改的内存单元地址是2200:00FE 内容为2F31H            
             push [6]      ;sp = 00FCH ,修改的内存单元地址是2200:00FC 内容为30F0H


            

四、汇编程序

      1.一个源程序从写出到执行的过程

        一个汇编语言程序从写出到最终执行的简要过程:
            编写-->编译-->链接-->执行

      2.可执行文件

          可执行文件中包含两部分内容:
         - 程序(从原程序中的汇编指令翻译过来的机器码)和数据(源程序中定义的数据)
         - 相关的描述信息(比如:程序有多大、要占多少内存空间等)

      3.执行可执行文件中的程序

         - 在操作系统中,执行可执行文件中的程序。
        
         - 操作系统依照可执行文件中的描述信息,将可执行文件中的机器码和数据加载入内存,
           并进行相关的初始化(比如:设置CS:IP指向第一条要执行的指令),然后由CPU执行程序。

      4.源程序

         4.1 汇编指令

             有对应的机器码的指令,可以被编译为机器指令,最终为CPU所执行。
    

         4.2 伪指令

             没有对应的机器码的指令,最终不被CPU所执行。
            

         4.3 谁来执行伪指令呢?

             伪指令是由编译器来执行的指令,编译器根据伪指令来进行
             相关的编译工作。
    

         4.4 源程序

             4.4.1  源程序中的“程序”
                   汇编源程序:
                        伪指令    (编译器处理)
                        汇编指令  (编译为机器码)

                  程序:源程序中最终由计算机执行、处理的指令或数据。
                
             4.4.2  我们可以将源程序文件中的所有内容称为源程序,
                    将源程序中最终由计算机执行处理的指令或数据 ,成为程序。
                   
             4.4.3  程序最先以汇编指令的形式存在源程序中,经编译、
                    连接后转变为机器码,存储在可执行文件中,

             4.4.4  标号
                      一个标号指代了一个地址。

             4.4.5  程序返回
                    应该在程序的末尾添加返回的程序段。
                        mov ax,4c00H
                        int 21H
                        用T命令担不执行程序中的每一条指令,
                        并观察每条指令的执行结果,到了 int 21,
                        我们要用P命令执行:
                     这两条指令所实现的功能就是程序返回

       4.5 编译

             配置环境变量 PATH = xxxx/masm6.15/        

             masm 查看是否配置成功
            
             masm  1.asm   编译程序,产生1.obj中间文件
            

         4.6 链接

              link   1.obj  链接程序,生成1.exe执行文件

         4.7 执行程序

                       

         4.8 可执行文件中的程序装入内存并运行的原理

             edit -> masm -> link -> command -> CPU
             ml 1.asm

        4.9 程序执行过程的跟踪

              >debug 1.exe
               -u cs:0   查看指令              

五、[bx]和loop指令

        1.[bx]和内存单元的描述

              1.1 [bx]是什么呢?
                 和[0]有些类似,[0]表示内存单元,它的偏移地址是0。

              1.2 我们要完整地描述一个内存单元,需要两种信息:
                (1)内存单元的地址;
                (2)内存单元的长度(类型)。
                    我们用[0]表示一个内存单元时,0 表示单元的偏移地址,
                    段地址默认在ds中,单元的长度(类型)可以由具体指令中的
                    其他操作对象(比如说寄存器)指出。
 
              1.3 [bx]同样也表示一个内存单元,它的偏移地址在bx中,
                   比如下面的指令:
                   mov ax,[bx]
                   mov al,[bx]

       2.loop指令(循环指令)

                 2.1 指令的格式是:loop 标号,CPU 执行loop指令的时候,要进行两步操作:
                    ① (cx)=(cx)-1;
                    ② 判断cx中的值,不为零则转至标号处执行程序,如果为零则向下执行。

                 2.2 从上面的描述中,我们可以看到,cx中的值影响着loop指令的执行结果。
                   通常(注意,我们说的是通常)我们用loop指令来实现循环功能,
                   cx 中存放循环次数。

               2.3 从上面的过程中,我们可以总结出用cx和loop 指令相配合实现循环
                   功能的三个要点:
                   (1)在cx中存放循环次数;
                   (2)loop 指令中的标号所标识地址要在前面;
                  (3)要循环执行的程序段,要写在标号和loop 指令的中间。

              2.4 用cx和loop指令相配合实现循环功能的程序框架如下:
                   mov cx,循环次数
                   s:循环执行的程序段
                   loop s

         问题分析:5.2
        
            //循环了236次
            assume cs:code
            code segment
                mov ax,0
                mov cx,236
              s:add ax,123
                loop s

                mov ax,4c00h
                int 21h
            code ends
            end
            
           //循环了123次
            assume cs:code
            code segment
                mov ax,0
                mov cx,123
              s:add ax,236
                loop s

                mov ax,4c00h
                int 21h
            code ends
            end

    3.loop和[bx]的联合应用

        在实际编程中,经常会遇到,用同一种方法处理地址连续的
        内存单元中的数据的问题。
        
        我们需要用循环来解决这类问题,同时我们必须能够在每次循环
        的时候按照同一种方法来改变要访问的内存单元的地址。

        这时,我们就不能用常量来给出内存单元的地址
        (比如[0]、[1]、[2]中,0、1、2是常量),而应用变量。
        
        “mov al,[bx]”中的 bx就可以看作一个代表内存单元地址的变量,
        我们可以不写新的指令,仅通过改变bx中的数值,改变指令访问的内存单元。

        

     4.段前缀

        指令“mov ax,[bx]”中,内存单元的偏移地址由bx给出,
        而段地址默认在ds中。
        
        我们可以在访问内存单元的指令中显式地给出内存单元
        的段地址所在的段寄存器。

        这些出现在访问内存单元的指令中,用于显式地指明内存单元的段
        地址的“ds:”、“cs:”、“ss:”或“es:”,在汇编语言中称为段前缀。
        

六、更灵活的定位内存地址的方法

    1 and和or指令        

        (1)and 指令:逻辑与指令,按位进行与运算。
            如 mov al, 01100011B
               and al, 00111011B
          执行后  al = 00100011B
        通过该指令可将操作对象的相应位设为0,其他位不变。
        例如: al = 1111 1111B
          将al的第6位设为0:  and al,1011 1111B
          将al的第7位设为0:  and al,0111 1111B
          将al的第0位设为0:  and al,1111 1110B          
       
         同C语言里面的"&"
        
         (2)or 指令:逻辑或指令,按位进行或运算
             如 mov al, 01100011B
                 or al, 00111011B
           执行后  al = 01111011B
           通过该指令可将操作对象的相应位设为1,其他位不变。
           例如:al = 0000 0000B
           将al的第6位设为1:  and al,0100 0000B
           将al的第7位设为1:  and al,1000 0000B
           将al的第0位设为1:  and al,0000 0001B        
          
          同C语言里面的"|"
       

    2.[bx+idata]  

        在前面,我们可以用[bx]的方式来指明一个内存单元,
        我们还可以用一种更为灵活的方式来指明内存单元:
       
        [bx+idata]表示一个内存单元,它的偏移地址为(bx)+idata
        (bx中的数值加上idata)。

        我们看一下指令mov ax,[bx+200]的含义:
        将一个内存单元的内容送入ax,这个内存单元的长度为2字节(字单元),
        存放一个字,偏移地址为bx中的数值加上200,段地址在ds中。
        数学化的描述为: (ax)=((ds)*16+(bx)+200)

     3.SI和DI

       SI和DI是8086CPU中和bx功能相近的寄存器,
       SI和DI不能够分成两个8 位寄存器来使用。
       下面的三组指令实现了相同的功能:
        (1) mov bx,0
              mov ax,[bx]
        (2) mov si,0
              mov ax,[si]
        (3) mov di,0
              mov ax,[di]

         (4) mov bx,0
              mov ax,[bx+123]
         (5) mov si,0
              mov ax,[si+123]
         (6) mov di,0
              mov ax,[di+123]

     4. [bx+si]和[bx+di]    

          在前面,我们用[bx(si或di)]和[bx(si或di)+idata] 的方式来指明
          一个内存单元,我们还可以用更灵活的方式:
            [bx+si]
            [bx+di]

            [bx+si]表示一个内存单元,它的偏移地址为(bx)+(si)(即bx中的数值加上si中的数值)。
            

     5. [bx+si+idata]和[bx+di+idata]  

        [bx+si+idata]表示一个内存单元
           它的偏移地址为(bx)+(si)+idata。
          (即bx中的数值加上si中的数值再加上idata)


     6.如果我们比较一下前而用到的几种定位内存地址的方法(可称为寻址方式)

       就可以发现有以下几种方式:
        (1)[iata] 用一个常量来表示地址,可用于直接定位一个内存单元;
        (2)[bx]用一个变量来表示内存地址,可用于间接定位一个内存单元;
        (3)[bx+idata] 用一个变量和常量表示地址,可在一个起始地址的基础上用变量间接定位一个内存单元;
        (4)[bx+si]用两个变量表示地址;
        (5)[bx+si+idata] 用两个变量和一个常量表示地址。
                 直接寻址
                 寄存器间接寻址
                 寄存器相对寻址
                 基址变址寻址
                 相对基址变址寻址
            

七、转移指令的原理

           8086CPU的转移指令分为以下几类:
           无条件转移指令 (如:jmp)
           条件转移指令
           循环指令(如:loop)
           过程   (函数调用)
           中断  
        

     1.jmp 指令

        jmp为无条件转移,可以只修改IP,也可以同时修改CS和IP;
        jmp指令要给出两种信息:
          转移的目的地址
          转移的距离(段间转移、段内短转移,段内近转移)

           
        (1)jmp short 标号(转到标号处执行指令)
         这种格式的 jmp 指令实现的是段内短转移,它对IP的修改范围为
         -128~127,也就是说,它向前转移时可以最多越过128个字节,
         向后转移可以最多越过127个字节。

        assume cs:codesg
        codesg segment
          start:mov ax,0
                  jmp short s
                  add ax,1
               s:inc ax
        codesg ends
        end start

           左面的程序执行后, ax中的值为 1 ,因为执行  jmp short s 后 ,
           越过了add ax,1 ,IP 指向了标号 s处的 inc ax。也就是说,
           程序只进行了一次ax加1操作。  

       (2)依据位移进行转移的jmp指令
         jmp short s指令的读取和执行过程:
        (1)(CS)=0BBDH,(IP)=0006,CS:IP指向EB 03(jmp short s的机器码);
        (2)读取指令码EB 03进入指令缓冲器;
        (3)(IP)=(IP)+所读取指令的长度=(IP)+2=0008,CS:IP指向add ax,1;
        (4)CPU指行指令缓冲器中的指令EB 03;
        (5)指令EB 03执行后,(IP)=000BH,CS:IP指向inc ax。
       
       注意: 要转移的目的地址是CS:000B,而CPU 执行 EB 03时,当前的(IP)=0008,
            如果将当前的IP值加3,使(IP)=000BH,CS:IP就可以指向目标指令。

         在转移指令EB 03中并没有告诉CPU要转移的目的地址,却告诉了
         CPU 要转移的位移,即将当前的IP向后移动3个字节。

           补码:
                -9的补码怎么算出来的
             +9 原码: 0000  1001       =>09H
            
                反码: 1111  0110
                                 + 1
                       ——————————————
              -9补码:  1111     0111       =>F7H                   
       
       结论:
          CPU执行 jmp short 标号 指令时并不需要转移的目的地址,
          只需要知道转移的位移就行了。

         (3) 实际上,指令“jmp short 标号”的功能为(IP)=(IP)+8位位移。
             (1)8位位移=“标号”处的地址-jmp指令后的第一个字节的地址;
             (2)short指明此处的位移为8位位移;
             (3)8位位移的范围为-128~127,用补码表示
             (4)8位位移由编译程序在编译时算出。
      
        
         (4) 指令“jmp near ptr 标号”的说明:
            (1)16位位移=“标号”处的地址-jmp指令后的第一个字节的地址;
            (2)near ptr指明此处的位移为16位位移,进行的是段内近转移;
            (3)16位位移的范围为
               -32769~32767,用补码表示;
            (4)16位位移由编译程序在编译时算出。
            
            
         (5)jmp near ptr 标号
               它实现的时段内近转移。
                指令“jmp near ptr 标号”的功能为:
                (IP)=(IP)+16位位移。
    
               指令“jmp near ptr 标号”的说明:
                (1)16位位移=“标号”处的地址-jmp指令后的第一个字节的地址;
                (2)near ptr指明此处的位移为16位位移,进行的是段内近转移;
                (3)16位位移的范围为
                   -32769~32767,用补码表示;
                (4)16位位移由编译程序在编译时算出。
     
         (6)指令 “jmp far ptr 标号”
                实现的是段间转移,又称为远转移。

                指令 “jmp far ptr 标号” 功能如下:
                (CS)=标号所在段的段地址;
                (IP)=标号所在段中的偏移地址。
                far ptr指明了指令用标号的段地址和偏移地址修改CS和IP。
            
              
          (7) 指令格式:jmp 16位寄存器
             功能:IP =(16位寄存器)
             这种指令我们在前面的课程(参见2.11节)中已经讲过,这里就不再详述。
    
          (8)转移地址在内存中的jmp指令有两种格式:
                (1) jmp word ptr 内存单元地址(段内转移)
                    功能:从内存单元地址处开始存放着一个字,是转移的目的偏移地址。
                    
                    内存单元地址可用寻址方式的任一格式给出。        
            
                mov ax,0123H
                mov ds:[0],ax
                jmp word ptr ds:[0]
                执行后,(IP)=0123H

            
                mov ax,0123H
                mov [bx],ax
                jmp word ptr [bx]
                执行后,(IP)=0123H

            
                2) jmp dword ptr 内存单元地址(段间转移)
                    功能:从内存单元地址处开始存放着两个字,高地址处的字是转移的目的段地址,
                    低地址处是转移的目的偏移地址。
                        (CS)=(内存单元地址+2)
                        (IP)=(内存单元地址)
                内存单元地址可用寻址方式的任一格式给出。
            
                    mov ax,0123H
                    mov ds:[0],ax
                    mov word ptr ds:[2],0
                    jmp dword ptr ds:[0]
                    执行后,
                    (CS)=0
                    (IP)=0123H
                    CS:IP指向0000:0123。

            
                    mov ax,0123H
                    mov [bx],ax
                    mov word ptr [bx+2],0
                    jmp dword ptr [bx]
                    执行后,
                    (CS)=0
                    (IP)=0123H
                    CS:IP指向0000:0123。

            

        2. jcxz指令

            (1)jcxz指令为有条件转移指令,所有的有条件转移指令都是短转移,在对应的机器码中
                包含转移的位移,而不是目的地址。对IP的修改范围都为-128~127。
            
                指令格式:jcxz 标号  
                   (如果(cx)=0,则转移到标号处执行。)    
            
            
            (2)jcxz 标号 指令操作:
                - 当(cx)=0时,(IP)=(IP)+8位位移)
                    8位位移=“标号”处的地址-jcxz指令后的第一个字节的地址;
                    8位位移的范围为-128~127,用补码表示;
                    8位位移由编译程序在编译时算出。
                    当(cx)=0时,什么也不做(程序向下执行)。

            
                - jcxz 标号 指令操作:
                    当(cx)=0时,(IP)=(IP)+8位位移)
                    8位位移=“标号”处的地址-jcxz指令后的第一个字节的地址;
                    8位位移的范围为-128~127,用补码表示;
                    8位位移由编译程序在编译时算出。
                    
                    当(cx)!=0时,不进行跳转(程序向下执行)。

                我们从 jcxz的功能中可以看出,指令“jcxz 标号”的功能相当于:
                if((cx)==0)
                    jmp short 标号;
                (这种用C语言和汇编语言进行的综合描述,或许能使你对有条
                件指令理解得更加清楚。)

            
            检测点9.2
            s:mov ch,0
              mov cl,[bx]   
                 jcxz ok       ;当cx=0时,CS:IP指向ok       
              inc bx
            
            

        3.loop指令

           (1) loop指令为循环指令,所有的循环指令都是短转移,在对应的机
               器码中包含转移的位移,而不是目的地址。对IP的修改范围
               都为-128~127。
            
            指令格式:loop 标号
                ((cx))=(cx)-1,如果(cx)≠0,转移到标号处执行。    
            
            (2)loop 标号 指令操作:
                (1)(cx)=(cx)-1;
                
                (2)如果(cx)≠0,(IP)=(IP)+8位位移。
                    8位位移 = “标号”处的地址 - loop指令后的第一个字节的地址;
                    8位位移的范围为-128~127,用补码表示;
                    8位位移由编译程序在编译时算出。
                    
                    当(cx)=0,什么也不做(程序向下执行)
            
                    我们从loop的功能中可以看出,指令“loop 标号”的功能相当于:
                    (cx)--;
                    if((cx)≠0)
                     jmp short 标号

            
             检点的9.3
                inc cx
            

        4. 根据位移进行转移的意义

          前面我们讲到:
             jmp short 标号
             jmp near ptr 标号
             jcxz 标号
             loop 标号
           等几种汇编指令,它们对 IP的修改是根据转移目的
           地址和转移起始地址之间的位移来进行的。在它们
           对应的机器码中不包含转移的目的地址,而包含的起
           始地址到目的地址的位移。
        
           这样设计,方便了程序段在内存中的浮动装配。
    
    

        5.编译器对转移位移超界的检测

            注意,根据位移进行转移的指令,它们的转移范围受到转移位移的限制,
            如果在源程序中出现了转移范围超界的问题,在编译的时候,编译器将
            报错。
 

八、 call 和 ret 指令

     1.ret 和 retf

        (1)ret指令用栈中的数据,修改IP的内容,从而实现近转移;
              CPU执行ret指令时,进行下面两步操作:
                (1)(IP)=((ss)*16+(sp))
                (2)(sp)=(sp)+2

         
        (2)retf指令用栈中的数据,修改CS和IP的内容,从而实现远转移;
             CPU执行retf指令时,进行下面四步操作:
                (1)(IP)=((ss)*16+(sp))
                (2)(sp)=(sp)+2
                (3)(CS)=((ss)*16+(sp))
                (4)(sp)=(sp)+2

            可以看出,如果我们用汇编语法来解释ret和retf指令,则:
            CPU执行ret指令时,相当于进行:
             pop IP
            
            CPU执行retf指令时,相当于进行:
             pop IP
             pop CS

          
            检测点10.1
            
            分析:retf执行后 CS = 1000H  IP:0000H
            
            mov ax,1000H  ;段地址cs入栈
            
            mov ax,0000H  ;偏移地址ip入栈
            
            
            执行retf指令时,相当于进行:
             pop IP
             pop CS
            根据栈先进后出原则,应先将段地址CS入栈,
            再将偏移地址ip入栈
            
          

       2. call 指令

          (1) CPU执行call指令,进行两步操作:
                (1)将当前的 IP 或 CS和IP 压入栈中;
                (2)转移。

              call 指令不能实现短转移,除此之外,call指令实现转移的方法
              和 jmp 指令的原理相同,下面的几个小节中 ,我们以给出转移目
              的地址的不同方法为主线,讲解call指令的主要应用格式。


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

           (3)CPU执行此种格式的call指令时,进行如下的操作:
                (1) (sp) = (sp) – 2
                        ((ss)*16+(sp)) = (IP)
                        
                (2) (IP) = (IP) + 16位位移
 
            

       3. 依据位移进行转移的call指令

            (1)call 标号
                16位位移 = “标号”处的地址 - call指令后的第一个字节的地址;
                16位位移的范围为 -32768~32767,用补码表示;
                16位位移由编译程序在编译时算出。

            (2)如果我们用汇编语法来解释此种格式的 call指令,则:
               CPU 执行指令“call  标号”时,相当于进行:
                    push IP
                    jmp near ptr 标号           
 
            检测点10.2
            mov ax,0      ;ax=0,ip指向1000:3
            call s        ;ax=0,
                          ;读取call指令后ip=6, push ip
                          ;jmp near ptr s
            inc ax        ;被跳过的代码
            s:pop ax      ;ax = 6
            
            答案:ax = 6
            
            

        4. 转移的目的地址在指令中的call指令

            前面讲解的call指令,其对应的机器指令中并没有转移的目的地址 ,
            而是相对于当前IP的转移位移。
            
            指令“call far ptr 标号”实现的是段间转移。
            
            
            CPU执行“call far ptr 标号”这种格式的call指令时的操作:
            (1) (sp) = (sp) – 2
                ((ss) ×16+(sp)) = (CS)
                
                (sp) = (sp) – 2
                ((ss) ×16+(sp)) = (IP)
                
            (2) (CS) = 标号所在的段地址
                (IP) = 标号所在的偏移地址

            
                
            从上面的描述中可以看出,如果我们用汇编语法来解释此种格式的 call 指令,则:
               CPU 执行指令 “call far ptr 标号” 时,相当于进行:
                   push CS
                   push IP
                   jmp far ptr  标号
    
            检测点10.3
            
            mov ax,0        ;ax=0, ip指向1000:3
            call far ptr s  ;读取call之后,ip=8, push cs push ip,ip指向1000:8
                            ;执行call后, ip=9 cs=1000H
            inc ax          ;被跳过的指令
            s:pop ax        ;ax=8H
            add ax,ax       ;ax=10H
            pop bx          ;bx = 1000H
            add ax,bx       ;ax = 1010H            
                            
            答案:ax = 1010H    
        
      

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

             (1)指令格式:call 16位寄存器
                功能:
                    (sp) = (sp) – 2
                    ((ss)*16+(sp)) = (IP)
                    (IP) = (16位寄存器)

             (2)指令格式:call 16位寄存器
                 汇编语法解释此种格式的 call 指令,CPU执行call 16位reg时,相当于进行:
                     push IP
                     jmp 16位寄存器

            检测点10.4
            mov  ax,6    ;ax=6,ip指向1000:3    
            call ax         ;读取call指令后,ip=5,push ip
                         ;执行call指令后,ip=6
            inc ax       ;被跳过的代码
            mov bp,sp    ;将得到栈顶指针赋值给bp
            add ax,[bp]  ;ax = 6 + 5 = 0bH              
            
            "call ax" 是先将该指令后的第一个字节偏移地址ip入栈,
            再转到偏移地址为ax出执行指令        
            
        

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

            转移地址在内存中的call指令有两种格式:
               (1) call word ptr 内存单元地址

               (2) call dword ptr 内存单元地址
          
           
           (1) call word ptr 内存单元地址
               汇编语法解释:
                       IP
                    jmp word ptr 内存单元地址    
            
            
             call word ptr 内存单元地址(示例)
                比如下面的指令:
                 mov sp,10h
                 mov ax,0123h
                 mov ds:[0],ax
                 call word ptr ds:[0]
                执行后,(IP)=0123H,(sp)=0EH

            
            (2) call dword ptr 内存单元地址
                汇编语法解释:
                     push CS
                     push IP
                     jmp dword ptr 内存单元地址
            
            
            call 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

         持续更新!