8086汇编


本笔记是笔者观看小甲鱼老师(鱼C论坛)《零基础入门学习汇编语言》系列视频的笔记,是王爽所著的《汇编语言》的简单版,感谢ttps://my.csdn.net/baidu_36313748的,在建议此感谢小甲鱼和像他一样共享资源、帮助他人的筒子们==本文比较长,由于笔者个人能力有限,错漏在所难免,欢迎读者们批评指正。

一、基础知识

引言

  • 基本了解硬件系统的结构;
  • 利用硬件系统的编程结构和指令集,有效灵活地控制系统进行工作。

1.1 机器语言

  • 机器语言是机器指令的集合。电子计算机的机器指令是一系列二进制数字。计算机将之转换为一系列高低电平脉冲信号来驱动硬件工作的。

1.2 汇编语言的产生

  • 由于机器语言指令都是由01组成,难以编写,记忆和维护程序.所以汇编语言为了解决这一问题产生。汇编语言的主体是汇编指令,汇编指令是机器指令的助记符。
  • 寄存器: CPU中存储数据的器件,一个CPU中有多个寄存器。

1.3 汇编语言的组成

  • 1、汇编指令(机器码的助记符,有对应的机器码);
  • 2、伪指令(由编译器执行)和其他符号(由编译器识别)。

1.4 存储器

  • CPU工作需要指令和数据,指令和数据存储在存储器中。

1.5 指令和数据

  • 在内存或者磁盘中存储的都是为二进制信息,指令和数据由我们设定(走的总线)

1.6 存储单元

  • 存储器被划分为若干个存储单元,每个存储单元从0开始顺序编号。
  • B、KB、MB、GB、TB等单位。

1.7 CPU对存储器的读写

  • CPU要对数据进行读写,必须和外部器件进行以下三类信息的交互:

    • 1、存储单元的地址(地址信息);
    • 2、器件的选择、读或写命令(控制信息);
    • 3、读或写的数据(数据信息) 。
  • 总线是连接CPU和其他芯片的导线,逻辑上分为地址总线数据总线控制总线

  • CPU从内存单元中读写数据的过程:

    • 1、CPU通过地址线将地址信息发出;
    • 2、CPU通过控制线发出内存读命令,选中存储器芯片,并通知它将要从中读或写数据;
    • 3、存储器将相应的地址单元中的数据通过数据线送入CPU或CPU通过数据线将数据送入相应的内存单元。

1.8 地址总线

  • CPU是通过地址总线指定存储单元,地址总线传送的能力决定了CPU对存储单元的寻址能力。(一般32位CPU,寻址能力为2^32=4G)

1.9 数据总线

  • CPU通过数据总线来与内存等器件进行数据传送,数据总线的宽度决定了CPU和外界的数据传送速度。

1.10 控制总线

  • 控制总线是一些不同控制的集合,CPU通过控制总线对外部器件的控制。控制总线的宽度决定了CPU对外部器件的控制能力。

小结

  • 1、汇编指令时机器指令的助记符,与机器指令一一对应。
  • 2、每一种CPU都有自己的汇编指令集。
  • 3、CPU可以直接使用的信息在存储器中存放。
  • 4、在存储器中指令和数据都是二进制信息。
  • 5、存储单元从0开始顺序编号。
  • 6、一个存储单元可以存储8个bit。
  • 7、B、KB、MB、GB等单位之间的转换。
  • 8、CPU管脚和总线相连。总线的宽度表示CPU不同方面的性能:
    • 地址总线的宽度决定了CPU的寻址能力;
    • 数据总线的宽度决定了CPU与其他器件进行一次数据传送的量;
    • 控制总线宽度决定了CPU对系统中其他器件的控制。

检测点 1.1

1.11 内存地址空间(概述)

  • CPU可寻的内存单元构成这个CPU的内存地址空间。例如一个CPU的地址总线宽度为10,那么可以寻址的1024个内存单元构成了这个CPU的内存空间。

1.12 主板

  • 主板主板,主要的电路板 :laughing:

1.13 接口卡

  • CPU通过接口卡间接控制外部设备。

1.14 各类存储器

  • 随机存储器RAM(主板上的RAM、拓展插槽上的RAM和接口卡上的RAM)和只读存储器器ROM(装有BIOS的ROM)。

1.15 内存地址空间

  • 各类存储器在物理上是独立的,但是:

    • 1、都和CPU的总线相连;
    • 2、 CPU对他们进行读或写的时候都通过控制线发出的内存读写命令。

  • 不同的计算机系统的内存地址空间分配情况是不同的。

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

引言

  • CPU由运算器、控制器、寄存器 等器件组成,靠内部总线相连。
  • 内部总线实现CPU内部各器件之间的联系;外部总线实现CPU和主板上其他器件的联系。
  • 在CPU中:
    • 运算器进行信息处理;
    • 寄存器进行信息存储;
    • 控制器控制各种器件进行工作;
    • 内部总线连接各种器件在它们之间进行数据的传送。

2.1 通用寄存器

  • 8086有14个寄存器:

    • AX、BX、CX、DX、SI、DI、SP、BP、IP、CS、SS、CS、ES、PSW
  • AX、BX、CX、DX通常用来存放一般性数据,被称为通用寄存器。
  • 16位寄存器所能存储的数据最大值为2^16^-1 。
  • 为保证兼容性,8086 CPU的通用寄存器可以分为两个独立的8位寄存器使用。例: AX可分为AH和AL。

2.2 字在寄存器中的存储

  • 8086 CPU所有的寄存器是16位,可以存放2个字节(一个字)。

  • 一字节由8 bit 组成,可以存在8位寄存器中。

  • 字(word)是两字节,16位。

2.3 几条汇编指令

  • 汇编指令对大小写不敏感

    汇编指令举例

汇编指令 控制CPU完成的操作 用高级语言的语法描述
mov ax,18 将8送入AX AX=18
mov ah,78 将78送入AH AH=78
add ax,8 将寄存器AX中的数值加上8结果存入AX中 AX=AX+8
mov ax,bx 将寄存器BX中的数据送入寄存器AX AX=BX
add ax,bx 将AX,BX中的内容相加结果存入AX中 AX=AX+BX

检测点 2.1

2.4 物理地址

  • 所有的内存单元构成一个一维的线性存储空间。
  • CPU访问内存单元时要给出内存单元的唯一地址就是物理地址。

2.5 16位结构的CPU

  • 1、运算器一次最多可以处理16位数据。
  • 2、 寄存器的最大宽度为16位。
  • 3、寄存器和运算器之间的通路是16位。

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

  • 8086有20位的地址总线,可以传送20位地址,寻址能力为1M;但8086内部为16位结构,只能传送16位的地址。
  • 8086CPU采用一种在内部用两个16位地址合成的方法来形成一个20位的物理地址。

  • 8086CPU读写内存的步骤:

    • 1、CPU中的相关部件提供段子和偏移地址这两个16位的地址;
    • 2、段地址和偏移地址通过内部总线送入到一个称为地址加法器的部件;
    • 3、地址加法器将两个16位地址合并成一个20位的地址;
    • 4、地址加法器通过内部总线将20位物理地址送送入输入输出地址;
    • 5、输入输出控制电路将20位物理地址送上地址总线;
    • 6、20位物理地址被地址总线传送到存储器。
  • 地址加法器工作原理:物理地址=段地址*16+偏移地址。


- 段地址*16就是数据左移4位(二进制)

移位位数 二进制 十六进制 十进制
0 10B 2H 2
1 100B 4H 4
2 1000B 8H 8
3 10000B 10H 16
4 100000B 20H 32
  • 一个数据的二进制形式左移N位,相当于该数据乘以2的N次方。一个数据X进制形式左移N位,相当乘以NX。

2.7 段地址*16+偏移地址=物理地址

  • CPU可以通过不同的段地址和偏移地址形成一个相同的物理地址。

段地址*16是移位

2.8 段的概念

  • 人为定义的,将若干地址连续的内存单元看作一个段。用段地址*16定位段的起始地址(基址),用偏移地址定位段中的内存单元。

  • 一个段的起始地址是16的倍数。偏移地址为16位,寻址能力为64K,所以段的最大长度也是64K。

检测点 2.2

2.9 段寄存器

  • 8086 CPU有4个段寄存器:CS(代码段)、DS(数据段)、SS(堆栈段)、ES(附加段),这4个段提供给8086CPU内存单元的段地址。

2.10 CS和IP

  • CS(代码段寄存器)IP(指令指针寄存器) 是8086CPU中最关键的寄存器,它们指示了CPU当前要读取指令的地址。在任意时刻CPU将CS:IP指向的内容当作指令执行。
  • 8086CPU工作过程的简要概述:

    • 1、从CS:IP指向内存单元读取指令,读取的指令进入指令缓冲器;

      8086PC机刚开始启动时,CPU从内存FFFF0h单元中读取指令执行,FFFF0h单元中的指令时8086PC机开机后执行的第一条指令。

    • 2、 IP=IP+所读取指令的长度,从而正确的指向下一条指令;

    • 3、执行指令。转到步骤1,周而复始。

2.11 修改CS、IP的指令

  • mov指令(传送指令) 可以改变8086CPU大部分寄存器的值,但不能用于设置CS、IP的值。
  • jmp指令(转移指令) 可以用来同时修改CS和IP的值,格式为
    jmp 段地址:偏移地址;同时修改CS和IPjmp 某一合法寄存器;则是仅修改IP

2.12 代码段

  • 对于8086PC机,在编程时可以将长度为N(N小于等于64KB)的一组代码存在一组地址连续、起始地址为16的倍数的内存单元中,这段内存是用来存放代码的,从而定义了一个代码段。
  • 利用CS:IP来指向内存单元从而让CPU执行其中的内容。

检测点 2.3

使用Debug

windows xp系统自带debug,请使用xp以上系统的读者执行自行下载debug.exe和dosbox,使用方法笔者不再赘述,在dosbox中可以使用debug。

  • 可以使用汇编金手指查阅指令。
  • R命令查看、改变CPU寄存器的内容;
  • D命令查看内存中的内容;
  • E命令改写内存中的内容;
  • U命令将内存中的机器指令翻译成汇编指令;
  • T命令执行一条机器指令;
  • G命令跳转到偏移地址;
  • P命令结束循环或者是int 21H时是退出程序;
  • A命令是以汇编指令的格式在内存中写入一条机器指令。

三、寄存器(内存访问)

3.1 内存中字的存储

  • 字是两个字节,要用两个地址连续的内存来存放,字的低位字节存在低地址中,高位字节存放在高地址单元中。

3.2 DS和[address]

  • DS通常存放要访问的数据的段地址。
  • 8086 CPU由于硬件的设计不支持将数据直接送入段寄存器的操作。

    数据 -> 通用寄存器 -> 段寄存器

  • [ ]里边的数据代表偏移地址值

  • mov指令:
    • 将数据直接送入寄存器;
    • 将一个寄存器或内存单元中的内容送入另一个寄存器;
  • mov指令格式:
mov 寄存器名,内存单元

3.3 字型的传送

  • 高地址单元和高8位寄存器,低地址单元和低8位寄存器相对应。

3.4 mov、add、sub指令

  • 有两个操作对象,jmp只有一个操作对象。
  • 使用汇编金手指查阅指令
  • mov指令的几种形式
mov 寄存器,数据;mov ax,8
mov 寄存器,寄存器;mov ax,bx
mov 寄存器,内存单元;mov ax,[0]
mov 内存单元,寄存器;mov [0],ax
mov 段寄存器,寄存器;mov ds,ax
mov 寄存器,段寄存器;mov ax,ds……
  • add指令的几种形式
add 通用寄存器,数据
add 通用寄存器,通用寄存器
add 通用寄存器,内存单元
add 内存单元,寄存器
  • sub指令的几种形式
sub 通用寄存器,数据
sub 通用寄存器,通用寄存器
sub 通用寄存器,内存单元
sub 内存单元,通用寄存器  

3.5 数据段

  • 对于8086PC机,在编程时可以将长度为N(N小于等于64KB)的一组代码存在一组地址连续、起始地址为16的倍数的内存单元中,这段内存是用来存放数据的,从而定义了一个数据段。
  • 可以通过在DS中存放数据段的段地址,用相关的指令访问数据段中的具体单元来访问数据段中的数据。

检测点 3.1

3.6 栈

  • 具有特殊的访问方式的存储空间,也是内存空间的一部分,数据先进后出。
  • 有两个基本操作:
    • 入栈:将一个新的元素放到栈顶;
    • 出栈:从栈顶取出一个元素。
  • 栈顶元素最后入栈最先出栈。

3.7 8086 CPU提供的栈机制

  • 现今的CPU都有栈的设计,基于8086CPU编程可以将一段内存当作栈来使用。
  • 8086CPU的入栈(PUSH)POP(出栈),以字为单位。
    • push ax 将寄存器ax中的数据送入栈
    • pop ax 从栈顶取出数据送入ax
  • 段寄存器SS存放栈顶的段地址,寄存器SP存放栈顶的偏移地址。任意时刻SS:SP指向栈顶元素。push时SP先自减法后写内存,pop先读内存sp后自加。
  • pop之后数据还是存在内存中,push时覆盖。

    CS和IP存放当前指令的段地址和偏移地址。

3.8 栈顶越界的问题

  • 栈是空的,则SP指向栈底+1的内存。
  • 8086 CPU只纪录栈顶,栈空间由自己控制。栈顶越界问题导致溢出漏洞。
  • 8086CPU只考虑当前的情况:
    • 当前栈顶在何处;
    • 当前要执行的指令时哪一条。

3.9 push、pop指令

  • 可以直接对段寄存器使用。
;push和pop格式
push 寄存器
pop 寄存器
push 段寄存器
pop 段寄存器
push 内存单元
pop 内存单元
  • 通用寄存器命名是x结尾的,段寄存器是以s结尾。
  • CPU在执行指令时,数据的段地址是从DS中获得,代码是在CS中获得,栈地址是从SS获得。

3.10 栈段

  • 对于8086PC机,在编程时可以将长度为N(N小于等于64KB)的一组代码存在一组地址连续、起始地址为16的倍数的内存单元中,这段内存是当作栈来用,从而定义了一个栈段。
  • 寄存器清零可用sub ax,ax或者直接赋值0,常见的也有使用xor。
  • 当栈空间定义为最大时,栈为空时SP=0。

检测点 3.2


四、第一个程序

引言

编写完成的汇编语言程序,用编译器编译成可执行文件并在操作系统中运行。

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

  • 编写

    • 用编辑器(Sublime Text、Nodepad++、UltraEdit)编写,文件后缀为.asm。
  • 编译链接

    • 使用MASM.EXE编译生产obj(目标文件)。masm也请读者自行搜索下载。
    • LINKE.EXE对目标文件进行连接生产可在操作系统中直接运行的可执行文件。

    可执行文件包含程序(机器码)、数据(源程序中定义的数据)和相关的描述信息。

  • 执行

    • 操作系统中依照可执行文件中的描述信息将可执行文件中的机器码和数据加载入内存并进行相关的初始化,然后CPU执行。
  • 4.2 源程序

    • 汇编指令:有对应的机器码的指令,编译为机器码被CPU执行
    • 伪指令:没有对应的机器码,不被CPU所执行,由编译器执行来进行相关的编译工作。
      • segment和ends是用来定义一个段的,是成对使用的伪指令,再写可被编译器编译的汇编程序是必须要用的。
    assume cs:codesg ;假设代码段的名称为codesg
    codesg segment ;定义一个codesg段
    mov ax,0123H
    mov bx,0456H
    add ax,bx
    add ax,ax
    mov ax,4c00h
    int 21h
    codesg ends ;codesg段结束
    end ;是个伪指令,程序的结束标记

    assume用来加上某一段寄存器和程序中的某一用segment……ends定义的段相关联。通过assume说明这种关联,在需要的情况下编译程序可以将段寄存器和某一个具体的段相联系。
    - 一个汇编程序是由多个段组成。一个有意义的汇编程序中至少要用一个段来存放代码。
    - 程序与源程序

    • 标号:指代地址
    • 程序的结构

    • 小练习:

    ;编程运算2^3
    assume cs:abc ;段与寄存器关联abc segment ;定义一个段,名称为abc
    mov ax,2;写入汇编指令
    add ax,ax
    add ax,axabd ends
    end ;程序结束处
    • 程序的返回:一个程序结束后将CPU的控制权交还给使它得以运行的程序的过程。应该在程序的末尾添加返回的程序段。

      codesg:放在segment前面,作为一个段的名称,这个段的名称最终将被编译、连接程序,称为一个段的段地址 。

    mov ax,4c00H
    int 21H ;第21号中断
    ;这两条指令说实现的功能就是程序返回。
    • 语法错误和逻辑错误

    4.3 编辑源程序

    • 使用编辑器编辑,扩展名为.asm
        assume cs:ABCABC segmentmov ax,2add ax,axadd ax,axmov ax,4c00Hint 21hABC endsend

    4.4 编译

    • masn和 1.asm在同一目录中,dos下使用masm 1.asm命令即可生产1.obj文件。

    4.5 连接

    • link 1.obj,生成exe文件,摁enter忽略编译程序提示输入的信息。

    • 当源程序很大时,可以将它分成多个源程序文件编译,每个源程序编译成目标文件后再用连接程序将他们连接到一起,生成一个可执行文件。或者程序中调用了某个库文件中的子程序,需要将这个库文件和该目标文件连接到一起,生成一个可执行文件。或者一个源程序编译后得到存有机器码的目标文件,目标文件中的有些内容还不能直接生成可执行文件,连接程序将此内容处理为最终的可执行文件信息。

    4.6 简化编译和连接

    • 使用ml命令,ml 1.asm

    4.7 exe的执行

    • 为兼容16位的程序,使用dosbox运行。

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

    • 在dos中可执行文件中的程序p1若要运行吗必须有一个正在运行的程序p2将p1从可执行文件中加载如内存,将CPU的控制权交给它,p1才能得以运行;当p1运行完毕后,应该将CPU的控制权交还给使它de’yi 运行的程序p2。
    • 汇编程序从写出到执行的过程:编程 -> 编译 -> 连接 -> 加载 -> 内存中的程序 -> 运行
    • 在dos系统中.exe文件中的加载过程

    4.9 程序执行过程的跟踪

    • 使用debug(xp以上的系统在dosbox中使用)来跟踪一个程序的运行过程。


    五、[BX]和loop指令

    引言

    • 约定符号()来表示一个寄存器或者一个内存单元中的内容。例如(ax)=0010H表示ax中的内容为0010H;(21000H)=0010H,表示2000:1000处的内容为0010H。
    • 约定符号idata表示常量。

    5.1 [BX]

    • inc指令是自增1的意思
    • 和[0]有些类似,[0]表示内存单元,它的偏移地址是0。[bx]也是表示一个内存单元,它的内存偏移地址在bx中。
    mov bx,0
    mov ax,[bx]
    mov al,[bx]
    • 用以下两种信息描述一个内存单元:

      • 1、内存单元的地址;
      • 2、内训单元的长度(类型)。

        我们用[0]表示一个内训单元时,0表示单元的偏移地址,段地址默认在DS中,单元的长度(类型)可以由具体指令中的其他的操作对象(比如说寄存器)指出。

    mov ax,[0];0对应的字单元,主要单位要看操作对象(寄存器)
    mov al,[0];字节

    5.2 loop指令

    • 指令的格式是loop 标号。CUP执行loop指令时要进两步操作:

      • CX中存放循环的次数,执行时CX中的内容自减1。相当于C的do while
      • 判断CX中的值,不为0则转至标号处执行程序,为0则向下执行。
    • 通常loop指令来实现循坏功能CX中存放循环的次数。
    assume cs:codecode segmentmov ax,2add ax,axmov ax,4c00Hint 21H
    code ends
    end
    ;计算2^3
    assume cs:code
    code segmentmov ax,2add ax,axadd,ax,axmov ax,4c00Hint 21h
    code ends
    end
    ;计算2^12
    assume cs:code
    code segment
    start:  mov ax,2mov cx,11p:add,ax,ax       loop p;p是标号mov ax,4c00H;masm默认数字是十进制int 21H
    code ends
    end start
    ;编程计算123*236,结果放在ax中
    assume cs:code
    code segment
    start:mov ax,0mov cx,236an:add ax,123loop anmov ax,4c00Hint 21H
    code ends
    end start
    assume cs:code
    code segment
    start:mov ax,0mov cx,123pa:add ax,236loop pamov ax,4c00Hint 21Hcode ends
    end start

    5.3 在Debug中跟踪用loop指令实现的循环程序

    • 注意:在汇编源程序中数据不能以字母开头,有字母的在前面加0处理。
    • t命令单步执行、G命令和P命令。
    • 使用汇编金手指查阅指令。

    5.4 Debug和汇编编译器Masm对指令的不同处理

    • Degug中mov ax,[0],表示将ds:0处的数据存入al中。ah=0,因为一个内存单元是8位的,ax是16位的,同位存储。而编译器[0]会被当作0处理
    • 将内存2000:0、2000:1、2000:2、2000:3单元中的数据(字节)送入阿al、bl、cl、dl中。

      • debug中:

      • 在MASM中:

      • 要在编译器中实现用偏移地址[]中的内容传送先bx来代替,mov 偏移地址,bx 再 mov al,[bx]。如要直接使用[ ]则要加上段地址ds:[偏移地址]
    • 在MASM中:
    mov al,[0] ;将al赋值0
    mov al,ds[0] ;将al赋值段地址为ds,偏移地址为0的内存单元中的内容
    mov al,[bx] ;默认段地址为ds,将al赋值偏移地址为bx
    mov al,ds:[bx] ;将al赋值段地址为ds,偏移地址为bx

    5.5 loop和[BX]的联合应用

    • 可以用循环来解决处理地址连续的内存单元中的数据的问题,用变量来给出内存单元的地址。

    5.6 段前缀

    • 出现在访问内存单元的指令中用显式地指明内存单元的段地址的ds、cs、ss、es称为段前缀。没有显式地给出内存单元的段地址则默认在ds中。

    5.7 一段安全的空间

    在8086模式中,随意向一段内存空间写入数据是危险的,因为这段空间中可能存放着重要的系统数据或代码。

    assume cs:code
    code segmentmov ax,0mov ds,axmov ds:[26H],axmov ax,4c00Hint 21H
    code ends
    end
    • 但笔者在练习的时候出现dosbox下debug卡死

    • dos下0:200H~0:2FFH的256个字节的空间是安全的,dos和其他合法程序一般都不会使用这段空间。内存0000:0000~0000:03FF大小为1kb的空间是系统存放中断处理程序入口地址的中断向量表。一般情况下0:200H~0:2FFH的256个字节的空间所对应的中断向量表都是空的,操作系统和其他应用程序都不占用。

    5.8 段前缀的使用

    • 将内存ffff:0~ffff:b段单元中的数据拷贝到0:200 ~ 0:20b单元中
    assume cs:code
    code segmentmov bx,0 ;(bx)=0,偏移地址从0开始mov cx,12 ;(cx)=12,循环12次s:  mov ax,offffhmov ds,ax ;(ds)=0ffffhmov dl,[bx] ;(ds)=((ds)*16+(bx)),将ffff:bx中的数据送入dlmov ax,0020hmov ds,ax ;(ds)=0020hmov [bx],dl ;((ds)*16+(bx))=dl,将数据送入0020:bxinc bx ;(bx)=(bx)+1loop smov ax,4c00hint 21h
    code ends
    end
    • 两个内存单元相差64KB则不再同一个段里,需要设置ds的值两次,效率不高。
    • 使用 es(附加段)
    ;优化后的代码,优化了两次设置ds
    assume cs:code
    code segmentmov ax,offffhmov ds,ax ;(ds)=0ffffhmov ax,0020hmov es,ax ;(es)=0020Hmov bx,0 ;(bx)=0,此时ds:bx指向ffff:0,es:bx指向0020:0mov cx,12 ;(cx)=12,循环12次s:  mov dl,[bx] ;(ds)=((ds)*16+(bx)),将ffff:bx中的数据送入dlmov es:[bx],dl ;((es)*16+(bx))=dl,将数据送入0020:bxinc bx ;(bx)=(bx)+1loop smov ax,4c00hint 21h
    code ends
    end

    六、包含多个段的程序

    6.1在代码段中使用数据

    • 编程计算0123H、0456H,0abxH、0defH、0fesH、0cbaH、0987H这8个数据的和,结果存放在ax中:
    assume cs:codesg
    codesg segmentdw 0123H,0564H,0789H,0abcH,0defH,0fedH,0cbaH,0987H;dw,define word,定义字型数据,db定义字节型数据;由于数据在代码段中,所以段地址是CS;dw定义的数据在最开始的地方,所以偏移地址是0开始start:mov bx,0 ;第一条指令mov ax,0mov cx,8s:      add ax,cs:[bx]add bx,2loop smov ax,4c00Hint 21H
    codesg ends
    end start ;入口找end

    • end的作用除了通知编译器结束之外还有告诉编译器程序的入口在什么地方。
    • 可执行文件中的程序执行过程

    6.2 在代码段中使用栈

    • 利用栈编程将定义的数据逆序(联想栈的特性)存放:dw 0123H,0564H,0789H,0abcH,0defH,0fedH,0cbaH,0987H
    assume cs:codesg
    codesg segmentdw 0123H,0564H,0789H,0abcH,0defH,0fedH,0cbaH,0987H;地址0~15dw 0,0,0,0,0,0,0,0;定义8个字型空数据,后面当作栈来使用,地址是16~31start:  mov ax,csmov ss,axmov sp,32;设置栈底ss:sp指向cs:32,十进制的32mov bx,0mov cx,8s:push cs:[bx]add bx,2loop s; 以上代码段0~15个单元中的8个字型数据一次入栈mov bx,0mov cx,8s0:pop cs:[bx]add bx,2loop s0;依次出栈8个执行数据到代码段0~15单元中mov ax,4c00hint 21h
    codesg ends
    end start;指明程序入口在start处
    • 如果对此程序的栈有疑惑,跳转到 3.6 栈和3.10 栈段

    6.3 将数据、代码、栈放入不同的段

    • 在8086CPU中数据、栈和代码存储空间不能大于64KB。可以用像定义代码段一样的方法来定义多个段并在其中定义需要的数据,或者通过定义数据来取得栈空间。
    assume cs:codesg,ds:data,ss:stack;在源程序中为三个段进行有意义的名称
    data segmentdw 0123H,0564H,0789H,0abcH,0defH,0fedH,0cbaH,0987H
    data endsstack segmentdw 0,0,0,0,0,0,0,0;定义8个字型空数据,后面当作栈来使用
    stack endscode segmentstart:      mov ax,stackmov ss,axmov sp,16;设置栈底ss:sp指向stack:16,mov ax,datamov ds,ax;ds指向data段mov bx,0;ds:bx指向data段中的第一个单元s:push cs:[bx]add bx,2loop s; 以上代码段0~16个单元中的8个字型数据一次入栈mov bx,0mov cx,8s0:pop cs:[bx]add bx,2loop s0;依次出栈8个执行数据到代码段0~16单元中mov ax,4c00hint 21h
    codesg ends
    end start;指明程序入口在start处
    • 程序中指令决定了断中的内容是作为数据处理还是作为指令执行还是作为栈空间使用。

    检测点 6.1

    实验五

    assume cs:codesg,ds:data,ss:stack
    data segmentdw 0123H,0564H,0789H,0abcH,0defH,0fedH,0cbaH,0987H
    data endsstack segmentdw 0,0,0,0,0,0,0,0
    stack endscodesg segmentstart:  mov ax,stackmov ss,axmov sp,16mov ax,datamov ds,axpush ds:[0]push ds:[2]pop ds:[2]pop ds:[0]mov ax,4c00hint 21h
    codesg ends
    end start

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

    7.1 and和or指令

    • and指令:逻辑与指令,按位进行与运算。

      and两个同时为真的结果才为真。

    mov al,01100011B
    and al,00111011B
    ;执行后 al=00100011B
    • 可用and指令将操作对象的相应位设为0,其他位不变
    and al,10111111B;将al第六位设为0
    and al,01111111B;将al第七位设为0
    and al,11111110B;将al第0位设为0
    • or指令:逻辑或指令,按位进行或运算。
    mov al,01100011B
    and al,00111011B
    ;执行后 al=01111011B
    • 可用or指令将操作对象的相应位设为1,其他位不变
    and al,01000000B;将al第六位设为1
    and al,10000000B;将al第七位设为1
    and al,00000001B;将al第0位设为1

    7.2 关于ASCII码

    • 将字符的ascii码写入显存屏幕就显示出相关的字符。

    7.3 以字符形式给出数据

    • 用‘’的方式指明数据是以字符的形式给出的。例如’A’
    assume cs:code,ds:datadata segmentdb 'unIx'db 'foRK'
    data endscode segmentstart:  mov al,'a'mov bx,'b'mov ax,4c00hint 21h
    code ends
    end start

    7.4 大小写转换的问题

    • 大写字母比小写字母ASCII大32(20H)。
    大写 二进制 小写 二进制
    A 01000001 a 01100001
    B 01000010 b 01100010
    C 01000011 c 01100011
    D 01000100 d 01100100
    • 从第0位开始计算,大写字母ASCII码第五位为0,小写字母ASCII码第五位为1。
    ;大小写转换
    assume cs:codesg,ds:datasg
    datasg segment
    db'BaSiC'
    db'iNfOfMaTiOn'
    datasg endscodesg segmentstart:  mov ax,datasgmov ds,ax;设置ds执行datasg段mov bx,0;设置(bx)=0,ds:bx指向'BaSiC'的第一个字母mov cx,5;设置循环次数,因为BaSiC有5个字母s:mov al,[bx];将ASCII码从ds:bx所指向的单元中取出and al,11011111B;口岸al中ASCII码的第5个位置变为0,变为大写字母mov [bx],al;转变后将ASCII码写回单元inc bx;(bx)加1,ds:bx指向下一个字母loop xmov bx,5;设置(bx)=5,ds:bx指向'iNfOfMaTiOn'的第一个字母mov cx,11s0:mov al,[bx]or al,00100000Bmov [bx],alinc bxloop s0mov ax,4c00Hint 21H
    codesg ends
    end start
    

    7.5 [bx+idata]

    • [bx+idata]表示的是一个内存单元,它的偏移地址为bx+idata
    ;[bx+idata]可以写成以下格式
    mov ax,[200+bx]
    mov ax,200[bx]
    mov ax,[bx].200
    ;使用debug查看内存
    mov ax,2000H
    mov ds:ax
    mov bx,1000H
    mov ax,[bx]
    mov cx,[bx+1]
    add cx,[bx+2]

    7.6 用[bx+idata]的方式进行数组的处理

    • 用[bx+idata]的方式进行数组处理
    ;改进大小写转换程序
    assume cs:codesg,ds:datasg
    datasg segment
    db'BaSiC'
    db'iNfOfMaTiOn'
    datasg endscodesg segmentstart:  mov ax,datasgmov ds,ax;设置ds执行datasg段mov bx,0;设置(bx)=0,ds:bx指向'BaSiC'的第一个字母mov cx,5;设置循环次数,因为BaSiC有5个字母s:mov al,[bx+0];将ASCII码从ds:bx所指向的单元中取出and al,11011111B;口岸al中ASCII码的第5个位置变为0,变为大写字母mov [bx],al;转变后将ASCII码写回单元mov [bx+5];定位第二个字符串的字符or al,00100000Bmov [bx+5],alinc bxloop smov ax,4c00Hint 21H
    codesg ends
    end start
    • C语言的形式
        include<stdio.h>char a[5]="BaSiC";char b[11]="iNfOfMaTiOn";main(){int i;i=0;do{a[i]=a[i]&0xDF;b[i]=b[i]|0x20;i++;}while(i<5);}

    7.7 SI和DI

    • SI和DI在8086CPU中和bx功能相近,充当BX的扩充,但是不能分成两个8位寄存器来使用。[SI]段地址默认也是在DS中。
    • 下面的指令实现了相同的功能
    mov bx,0
    mov ax,[bx]mov si,0
    mov ax,[si]mov di,0
    mov ax,[di]
    ;-------------
    ;下面的三组指令也实现了另一个组相同的功能
    ;-------------
    mov bx,0
    mov ax,[bx+123]mov si,0
    mov ax,[si+123]mov di,0
    mov ax,[di+123]
    • 一般ds:si指向要复制的原始空间,ds:di指向复制的目的空间。
    ;用DI和SI实现复制到它后面的数据区中
    assume cs:codesg,ds:datasg
    datasg segment
    db'welcome to asm!'
    db'................'
    datasg endscodesg segmentstart  :mov ax,datasgmov ds,axmov si,0mov di,16mov cx,8s:mov ax,[si]mov [di],axadd si,2add di,2loop smov ax,4c00hint 21H
    ;------
    ;用数组的思维[bx(si或di)+idata]的方式优化程序
    ;------
    assume cs:codesg,ds:datasg
    datasg segment
    db'welcome to asm!'
    db'................'
    datasg endscodesg segmentstart  :mov ax,datasgmov ds,axmov si,0mov cx,8s:mov ax,[si];第一个字符串的的第一个元素mov [si+16],ax;目标字符串的第二个元素add si,2loop smov ax,4c00hint 21H
    codesg ends
    end start
    

    7.8 [bx+si]和[bx+di]

        mov ax,2000hmov ds,axmov bx,1000hmov si,0mov ax,[bx+si]inc simov cx,[bx+si]inc simov di,simov ax,[bx+di]

    7.9 [bx+si+idata]和[bx+di+idata]

    • 常数后要加.例如[bx+si].idata或者[bx].idata[si]
        mov ax,2000hmov ds,axmov bx,1000hmov si,0mov ax,[bx+2+si]inc simov cx,[bx+si+2]inc simov di,simov ax,[bx+di+2]

    7.10 不同的寻址方式的灵活应用

    • 编程将数据段中每一个单词的头一个字母改为大写字母。

    assume cs:codesg,ds:datasg
    datasg segmentdb'1. file          ';长度刚好都是16个字节db'2. edit          'db'3. search        'db'4. view          'db'5. options       'db'6. help          '
    datasg endscodesg segmentstart:mov ax,datasgmov ds,axmov bx,0mov cx,6s:    mov al,[bx+3]and al,11011111Bmov [bx+3],aladd bx,16loop smov ax,4c00hint 21h
    codesg ends
    end start
    • 编程将数据段中每个单词改为大写字母

    ;有bug,问题在于cx的使用,进行二重循环,只用一个循环计数器,造成在进行内层的时候覆盖了外层循环的循环计数值。
    assume cs:codesg,ds:datasg
    datasg segmentdb 'ibm             'db 'dec             'db 'dos             'db 'vax             '
    datasg endscodesg segmentstart:mov ax,datasgmov ds,axmov bx,0;用bx来定位行mov cx,4s0:mov si,0;用si来定位列mov cx,3s:mov al,[bx+si]and al,11011111Bmov [bx+si],alinc siloop sadd bx,16loop s0mov ax,4c00hint 21h
    codesg ends
    end start
    • 程序没有返回到cmd

    loop s;三次循环后cx等于0了
    add bx,16
    loop s0;先是cx=cx-1再判断时候等于0,此时cx=FFFF不为0再循环,变成死循环了
    • 因为loop是和cx一起使用的,不能多用个寄存器来解决loop循环次数的问题。解决的方法是在每次开始内层循环时用dx将外层循环cx的值保存起来,在执行外层循环的loop指令前再回复外层循环的cx的数值。
    • 改进后程序
    assume cs:codesg,ds:datasgdatasg segmentdb 'ibm             'db 'dec             'db 'dos             'db 'vax             '
    datasg endscodesg segmentstart:mov ax,datasgmov ds,axmov bx,0;用bx来定会行mov cx,4s0:mov dx,cx;用dx寄存器来临时存放外层cx的值mov si,0;用si来定位列mov cx,3s:mov al,[bx+si]and al,11011111Bmov [bx+si],alinc siloop sadd bx,16mov cx,dx;在进行外层循环的时候回复cx的值loop s0mov ax,4c00hint 21h
    codesg ends
    end start
    • 在上面的程序中,8086 CPU si、cx、ax、bx这些寄存器经常要使用到;cs、ip、ds也不能用,因为cs:ip时刻指向当前指令,ds指向datasg段;那么可用的寄存器就只用dx、di、es、ss、sp、bp等寄存器了。内存可以解决经常性的数据暂存问题。为了使程序结构清晰便于阅读,应该使用栈
    • 再次被改进的程序
    assume cs:codesg,ds:datasgdatasg segmentdb 'ibm             'db 'dec             'db 'dos             'db 'vax             'dw 0;定义一个字用来保存cx的值
    datasg endscodesg segmentstart:mov ax,datasgmov ds,axmov bx,0;用bx来定位行mov cx,4s0:mov ds:[40h],cx;datasg:40h单元存放外层cx的值mov si,0;用si来定位列mov cx,3s:mov al,[bx+si]and al,11011111Bmov [bx+si],alinc siloop sadd bx,16mov cx,ds:[40h];在进行外层循环的时候回复cx的值loop s0mov ax,4c00hint 21h
    codesg ends
    end start
    • 再次使用栈改进程序
    assume cs:codesg,ds:datasg,ss:stacksg
    datasg segmentdb 'ibm             'db 'dec             'db 'dos             'db 'vax             '
    datasg endsstacksg segmentdw 0,0,0,0,0,0,0,0;定义一个段,用作栈段,容量为16个字节
    stacksg endscodesg segmentstart:mov ax,stacksgmov ss,axmov sp,16mov ax,datasgmov ds,axmov bx,0;用bx来定位行mov cx,4s0:push cx;datasg:40h单元存放外层cx的值mov si,0;用si来定位列mov cx,3s:mov al,[bx+si]and al,11011111Bmov [bx+si],alinc siloop sadd bx,16pop cx;在进行外层循环的时候回复cx的值loop s0mov ax,4c00hint 21h
    codesg ends
    end start
    • 编程将数据段中的每个单词的前四个字母改为大写字母

    assume cs:codesg,ds:datasg,ss:stacksgstacksg segment
    stacksg endsdatasg segmentdb '1. display      'db '2. brows        'db '3. replace      'db '4. modify       '
    datasg ends
    codesg segmentstart:mov ax,stacksgmov ss,axmov sp,16mov ax,datasgmov ds,axmov bx,0mov cx,4s0:push cxmov si,0mov cx,4s:mov al,[bx+si+3]and al,11011111Bmov [bx+si+3],alinc siloop sadd bx,16pop cxloop s0mov ax,4c00hint 21h
    codesg ends
    end start

    八、数据处理的两个基本问题

    引言

    • 本章是总结性的内容,数据处理的两个基本问题是

      • 处理的数据在哪?
      • 要处理的数据有多长?
    • 自定义得描述符:
      • reg寄存器

        • ax、bx、cx、dx、ah、al、bh、bl、ch、cl、dh、dl、sp、bp、si、di;
      • sreg段寄存器
        • ds、ss、cs、es。
        • -

    8.1 bx、si、di、bp

    • 在8086 CPU中只有bx、si、di、bp这四个寄存器用在[ ]中进行内存单元寻址。在[]中,组合只能以这四种形式:bx和si、bx和di、bp和si、bp和di
        ;以下指令是错误的mov ax,[ax]mov ax,[cx]mov ax,[dx]mov ax,[ds]mov ax,[bx+bp]mov ax,[si+di]
    • 正确的指令
        mov ax,[bx]mov ax,[si]mov ax,[di]mov ax,[bp]mov ax,[bx+si]mov ax,[bx+di]mov ax,[bp+si]mov ax,[bp+di]mov ax,[bx+si+idata]mov ax,[bx+di+idata]mov ax,[bp+si+idata]mov ax,[bp+di+idata]
    • [bp]的段地址默认在ss中。

    8.2 机器指令处理的数据所在的位置

    • 绝大部分机器指令时进行数据处理的,大致可以分为3类:读、写、运算。指令在处理前可以在三个地方:CPU内部、内存、端口。
    机器码 汇编指令 指令执行前数据的位置
    89C3 mov bx,[0] 内存,ds:0单元
    89C3 mov bx,ax CPU内部,ax寄存器
    BB0100 mov bx,1 CPU内部,指令缓冲器

    8.3 汇编语言中数据位置的表达

    • 汇编语言中用三个概念来表达数据的位置。

      • 1、立即数(idata)
      • 2、寄存器
      • 3、段地址(SA)和偏移地址(EA)

    8.4 寻址方式总小结

    8.5 指令要处理的数据有多长

    • 8086 CPU可以处理byte和word两种数据尺寸。
    • 通过寄存器指明要处理的数据尺寸;push指令只进行字操作,若没有寄存器名存在的情况下,用操作符word ptr或者byte ptr指明内存单元的长度。 例如
        mov word ptr ds:[0],1inc word ptr [bx]inc word ptr ds:[0]add byte ptr [bx],2
        ;假设内存2000:1000 FF FF FF FF FF FF ……;如果用以下指令mov ax,2000Hmov ds,axmov byte ptr [1000H],1;那么内存中的内容变为;2000:1000 01 FF FF FF FF FF ……如果是用以下指令mov ax,2000Hmov ds,axmov word ptr [1000H],1;那么内存中的内容变为;2000:1000 01 00 FF FF FF ……

    8.6 寻址方式的综合应用

    • 初步汇编代码
        mov ax,segmov ds,axmov bx,60h;确定记录物理地址:ds:bxmov word ptr [bx+0ch],38;寄存器相对寻址     排名字段改为38add word ptr [bx+0eh],70;收入字段增加70mov si,0;用si来定位产品字符串中的字符mov byte ptr [bx+10h+si],'V';相对基址变址寻址inc simov byte ptr [bx+10h+si],'A'inc simov byte ptr [bx+10h+si],'X'
    • c语言描述
        struct company  /*定义一个公司记录的结构体*/{char cn[3]; /*公司名称*/char hn[9]; /*总裁姓名*/int pm;     /*排名*/int sr;     /*收入*/char cp[3]; /*著名产品*/};struct compant dec={"DEC","Ken Olsen",137,40,"PDF"};/*定义一个公司记录的变量,内存中将存有一条公司的记录*/mian(){int i;dec.pm=38;dec.sr=dec.sr+70;i=0;dec.cp[i]='V';i++;dec.cp[i]='A';i++;dec.cp[i]='X';return 0;}
    • 按照c语言的风格用汇编写
        mov ax,segmov ds,axmov bx,60h;记录首地址送入bxmov word ptr [bx].och,38;排名字段改为38add word ptr [bx].0eh,70;收入字段增加70;产品名字段改为字符串'VAX'mov si,0mov byte ptr [bx].10h[si],'V'inc simov byte ptr [bx].10h[si],'A'inc simov byte ptr [bx].10h[si],'X'
    • 多种寻址方式为结构化数据的处理提供了方便。
    • 一般用[bx+idata+si]的方式来访问结构体,用idata定位结构体中的某一数据项,用si定位数组项中的每个元素。 例如:[bx].idata、[bx].idata[si]。

    8.7 div指令

    • div(divide)是除法指令,可用乘法模拟,格式为:
    div reg(寄存器)
    div 内存单元。
    • 除数:8位或16位,在寄存器或内存单元中;被除数:默认放在AX或DX和AX中。
            div byte ptr ds:[0]div byte ptr [bx+si+idata];al放商,ah放余数div word ptr es:[0]div word ptr [bx+si+idata];ax放商,dx放余数
    除数 被除数
    8位 16为(AX)
    16位 32位(DX高16位+AX低16位)

    - 8位或16位看的是除数。

    运算 8位 16位
    AL AX
    余数 AH DX

    - 利用除法指令计算10001/100编程

        ;被除数1001可用ax寄存器存放,除数100可用8位寄存器存放,要进行8位除法。mov ax,1001mov bl,100div bl;执行后al的值等于0AH(10),ah的值等于1(余数为1)。
    • 利用除法指令计算100001/100编程
        ;被除数100001大于2^16=65535(FFFF),不能用ax来存放,要用dx和ax两个寄存器联合存放。除数小于255,可用一个8位寄存器存放,但是被除数是32位的,除数应为16位,所以要用一个16位寄存器来存放除数。;100001的十六进制为186A1H,100001的高16位(1)存放在dx,低16位(86AH)存放在ax中。mov dx,1mov ax,86A1Hmov bx,100div bx;执行后ax内容等于03E8H(即1000),dx的值等于1(余数)。

    8.8 伪指令dd

    • db定义字节型数据,dw定于字型数据,dd 定于 dword(double word双字型数据)
    data segmentdb 1;第一个数据为01h,在data:0处,占1个字节dw 1;第二个数据为0001h,在data:1处,占1个字dd 1;第三个数据为00000001h,在data:3处,占2个字
    data ends
    • 利用除法指令计算 dd 100001H 除以 dw 100,商放在 dw 0中
    data segmentdd 100001H;低16位存储在ax中,高16位存储在dx中dw 100dw 0
    data endsmov ax,datamov ds,axmov ax,ds:[0];低16位存储在ax中mov dx,ds:[2];高16位存储在dx中div word ptr ds:[4]mov ds:[6],ax

    8.9 伪指令dup

    • 和db、dw、dd等数据定义伪指令配合使用,用来进行数据的重复。格式 db或者dw或者dd 重复的次数 dup (重复的数据)
    • 例如:
    db 3 dup(0)
    ;定义了3个字节,它们的值都是0,等同于db 0,0,0。
    db 3 dup(0,1,2)
    ;定义了9个直接,它们是0、1、2、0、1、2、0、1、2,相当于db 0、1、2、0、1、2、0、1、2
    db 3 dup('abc','ABC')
    ;定义了18个直接,它们是'abcABCabcABCabcABC'

    实验七 寻址方式在结构化数据访问中的应用


    • ds已经和data段联系了,数据段不够用时用扩展段ES
        ;初始化阶段mov ax,datamov ds,axmov ax,table;data已经被占用mov es,axmov bx,0mov si,0mov di,0mov cx,21;存放年份,每一个bx就是一个字节mov al,[bx]mov es:[di],almov al,[bx+1]mov es:[di+1],almov al,[bx+2]mov es:[di+2],almov al,[bx+3]mov es:[di+3],al;存放公司的总收入mov ax,[bx+54H];第一个年收入是dd数据类型,段地址为54Hmov dx,[bx+54H]mov es:[di+5H],axmov es:[di+7H],dx;存放公司的人数mov ax,[si+0A8H];第一个人数的数据段地址为0A8Hmov es:[di+0A8H],ax;计算人均收入并存放mov ax,[bx+54H]mov dx,[bx+56H];这两句诗初始化被除数div word ptr,ds:[si+0A8H];除以人数mov es:[di+0dH],ax;将商放入指定位置;为下一次循环时存放数据做准备add bx,4;bx确定年份和收入add si,2;si确定人数add di,16;di确定的是每行的列数
    • 完整的程序
    assume cs:codesg,ds:data,es:tabledata segmentdb '1975','1976' '1977' ……dd 16,22,382 ……dw 3,7,9 ……;数据在题目中
    data endstable segmentdb 21 dup('year summ ne ?? ')
    table endsstart:mov ax,datamov ds,axmov ax,tablemov es,axmov bx,0mov si,0mov di,0mov cx,21s:mov al,[bx]mov es:[di],almov al,[bx+1]mov es:[di+1],almov al,[bx+2]mov es:[di+2],almov al,[bx+3]mov es:[di+3],almov ax,[bx+54H]mov dx,[bx+56H]mov es:[di+5H],axmov es:[di+7H],dxmov ax,[si+0A8H]mov es:[di+0AH],axmov ax,[bx+54H]div word ptr ds:[si+0A8H]mov es:[di+0dH],axadd bx,4add si,2loop s
    mov ax,4c00h
    int 21h
    codesg ends
    end start

    九、转移指令的原理

    引言

    • 可以修改IP,或者同时修改CS和IP的指令统称为转移指令。 简单的来说可以控制CPU执行内存中某处代码的指令就是转移指令。
    • 8086
    • CPU的转移行为有只修改的段内转移(如jmp ax) 和同时修改该CS和IP的段间转移(如jmp 1000:0)。其中段内转移分为短转移(IP的修改范围为-128~127)和近转移 (IP的修改范围为-32768~32767)。
    • 8086 CPU的转移指令分为以下几类:
      • 无条件转移指令(如:jmp)
      • 条件转移指令
      • 循环指令(如:loop)
      • 过程
      • 中断

    9.1 操作符offset

    • offset是伪指令,由编译器处理,它的功能是取得标号的偏移地址。
        assume cs:codesgcodesg segmentstart:mov ax,offset start;相当于 mov ax,偏移地址0,段地址是从0开始s:mov ax,offset s;相当于 mov ax,3,标记的是代码段中的第二条指令,第一条指令长度为3个字节,则s的偏移地址为3codesg endsend start

    9.2 jmp指令

    • jmp为无条件转移,可以只修改IP,也可以同时修改CS和IP。
    • jmp需要两种信息
      • 1、转移的目的地址;
      • 2、转移的距离(段间转移、段内转移、段内近转移)。

    9.3 依据位移进行转移的jmp指令

    • 段内短转移,jmp short 标号 ,对IP的修改范围是-128~127,一个字节的空间,即向前转移最多128字节,向后最多127字节。short 表明指令进行的是短转移,标号指明了指令要转移的目的地,转移指令结束后CS:IP指向标号处的指令
        assume cs:codesgcodesg segmentstart:mov ax,0jmp short sadd ax,1s:inc axcodesg endsend start
    • 一般汇编指令中的立即数(idata)会出现在对应的机器指令中。而jmp指令的机器指令并不包含目的地址,包含的是相对于当前IP的转移位移,CPU并不需要目的地址就可以实现对IP的修改。

    • CPU执行指令的过程 在 2.10 CS和IP

    • jmp short s 指令的读取和执行过程:
      • 1、CS:IP指向jmp short s 的机器码;
      • 2、读取指令码进入指令缓冲器
      • 3、 改变IP,(IP)=(IP)+所读取指令的长度,IP指向下一个指令;
      • 4、CPU执行指令缓冲器中的指令;
      • 5、执行后CS:IP继续指向下一个指令
    • jmp short 标号的功能为(IP)=(IP)+8位位移。
      • 1、8位为=标号处的地址-jmp指令后的第一个字节的地址;
      • 2、short 指明此处的位移为8位;
      • 3、8位位移的范围为-128~127,用补码表示。
      • 4、8位位移由编译程序在编译时算出的。
    • jmp near ptr 标号 指令实现段内近转移,功能为(IP)=(IP)+16位位移。
      • 1、16位为=标号处的地址-jmp指令后的第一个字节的地址;
      • 2、nearptr 指明此处的位移为16位;
      • 3、16位位移的范围为-32769~32767,用补码表示。
      • 4、16位位移由编译程序在编译时算出的。

    9.4 转移的目的地址在指令中的jmp指令

    • jmp far ptr 段间转移,又称为远转移
    • jmp far ptr 标号的功能:
      • (CS)=标号所在段的段地址;
      • (IP)=标号所在段总的偏移地址;
      • far ptr 指明了指令用标号的段地址和偏移地址修改CS和IP。
              assume cs:codesgcodesg segmentstart:mov ax,0mov bx,0jmp far ptr sdb 256 dup(0)s:add ax,1inc axcodesg endsend start
    • 机器码中包含了转移的目的地址。

    附注3 汇编编译器(masm.exe)对jmp的相关处理

    9.5 转移地址在寄存器中的jmp指令

    • jmp 16位寄存器,功能是16位寄存器赋值给IP,实现段内的近(短)转移。
    • 参考 2.11 修改CS、IP的指令

    9.6 转移地址在内存中的jmp指令

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

      • 1、jmp word ptr内存单元地址(16位只能实现段内转移)。 功能是从内存单元地址处开始存放一个字(转移的目的偏移地址),内存单元地址可用寻址方式的格式给出。

            mov ax,0123Hmov ds:[0],axjmp word ptr ds:[0];相当于 jmp ax,执行后(IP)=0123hmov ax,0123Hmov [bx],axjmp word ptr [bx];执行后(IP)=0123h
      • 2、jmp dword ptr 内存单元地址(段间转移)。 功能:从内存单元地址处开始存放两个字型数据,高地址是转移的目的段地址,低地址处是转移的目的偏移地址。(CS)=(内存单元地址+2),(IP)=(内存单元地址),内存单元地址可用寻址方式的任一格式给出。

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

    检测点 9.1

    9.7 jcxz指令

    • 指令格式为jcxz 标号,如果cx的值为0,则转移到标号处执行,不为0则向下执行。

      • 当cx的值为0时,(IP)=(IP)+8位位移,8位位移=标号处的地址-jcxz指令后的第一个字节的地址。
      • 8位位移的范围是-128~127,用补码表。
      • 8位位移由编译器在编译时算出。
    • jcxz指令是有条件转移指令,所有的条件转移指令都是短指令,在对应的机器码中包含转移的位移而不包含目的地址,对IP的修改范围都为-128-127。

    检测点 9.2

    9.8 loop指令

    • loop指令为循环指令,所有的循环指令都是短转移,在对应的机器码中包含转移的位移而不包含目的地址。操作i:

      • cx先自减1;
      • 当cx的值不为0时,(IP)=(IP)+8位位移,8位位移=标号处的地址-loop指令后的第一个字节的地址。
      • 8位位移的范围是-128~127,用补码表。
      • 8位位移由编译器在编译时算出。

    检测点 9.3

    9.9 根据位移进行转移的意义

        jmp short 标号jmp near ptr 标号jcxz 标号loop 标号
    • 它们对IP的修改时根据转移目的地址和转移起始地址自检的位移来进行的。在它们对应的机器码中不包含转移的目的地址,而包含的是目的地址的位移距离。方便了程序段在内存中的浮动分配,没有固定目的地址的限制,更灵活。

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

    • 根据位移进行转移的指令,它们的转移范围受到了转移位移的限制,如果在源程序中出现了转移范围超界的问题,在编译时编译器会报错。
    assume cs:codecode segmentstart:  jmp short sdb 128 dup(0)s:mov ax,0FFFFHcode ends
    end start

    实验8

    • 实验八可以正常退出
    assume cs:codesg
    codesg segmentmov ax,4c00hint 21hstart:mov ax,0s:nopnop;nop占用两个字节,不执行任何操作mov di,offset smov si,offset s2mov ax,cs:[si];jmp short s1的机器码给了axmov cs:[di],ax;覆盖到指令 s:nop nop那s0:jmp short s;s那已经被jmp short s1机器码覆盖s1:mov ax,0int 21hmov ax,0s2:jmp short s1;jmp -8h,向上跳到s1,s1又向上跳-10字节nop
    codesg ends
    end start   

    实验9

    assume cs:code,ds:data,ss:stackdata segmentdb'welcome to masm!';定义要显示的字符串(共16字节)db 02H,24H,71H;定义字符的属性
    data endsstack segmentdw 8 dup(0)
    stack endscode segmentstart:mov ax,datamov ds,axmov ax,stackmov ss,axmov sp,10Hxor bx,bx;bx清零,用来索引颜色mov ax,0b872H;算出屏幕第12行中间的显存的段起始位置放入ax中mov cx,3;s3循环控制行数,要显示三个字符串外循环为3次s3: push cx;三个进栈操作为外循环s3保存相关寄存器的值push ax;以防止它们的值在内循环中被破坏push bxmov es,ax;此时es为屏幕第12行中间的显存的段起始位置mov si,0;si用来索引代码列的字符mov di,0;di用来定位目标列mov cx,10H;s1循环控制存放的字符,一个字符串中含有10H个字节内循环为10H次s1: mov al,ds:[si]mov es:[di],alinc siadd id,2loop s1;吃循环实现偶地址中存放字符mov di,1;设置di的值为1,为在显存奇数地址中存放字符的颜色属性做准备pop bxmov al.ds:[bx+10H];取消颜色属性inc bxmov cx,10H;第二个内循环也为10Hs2: mov es:[di],aladd di 2loop s2;此循环实现奇数地址存放字符的颜色属性;以下4句为下一趟外循环做准备pop axadd ax,0AH;将显存的段地址起始地址设置为当前行的下一行;[在段地址中甲0aH,相当于在偏移地址中加了0a0h(=160d)]pop cxloop s3mov ax,4C00Hint 21Hcode ends
    end start
    • welcome to masm

    十、CALL和RET指令

    引言

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

    10.1 ret和retf指令

    • ret指令用栈中的数据来修改IP的内容,从而实现近转移。
    • CPU执行ret指令时:
      • 1、(IP)=((SS)*16+(SP)),指向栈顶
      • 2、(SP)=(SP)+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
    assume cs:codesgstack segmentdb 16 dup(0)
    stack endscodesg segmentmov ax,4c00hint 21hstart:mov ax,stackmov ss,axmov sp,16mov ax,0push axmov bx,0ret
    codesg endsend start

    assume cs:codesgstack segmentdb 16 dup(0)
    stack endscodesg segmentmov ax,4c00hint 21hstart:mov ax,stackmov ss,axmov sp,16mov ax,0push cspush axmov bx,0retf
    codesg endsend start

    检测点 10.1

    10.2 call指令

    • call指令经常跟ret指令配合使用,CPU执行call指令时:

      • 1、将当前的IP或者CS和IP压入栈;
      • 2、转移(jmp)。
    • call指令除了不能实现短转移之外,call指令实现转移的方法和jmp指令的原理相同。call指令实现段间的转移(远转移)或近转移。

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

    • call标号(将当前的IP压入栈后转到目标处执行指令),执行时进行以下操作:

      • 1、(SP)=(SP)-2
        ((SS)*16+(SP))=(IP)
      • 2、(IP)=(IP)+16位位移;
      • 3、16位位移=标号处的地址减去call指令后的第一个字节的地址。16位位移的范围是-32768~32767,用补码表示。16位位移由编译器编译时算出。
    • 用汇编语法解释call指令:
    push IP
    jmp near 标号

    检测点 10.2

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

    • call far ptr 标号 实现的是段间转移,执行时:

      • 1、CS先自减2;
      • 2、CS的值等于SS的值乘以16加上SP的值,SP自减2,IP的值等于SS的值*16加上SP的值;
      • 3、CS的值等于标号所在的段地址,IP的值等于标号所在的偏移地址.
    • 用汇编语法解释call指令:
    push CS
    push IP
    jmp far ptr 标号

    检测点 10.3

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

    • 指令格式是:call 16位寄存器,功能是:

      • 1、SP的值先自减2;
      • 2、IP的值SS的值乘以16再加上SP的值;
      • 3、 IP的值等于16位寄存器的内容。
    • 用汇编语法解释此种call指令,CPU执行call 16位reg时,相当于:
    push IP
    jmp 16位寄存器

    检测点 10.4

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

    • 转移地址在内存中的call指令有两种格式
           call word ptr 内存单元地址;段内跳转call dword ptr 内存单元地址;段间跳转
    • 用汇编语法解释call word ptr 内存单元地址
           push IPjmp word ptr 内存单元地址
    • 例子:
           mov sp,10hmov ax,0123Hmov ds:[0],axcall word ptr ds:[0];执行后IP的值等于0123H,SP的值等于0EH
    • 用汇编语法解释call dword ptr 内存单元地址
           push CSpush IPjmp word ptr 内存单元地址
    • 例子:
           mov sp,10hmov ax,0123Hmov ds:[0],axmov word ptr ds:[0],0call dword ptr ds:[0];执行后IP的值等于0123H,SP的值等于0CH,CS的值等于0

    检测点 10.5

    10.7 call和ret的配合使用

    • 下面的程序返回前,bx中的值是多少?
    assume cs:code
    code segmentstart:mov ax,1mov cx,3call smov bx,axmov ax,4c00hint 21hs:add ax,axloop sret
    code ends
    end start
    • 具有一定功能的程序段称为子程序,用call转去执行,在子程序后面使用ret实现返回。
    • 具有子程序的源程序的框架如下

    10.8 mull指令

    • mull指令时乘法指令,相乘的两个数要么都是8位的,要么都是16位的

      • 8位:在AL中和8位寄存器中或内存字节单元中;
      • 16位:在AX中和16位寄存器或内存字单元中。
      • 结果
        • 8位的存放在AX中;
        • 16位:DX(高位)和AX(低位)中。
    mull regmull 内存单元
    mull byte ptr ds:[0]mull word ptr [bx+si+idata]
    ;(ax)=(ax)*((ds)*16+(bx)+(si)+idata)
    ;(dx)=(ax)*((ds)*16+(bx)+(si)+idata)
    ;计算100*10,两个数都小于255,可以做8位乘法
    mov ax,100
    mov bx,10
    mull bl
    ;结果(ax)=1000(03E8H);计算100*1000,1000都大于255,要做16位乘法
    mov ax,100;高位自动补零
    mov bx,10000
    mull bx
    ;结果(ax)=4240H,(dx)=000FH,F4240H=1000000

    10.9 模块化程序设计

    • cal和ret指令共同支持汇编语言编程中的模块化设计。

    10.10 参数和结果传递的问题

    • 用寄存器来存储参数和结果是最常用的方法。对于存放参数的寄存器和存放结果的寄存器,调用者和子程序的读写操作恰恰相反:

      • 调用者将参数送入参数寄存器,从结果寄存器中取到返回值;
      • 子程序 从参数寄存器中取到参数,将返回值送入结果寄存器。
    • 编程:根据提供的N来计算N^3

    cube:mov ax,bxmul bxmul bxret
    • 编程:计算data段中第一组数据的3次方,结果保存在后面一组dword单元中
    assume cs:codedata segmentdw 1,2,3,4,5,6,7,8dd 8 dup (0)
    data endscode segmentstart:mov ax,datamov ds,axmov si,0;ds:si指向第一组word单元mov di,16;ds:di指向第二组dword单元mov cx,8s:  mov bx,[si]call cubemov [di],axmov [di+2],dxadd si,2;ds:di指向下一个word单元add di,4;ds:di指向下一个dword单元loop smov ax,4c00hint 21hcube:mov ax,bxmul bxmul bxret
    code ends
    end start

    10.11 批量数据的传递

    • 将批量数据放在内存中,然后将他们呢所在内存空间的首地址放在寄存器中,传递给需要的子程序,批量数据的返回结果也是采用同样的方法。除此之外还可以用栈来传递参数。
    assume cs:codedata segmentdb'conversation'
    data endsstart:mov ax,datamov ds,axmov si,0;ds:si指向字符串(批量数据)所在空间的首地址mov cx,12;cx存放字符串的长度call capitalmov ax,4c00hint 21hcapital:add byte ptr [si],11011111Binc siloop capitalret
    code ends

    10.12 寄存器冲突的问题

    • 编程:将一个全是字母,以0结尾的字符串转化为大写
        capital:mov cl,[si];低8位mov ch,0;高8位设置为0jcxz ok;如果(cx)=0则结束,如果不是0则处理and byte ptr [si],11011111Binc sijmp short capitalok:ret
    • 编程将data段中的字符串全部转化为大写
        assume cs:codedata segmentdb'word',0db'unix',0db'wind',0db'good',0data ends
        ;此程序有bug,cx有问题assume cs:codedata segmentdb'word',0db'unix',0db'wind',0db'good',0data endscode segmentstart:mov ax,datamov ds,axmov bx,0mov cx,4s:mov si,bxcall capitaladd bx,5loop smov ax,4c00hint 21hcapital:mov cl,[si]mov ch,0jcxz okand byte ptr [si],11011111binc sijmp short capitalok:retcode endsend start

    实验十



    • 实验10.1 显示字符串
    assume cs:codedata segmentdb 'welcome to masm!',0
    data endscode segmentstart:mov dh,8;行号mov dl,3;列号mov cl,2;颜色属性mov ax,datamov ds,axmov si,0call show_strmov ax,4c00hint 21hshow_str:;子程序push cxpush simov al,0A0h;每行有80*2=160个字节=0a0hdec dh;行号在显存中下标从0开始,所以减1mul dh;相当于从第(n-1)*0a0h个byte单元开始mov bx,ax;定位好的位置偏移地址存放在bx里(行)mov al,2;每个字符占2个字节mul dl;定位列,结果ax存放的是定位好的列的位置sub ax,2;列号在显存中下标从0开始,又因为是偶字节存放字符,所以减2add bx,ax;此时bx中存放的是行与列的偏移地址mov ax,0B800h;显存开始的地方mov es,ax;es中存放的是显存的第0页的起始地段地址mov di,0;di指向显存的偏移地址,确定指向下一个要处理的字符的位置mov al,cl;cl存放颜色参数,下边cl要用来临时存放要处理的字符mov ch,0;下边cx存放的是每次准备处理的字符s:mov cl,ds:[si];指向'welcome to masm ',0jcxz ok;cl为0时跳转mov es:[bx+di],cl;偶地址存放字符mov es:[bx+di+1],al;奇地址存放字符的颜色属性inc siadd di,2;指向了下个字符jmp short s ;无条件跳转,jcxz是离开的关键跳ok:pop sipop cxret;定义结束
    code ends
    end start

    • 实验10.2
        assume cs:code,ss:stackstack segmentdw 8 dup(0)stack endscode segmentstart:mov ax,stackmov ss,axmov sp,10hmov ax,4240hmov dx,0fhmov xx,0ahcall divdwmov ax,4c00hint 21hdivdw:push ax;低16位先保存mov ax,dx;ax这时是高16位了mov dx,0;为了不影响余数位和高位数div cxmov bx,axpop axdiv cxmov cx,dxmov dx,dxretcode endsend start
    • 实验10.3
    assume cs:code,ds:datadata segmentdb 10 dup(0)
    data endscode segmentstart:mov ax,12666mov bx,data;指向字符串的首地址mov ds,bxmov si,0call dtoc;实现将word型整数转化为字符串并存储mov dh,8;打印初始化mov dl,3mov cl,0cahcall show_str;开始打印字符串mov ax,4c00hint 21hdtoc:push dxpush cxpush axpush simov bx,0;bx在子程序中用来存放位数,用栈来临时存放修改后的字符s1:mov cx,10d;d表示十进制,cx准备被除,用取余法来取出数字mov dx,0div cx;除以十mov cx,ax;得到的商复制给cx,要利用jcxzjcxz s2;当商为0则跳到s2add dx,30h;余数加上30h得到相应的ascii码push dxinc bxjmp short s1s2:add ax,30h;当商为0的时候,余数为个位push dxinc bx;再进行一次栈操作(补充当商为零而余数不为零时的情况)mov cx,bx;总共有bx位进栈,所以循环次数为bxmov si,0s3:pop ax;s3实现将栈中的数据依次出栈放到指定的内存中mov [si],alinc siloop s3okay:pop bxpop sipop axpop dxretshow_str:;子程序push bxpush cxpush simov al,0A0h;每行有80*2=160个字节=0a0hdec dh;行号在显存中下标从0开始,所以减1mul dh;相当于从第(n-1)*0a0h个byte单元开始mov bx,ax;定位好的位置偏移地址存放在bx里(行)mov al,2;每个字符占2个字节mul dl;定位列,结果ax存放的是定位好的列的位置sub ax,2;列号在显存中下标从0开始,又因为是偶字节存放字符,所以减2add bx,ax;此时bx中存放的是行与列的偏移地址mov ax,0B800h;显存开始的地方mov es,ax;es中存放的是显存的第0页的起始地段地址mov di,0;di指向显存的偏移地址,确定指向下一个要处理的字符的位置mov al,cl;cl存放颜色参数,下边cl要用来临时存放要处理的字符mov ch,0;下边cx存放的是每次准备处理的字符S:mov cl,ds:[si]jcxz okmov es:[bx+di],clmov es:[bx+di+i],alinc siadd di,2jmp short sok:pop sipop cxpop bxret
    code ends
    end start

    十一、标志寄存器

    引言

    • CPU内部的寄存器中有一种特殊的寄存器:

      • 1、用来存储相关指令的某些执行结果;
      • 2、用来为CPU执行相关指令提供行为依据;
      • 3、用来控制CPU的相关工作方式。
    • 8086 CPU的标志寄存器只有16位,其中存储的信息通常被称为程序状态字(PSW)。
    • 本章中的标志寄存器(以下简称为flag)。某些指令将影响标志寄存器中的多个标志位,这些被影响的标记位比较全面地记录ill指令的执行结果,为相关的处理提供了所需的依据。
    • flag寄存器是按位起作用的,每一位都有专门的含义,记录特定的信息,与其他寄存器不一样。
    • 8086 CPU的flag寄存器的结构:

    • flag的1、3、5、12、13、14、15位在8086 CPU中没有使用,而0、2、4、6、7、8、9、10、11位都具有特殊的含义。

    11.1 ZF(zero flag)标志

    • flag的第6位是ZF,零标志位,它记录相关指令执行后,结果为0,ZF=1(记录下是0这样的肯定信息),结果不为0,ZF=0(表示结果非0)。
        mov ax,1sub ax,1mov ax,1and ax,0;指令执行后,结果为0,则ZF=1mov ax,2sub ax,1mov ax,1or ax,0;指令执行后,结果为1,则ZF=0
    • 在8086CPU中,add、sub、mul、div、inc、or、and等它们大多都是运算(逻辑运算或是算术运算)指令,是影响标志寄存器的,而mov、push、pop等传送指令对标志寄存器一般没有影响,因为不会产生结果。

    11.2 PF标志

    • flag的第2位是PF,奇偶标志位,记录指令执行后结果所有的二进制位中1的个数。为偶数,PF=1,为奇数PF=0
        mov al,1add al,10;执行结果为00001011B,有3个1,则PF=0mov al,1or al,10;执行后结果为00000011B,有2个1,则PF=1

    11.3 SF(sign flag)标志

    • flag的第7位是SF符号标志位,记录指令执行后结果为负则SF=1,结果为正,SF=0。弱国我们将数据当作无符号数来运算,SF的值没有意义,虽然相关的指令影响了它的值。
    • 有符号数与补码
      • 计算机默认把负数用补码记录。
      • 00000001B,可以看作无符号数1,也可以看作符号数+1;
      • 10000001B,可以看作无符号数129,也可以看作有符号数-127。
    • 补码
    mov al,10000001B
    add al,1
    ;执行指令后al的值是10000010B,无符号数130,有符号数-126

    检测点 11.1

    11.4 CF(carry flag)标志

    • flag的第0位是CF,进位标志位。一般情况下,在进行无符号运算的时候,它记录了运算结果的最高有效位向更高位的进位值或从更高位的借位值。对于位数为N的无符号数,其对应的二进制信息的最高位为N-1位的最高有效位,假想存在第N位。

    • 两个8位的数据运算可能产生进位或者借位,由于这个进位值在8位数中无法保存,8086CPU就用flag的CF位来记录这个进位值。

        mov al.98hadd al,al;执行后(al)=30h,cf=1,cf记录了从最高有效位向更高位的进位值add al,al;执行后(al)=60h,cf=0,cf记录了从更高有效位向更高位的进位值mov al,97hsub al,98h;执行后(al)=ffh,cf=1,cf记录了向更高位的借位值sub al,al;执行后(al)=0,cf=0,cf记录了向更高位的借位值

    11.5 OF(overflow flag)标志

    • 如果运算结果超出了机器所能表达的范围(对于8位有符号数,机器所能表达的范围是-128~127)将产生溢出,对有符号数而言。
    assume cs:codecode segmentstart:mov al,01100010badd al,01100011bmov ax,4c00hint 21hcode ends
    end start

        assume cs:codecode segmentstart:mov al,10001000badd al,11110000bmov ax,4c00hint 21hcode endsend start

    assume cs:codecode segmentstart:mov al,98hadd al,aladd al,almov ax,4c00hint 21hcode ends
    end start
    

    assume cs:codecode segmentstart:mov al,97hsub al,98hadd al,almov ax,4c00hint 21hcode ends
    end start
    

    • CF是对无符号数运算有意义的标志位,而OF是对有符号数运算有意义的标志位; CPU用CF位来记录无符号数运算是否产生了进位,用OF位来记录有符号数是否产生了溢出。用SF位来记录结果的符号
        mov al,98dadd al,99d;对于无符号数运算,98+99没有进位,CF=0;对于有符号数运算,98+99发生溢出,OF=1

    检测点 11.2

    11.6 adc指令

    • adc是带有进位加法指令,利用了CF位上记录的进位值。格式:adc操作对象1,操作对象2,功能:操作对象1=操作对象1+操作对象2+CF。
        mov ax,2mov bx,1sub bx,axadx ax,1;执行后 (ax)=4,相当于计算(ax)+1+CF=2+1+1+4mov ax,1add ax,axadc ax,3;执行后(ax)=5,相当于执行(ax)+3+CF=2+3+0=5mov al,98Hadd al,aladx al,3;执行后 (ax)=34H,相当于执行(ax)+3+CF=30H+3+1=34H
    • 由adc指令前面的指令决定在执行adc指令的时候加上的CF的值的含义,关键在于所加上的CF的值是被什么指令设置的。如果CF的值是被sub指令设置的,那么它的含义就是借位值;如果是被add指令设置的,那么它的含义就是进位值。加法运算先是低位相加,再高位相加加上低位相加产生的进位值。
    • 编程:计算1EF000H+201000H,结果存放在AX(高16位)和BX(低16位)中。
        mov ax,001EHmov bx,0F000Hadd bx,1000Hadc ax,0020H
    • 编程:1EF0001000H+2010001EF0H,结果存放在AX(高16位)、BX(次16位)中和cx(低16位)。
        mov ax,001EHmov bx,0F000Hmov cx,1000Hadd cx,1EF0Hadd bx,1000Hadc ax,0020H
    • 编程:对两个128位数据进行相加
    assume cs:code,ds:datadata segmentdb 16 dup(88H)db 16 dup(11H)data endscode segmentstart:mov ax,datamov ds,axmov si,0mov di,16mov cx,8call add128mov ax,4C00Hint 21Hadd128:push axpush cxpush sipush disub ax,ax;将CF设置为0s:mov ax,[si]adc ax,[di]mov [si],axinc si;不能用add si,2代替inc si;因为会影响cf位inc di;而loop和inc不会影响inc diloop spop dipop sipop cxpop axretcode ends
    end start

    11.7 sbb指令

    • sbb是带借位减法指令,利用了CF位上记录的借位值。格式:sbb 操作对象1,操作对象2,功能是:操作对象1=操作对象1-操作对象2-CF。
    • 利用sbb指令我们可以对任意大的数据进行减法运算。sbb和adc是基于同样的思想设计的两条指令,在应用思路上sbb和adc类似。
    • 编程:计算003E1000H-00202000H,结果放在ax、bx中
        mov bx,1000Hmov ax,003EHsbb bx,2000Hsbb ax,0020H

    11.8 cmp指令

    • cmp是比较指令,功能上相当于减法指令,只是不保存结果。格式:cmp 操作对象1,操作对象2.功能:计算操作对象1-操作对象2但不保存结果,仅仅是根据计算结果对标志寄存器进行设置。
    • cmp指令运算执行后通过做减法将对标志寄存器产生影响,其他相关指令通过识别这些被影响的标志寄存器位来得知比较结果。
        cmp ax,ax;执行后结果为0,ZF=1,PF=1,SF=0,CF=0,OF=0mov ax,8mov bx,3cmp ax,bx;执行后ax、bx的值不变,ZF=0,PF=1,SF=0,CF=0,OF=0
        cmp ax,bx

    • CPU在执行cmp指令时也包含了对无符号数运算和进行有符号数运算,所以利用cmp指令可以对无符号数进行比较也可以对有符号数进行比较。
    • 单纯地考察SF的值不可能知道结果的正负。因为SF记录的只是可以在计算机中存放的相应位数的结果的正负(例如:add ah, al执行后,SF记录的是ah中的8位二进制信息所表示的数据的正负)。如果没有溢出发生的话,实际结果的正负和逻辑上真正结果的正负就一致了。。例如:22H(34)-0A0H(-96)=130=82H(是-126的补码),SF=1。
      • 1、如果SF=1或SF=0,OF=0,逻辑上真正结果的正负=实际结果的正负。
      • 2、如果SF=1或SF=0,OF=1,逻辑上真正结果的负正=实际结果的正负。

    11.9 检测比较结果的条件转移指令

    • 与cmp相配使用,根据cmp指令的比较结果(cmp指令执行后相关标志位的值)进行工作的指令。
    • cmp指令可以同时进行两种比较,无符号数比较和有符号数比较,所以根据cmp指令的比较结果进行转移的指令也分为两种:
      • 根据无符号数的比较结果进行转移的条件转移指令,它们检测ZF、CF的值;
      • 根据有符号数的比较结果进行转移的条件转移指令,它们检测SF、OF、ZF的值。
    • 它们所检测的标志位都是cmp指令进行无符号数比较时候记录比较结果的标志位。
    指令 含义 检测的相关标志位
    je 等于则转移 ZF=1
    jne 不等于则转移 ZF=0
    jb 低于则转移 CF=1
    jnb 不低于则转移 CF=0
    ja 高于则转移 CF=0 and ZF=0
    jna 不高于则转移 CF=1 or ZF=1
    j e ne b nb a na
    jump equal not equal below not below above not above
    • 编程:如果ah的值等于bh则ah的值等于ah的值加ah的值,否则ah的值等于ah的值加上bh的值。
            cmp ah,bhje s;ZF=1则跳转add ah,bhjmp short oks:add ah,bhok:ret
    • je检测的是ZF的位置,不管je前面是什么指令,只要CPU执行je指令时,ZF=1那么就发生转移。
            mov ax,0mov ax,0je sinc axs:inc ax;执行后ax的值等于1,add ax,0使得ZF=1,所以je指令将进行转移。
    • 课堂练习

    • 编程:统计data段中数值为8的字节的个数,用ax保存统计结果。

        ;方案一
    assume cs:codedata segmentdb 8,11,8,1,8,5,63,38data endscode segmentstart:mov ax,datamov ds,axmov bx,0;ds:bx指向第一个字节mov ax,0;初始化累加器mov cx,0s:cmp byte ptr [bx],8;和8进行比较jne next;如果不相等转到next,继续循环inc ax;如果相等就计数值加1next:inc bxloop s;执行后:(ax)=3mov ax,4c00hint 21hcode ends
    end segment 
    ;方案二
    assume cs:codedata segmentdb 8,11,8,1,8,5,63,38data endscode segmentstart:mov ax,datamov ds,axmov bx,0;ds:bx指向第一个字节mov ax,0;初始化累加器mov cx,0s:cmp byte ptr [bx],8;和8进行比较je ok;如果不相等转到ok,继续循环jmp short next;如果不想等就转到next,继续循环ok:inc ax;如果相等就计数值加1next:inc bxloop s;执行后:(ax)=3mov ax,4c00hint 21hcode ends
    end segment
    • 编程:统计data段中数值大于8的字节的个数,用ax保存统计结果。
    assume cs:codedata segmentdb 8,11,8,1,8,5,63,38data endscode segmentstart:mov ax,datamov ds,axmov bx,0;ds:bx指向第一个字节mov ax,0;初始化累加器mov cx,0s:cmp byte ptr [bx],8;和8进行比较jna next;如果大于8转到next,继续循环inc ax;如果大于就计数值加1next:inc bxloop s;执行后:(ax)=3mov ax,4c00hint 21hcode ends
    end segment 

    检测点 11.3

    11.10 DF(direction flag)标志和串传送指令

    • flag的第10位是DF,方向标志位,在串处理指令中,控制每次操作后si(一般指向原始偏移地址)、di(一般指向目标偏移地址)的增减。

      • DF=0:每次操作后si、di递增;
      • DF=1,每次操作后so、di递减。
    • movsb(mov string byte)串传送指令,以字节为单位传送。将ds:si指向的内存单元中的字节送入es:di中,然后根据标志寄存器DF位的值将si和di递增1或递减1。movsw,以字为单位传送。将ds:si指向的内存单元中的字送入es:di中,然后根据标志寄存器DF位的值将si和di递增2或递减2。
    • movsb和movsw进行的是串传送操作中的一个步骤,一般和rep配合使用,格式:rep movsb,rep的作用是根据cx 的值,重复执行后面的串传送指令。由于每次执行一次movsb指令si和di都会递增或递减指向后一个单元或前个单元,则rep movsb就可以循环实现(cx)个字符的传送。
      • 1、传送的原始位置;
      • 2、传送的目的位置;
      • 3、传送的长度;
      • 4、传送的方向。
      • movsb功能:((es)*16+(di))=((ds)*16+(si)),如果DF=0,则(si)=(si)+1,(di)=(di)+1;如果DF=1,则(si)=(si)-1,(di)=(di)-1。
    • 由于flag的DF位决定着串传送指令执行后,si和di改变的方向,8086CPU提供两条指令对DF位进行设置:
      • cld指令:将标志寄存器的DF位设置为0;
      • std指令:将标志寄存器的DF位设置为1。

    11.11 pushf和popf

    • pushf的功能 是件标志寄存器的值压栈,popf是从栈中弹出数据m,送入标志寄存器中。pushf和popf为直接访问标志寄存器提供了一种方法。
        ;下面的程序执行后ax的值是多少?mov ax,0push axpopfmov ax,0fff0hadd ax,0010hpushfpop ax and al,11000101band ah 00001000b
    • 编程:用串传送指令将data段总的第一个字符串复制到它后面的空间中。
    assume cs:codedata segmentdb'welcome to masm!'db 16 dup(0)data endscode segmentstart:mov ax,datamov ds,axmov si,0;指向data:0mov es,axmov di,16;指向data:16mov cx,16;rep循环16次cld;设置DF=0,正向传送rep movsbmov ax,4c00hint 21hcode ends
    end start

    • 用串传送指令将F00H段中的最后16个字符复制到data段中
    assume cs:codedata segmentdb 16 dup(0)data endscode segmentstart:mov ax,0f00hmov ds,axmov si,0ffffh;指向f0000:ffffmov ax,datamov es,axmov di,16;指向data:15mov cx,16;rep循环16次std;设置DF=1,逆向传送rep movsbmov ax,4c00hint 21hcode ends
    end start

    检测点 11.4

    11.12 标志寄存器在Debug中的表示

    标志 值为1的标记 值为0的标记
    OF OV NV
    SF NG PL
    ZF ZR NZ
    PF PE PO
    CF CY NC
    DF DN UP

    十二、内中断

    引言

    • 中断时CPU处理外部突发事件的一个重要技术。它能使CPU在运行过程中对外部事件发出的中断请求几时进行处理,处理完成后又立即返回断电,基础进行CPU原来的工作。引起中断的原因或是说发出中断请求的来源叫做中断源。根据中断源的不同,可以把中断分为硬件中断和软件中断两大类,而硬件中断又可以分为外部中断和内部中断两类。
    • 外部中断一般是指由计算器外部设备发出的中断请求。如:键盘中断、打印机中断、定时器中断等。外部中断时可以屏蔽的中断,业绩是说利用中断控制器可以屏蔽这些外部设备的中断请求。
    • 内部中断是指因硬件出错(如突然掉电)或运算出错(如除数为0、单步中断)所引起的中断。内部中断是不可屏蔽的。
    • 软件中断其实并不是真正的中断,它们只是可被调用执行的一般程序以及DOS的系统功能调用(int 21)等都是软件中断。
    • 中断的优先权:
      • 1、除法出错、溢出中断、软件中断;
      • 2、不可屏蔽中断;
      • 3、可屏蔽中断;
      • 4、单步中断。
    • 中断信息中包含有标识中断源的类型码。根据CPU的设计,中断源类型码的作用就是用来定位中断处理程序。

    12.1 内中断的产生

    • 8086CPU内部有以下情况发生时将产生相应的中断信息:

      • 1、除法错误;
      • 2、单步执行;
      • 3、执行into指令;
      • 4、执行int指令。
    • 8086CPU中的中断类型码如下:
      • 1、除法错误:0
      • 2、单步执行:1
      • 3、执行into指令:4
      • 4、执行int指令,该指令格式为int n,n为立即数是提供给CPU的中断类型码。

    12.2 中断处理程序

    • CPU在收到中断信息后立即去执行该中断信息的处理程序。

    12.3 中断向量表

    • 中断向量列表就是中断向量(中断处理程序的入口地址)的列表,其在内存中保存,存放着256个中断源说对应的中断处理程序的入口。8086PC机中断向量表放在内存地址0处。从内存0000:0000到0000:03FF的1024(一个物理地址是由段地址和偏移地址构成,要用4个字节来存放)个单元中存放着中断向量表。
    • CPU用8位的中断类型码通过中断向量表找到相应的中断处理程序的入口地址。中断向量表中存放的就是各个类型的处理程序的地址,8位的类型码是个索引。

    12.4 中断过程

    • 用中断码在中断向量表中找到中断处理程序的入口地址,用它来设置CS和IP,使CPU执行中断程序。用中断类型码找到中断向量并用它设置CS和IP,这个工作室由CPU的硬件自动完成的,这个工作的过程被称为中断过程。
    • 8086CPU的中断过程:
      • 1、从中断信息中取得中断类型码;
      • 2、标志寄存器的值入栈,以保护标志位;
      • 3、设置标志寄存器的第8位TF和第9位IF的值为0;
      • 4、CS的内容入栈,IP的内容入栈;
      • 5、从内存地址为中断类型码* 4和中断类型码 *4+2的两个字单元中读取中断处理程序的入口地址设置IP和CS。
      • 在最后一步完成后,CPU开始执行由程序员编写的中断处理程序。

    12.5 中断处理程序和iret指令

    • 常规的步骤

      • 1、保存用到的寄存器;
      • 2、处理中断;
      • 3、 恢复用到的寄存器;
      • 4、 用iret指令返回。
    • iret指令的功能为相应的按顺序恢复之前保存起来的IP、CS地址和标志位寄存器。用汇编语法描述为:
    pop IP
    pop CS
    popf

    12.6 除法错误中断的处理

    • 当CPU执行dvi等处罚指令的时候,如果发生了除法溢出错误,将产生中断类型码为0的中断信息,CPU将检测到这个信息然后引发中断过程,转去执行0号中断所对应的中断处理程序。
    assume cs:codesgcodesg segmentstart:mov ax,1000hmov bh,1    div bhcodesg endsend start


    12.7 编程处理0号中断

    • 改变0号中断处理程序的功能,在屏幕中间显示字然后再返回操作系统。

      • 当发生除法溢出时产生0号中断信息,引发中断过程。

        • 此时CPU将进行以下工作(中断过程)
        • 当中断0发生时,CPU将转去执行中断处理程序。
        • 先进行相关处理,然后向显示缓冲区送字符串,最后返回。
      • 改变后的中断处理程序应该放在内存中,因为除法溢出随时可能发生,CPU随时都可能将CS:IP指向改变后的中断处理程序的入口执行程序。
      • 把程序存入内存,修改向量表(即将内存地址登记在中断向量表的对应表项中),中断时调用这个内存。

        除法溢出对应的中断类型码为0,它的中断处理程序的入口地址应该从0* 4+2地址单元开始存放,段地址存放在0* 4+2字单元中,偏移地址存放在0*4字单元中。也就是改变后的中断处理程序的段地址0存放在0000:0002字单元中,偏移地址200H存放在0000:0000字单元中。如果要显示的字符串在程序的data段中,那么程序执行完成后返回,它所占用的内存空间被系统释放,在其中存放的信息也可能被别的信息覆盖。

    assume cs:codecode segmentstart:mov ax,csmov ds,axmov si,offset do0;设置ds:di指向源地址mov ax,0mov es,axmov di,200h;设置es:si指向目的地址mov cx,offset do0end - offset do0;设置cx为传输长度,编译器可以识别加减乘除运算符cld;设置传输方向为正rep movsbmov ax,0;设置中断向量表mov es,axmov word ptr es:[0*4],200hmov word ptr es:[0*4+2],0mov ax,4c00hint 21hdo0:jmp short do0startdb"welcome to masm!";在代码段中存储数据do0start:mov ax,csmov ds,axmov si,202h;jmp short do0start这条指令栈两个字节;显示字符串,设置es:di指向字符串mov ax,0b800h;显存空间,直接显示在显示器上mov es,axmov di,12*160+36*2;这只es:di指向显存空间的中间位置mov cx,16;设置cx为字符串(welcome to masm!)长度s:mov al,[si]mov es:[di],alinc siadd di,1mov al,02hmov es:[di],aladd di,1loop smov ax,4c00hint 21hdo0end:nopcode ends
    end start

    ![do0](https://img-blog.csdn.net/20170522081857031?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvR2liYnNfcA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast) ![do02](https://img-blog.csdn.net/20170522081929203?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvR2liYnNfcA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)

    12.8 单步中断

    • CPU执行完一条指令之后,如果检测到标志寄存器的TF位为1,则产生单步中断引发中断过程。单步中断的中断类型码为1,它所引发的中断过程如下:

      • 1、取得中断类型码;
      • 2、标志寄存器入栈,TF、IF设置为0;
      • 3、CS、IP入栈;
      • 4、指向指定类型码的中断向量表。

    12.9 响应中断的特殊情况

    • 在有些情况下CPU在执行完当前指令后,即便是发生了中断也不会响应。

      在执行完向ss寄存器传送数据的指令后,即便检测到了中断信号CPU也不会响应。因为ss:sp指向栈顶,对他们的设置应该连续完成。如果在执行完设置ss指令后mCPU响应中断引发中断过程,要在栈中压入标志寄存器、CS和IP的值。而ss改变,sp并未改变则ss:sp指向不是正确的栈顶将引发错误。

    • 我们要将栈顶设置为1000:0,不应该隔开

    应该 不应该
    mov ax,1000h mov ax,1000h
    mov ss,ax mov ss,ax
    mov sp,0 mov ax,0
    mov ax,0 mov sp,0

    十三、int 指令

    引言

    • 在第12章中了解中断过程和除法错误中断和单步中断的处理,这章了解int指令。

    13.1 int 指令

    • int格式:int n,n为中断类型码,它的功能是引发中断过程。CPU执行int n之力量能够,相当引发一个n号的中断过程,可以在程序中使用int指令调用任何一个中断的中断处理程序。执行过程如下:

      • 中断过程从,此处去执行n号中断的中断处理程序。

        assume cs:codecode segmentstart:mov ax,0b800hmov es,axmov byte ptr es:[12*160+40*2],'!'int 0;执行int 0指令,引发中断过程,执行0号中断处理程序code endsend start


    • int指令的最终功能和call类似,都是调用一段程序。一般情况下系统将一些具有一定功能的子程序以中断处理程序的方式提供给应用程序调用,也可以自己编写一些中断处理程序供别人使用。

    13.2 编写供应用程序调用的中断例程

    • 中断处理程序简称为中断例程。
    • 实例1:编写、安装中断7ch的中断例程实现求一word型数据的平方。
      • 1、编程实现求平方功能的程序;
      • 2、安装程序在0:200处;
      • 3、设置中断向量表将程序的入口地址保存在7ch表项中,使其成为中断7ch的中断例程。
                 ;计算
    ssume cs:codecode segmentstart:mov ax,3456int 7chadd ax,axadc ax,dxmov ax,4c00hint 21hcode endsend start;安装程序assume cs:codecode segmentstart:mov ax,csmov ds,axmov si offset sqr;设置ds:si指向源地址mov ax,0mov es,axmov di,200h;设置es:di指向目的地址mov cx,offset sqrend- offset sqr;设置cx为传输长度cld;设置传输方向为正rep movsbmov ax,0mov es,axmov word ptr es:[7ch*4],200hmov word ptr ws:[7ch*4+2],0mov ax,4c00hint 21hsqr:mul axiretsqrend:nopcode ends
    end start
    • CPU执行int 7ch指令进入中断例程之前,标志寄存器、当前的CS和IP都被压入栈中,在执行完中断例程后,用iret指令恢复int 7ch执行前的标志寄存器和CS和IP的值,从而接着执行应用程序。
    • int指令和iret指令配合使用与call指令和ret指令配合使用具有相似的思路。
    • 实例2:编写、安装中断7ch的中断例程,实现将一个全是字母,以0为结尾的字符串转化为大写。
    assume cs:codedata segmentdb'conversation',0data endscode segmentstart:mov ax,datamov ds,axmov si,0int 7chmov ax,4c00hint 21hcode ends end startassume cs:codecode segmentstart:mov ax,csmov ds,axmov si,offset capitalmov ax,0mov es,axmov di 200hmov cx,offset capitalend - offset capitalcldrep movsbmov ax,0mov es,axmov word ptr es:[7ch*4],200hmov word ptr es:[7ch*4+2],0mov ax,4c00hint 21hcapital:push cxpush sichange:mov cl,[si]mov ch,0jcxz okand byte ptr [si],11011111binc sijmp short changeok:pop sipop cxiretcapitalend:nopcode ends
    end start
    • 要注意用到的寄存器冲突。

    13.3 对int、iret和栈的深入理解

    • 中断处理程序和iret指令
    • 编程:用7ch中断例程完成loop指令的功能,在屏幕中间显示80个”!”.

      loop指令需要循环次数和到标号的位移。为了模拟loop指令7ch中断例程应具备下面dec cx和如果cx的值不等于0则转移到标号s处。

      • int 7ch引发中断过程后,进入7ch中断例程在中断过程中当前的标志寄存器、CS和IP都要压栈。此时压入的CS和IP中的内容分别是调用程序的段地址(可以认为是标号s的段地址)和int 7ch后一条指令的偏移地址(即标号se的偏移地址)。使用iret指令用栈中的内容设置CS、IP,从而实现转移到标号s处。
      assume cs:codecode segmentstart:mov ax,0b800h;显存地址mov es,axmov di,160*12mov bx,offset s- offset se;设置从标号s的转移位移mov cx,80s:mov byte ptr es:[di],'!'add di,2int 7ch;如果cx的值不等于0则转移到标号s处se:nopmov ax,4c00hint 21h
      code ends
      end start;7ch中断例程lp:push bpmov bp,sp;dec cxjcxz lpretadd [bp+2],bxlpret:pop bpiret

    13.4 BIOD和DOS所提供的中断例程

    • bios中主要包含以下几部分内容:

      • 1、硬件操作系统的检测和初始化程序;
      • 2、外部中断和内部中断的中断例程;
      • 3、用于对硬件设备进行I\O操作的中断例程;
      • 4、其他和硬件系统相关的中断例程。
    • bios和dos在所提供的中断例程中包含了许多子程序,可以用int指令直接调用。和硬件设备相关的dos中断例程中一般都调用了bios的中断例程

    13.5 bios和dos中断例程的安装过程

    • 1、开机后8086CPU一加电初始化CS和IP,自动执行FFFF:0处指令,转去执行bios中的硬件系统检测和初始化程序。
    • 2、初始化程序将建立bios所支持的中断向量,即将biso提供的中断例程的入口地址登记在中断向量表中。
    • 3、硬件系统检测和初始化完成后,调用int 19h进行操作系统的引导。从此将计算机交由操作系统控制。
    • 4、dos启动后除完成其他工作外,还将它所提供的中断例程装入内存并建立相应的中断向量。

    13.6 bios中断例程应用

    • bios和dos提供的中断例程douyongah来传递内部子程序的编号。
    • int 10h中断例程是bios提供的中断例程,其中包含了多个和屏幕输出相关的子程序。int 10h中断例程的设置光标位置功能:
        mov ah,2;表示调用10h号中断例程的2号子程序,功能为设置光标位置mov bh,0;页号mov dh,5;行号mov dl 12;列号int 10h;
    ;功能为在光标位置显示字符功能
    mov ah,9;置光标,调用9号子程序
    mov al,'a';字符
    mov bl,7;颜色属性,和在显存中的属性字节的格式相同
    mov bh,0;第0页
    mov cx,3;字符重复个数
    int 10h
    • 编程:在屏幕的第5行12列显示3个红底高亮闪烁绿色的’a’
        assume cs:codecode segmentmov ah,2;设置光标mov bh,0;第0页mov dh,5;dh中放行号mov dl,12;dl中放列号int 10mov ah,9;设置光标mov al,'a';字符mov bl,11001010b;颜色属性mov bh,0;第0页mov cx,3;字符重复个数int 10hmov ax,4c00hint 21hcode endsend

    13.7 dos中断例程应用

    • int 21h 中断例程
    mov ah,4ch;程序返回
    mov al,0;返回值0是正常返回
    ;合起来写就是 mov ax,4c00h
    int 21h
    • int 2h中断例程还具有在光标位置显示字符串的功能

      ds:dx;要显示的字符串需要用 $ 作为结束符
      mov ah,9;功能号9,表示在光标位置显示字符串
      int 21h

    十四、端口

    引言

    • CPU可以直接读写3个地方的数据:

      • 1、CPU内部的寄存器;
      • 2、内存单元;
      • 3、 端口。

    14.1 端口的读写

    • mov、push、pop等死内存读写指令。in和out是端口读指令写指令时in是从端口读取数据,out是往端口写入数据。in和out指令只能用ax或al来存放从端口中读入的数据或要发送到端口中的数据,访问8位短空时用al,访问16位端口时用ax。

      • 访问内存:

        • mov ax,ds:[8+0];假设(ds)=0
        • 执行时,与总线相关的操作:
          • 1、CPU通过地址线信息8发出;
          • 2、CPU通过控制线发出内存读命令,选中存储器芯片并通知它将要从中读取数据;
          • 3、 存储器将8号单元中的数据通过数据线送入CPU。
      • 访问端口:
        • in al,60h;从60h号端口读入一个字节。
        • 执行时与总线相关的操作:
          • 1、CPU通过地址线将地址信息60h发出;
          • 2、CPU通过控制线发出端口读命令,选中端口所在的芯片,并通知它,将要从中读取数据;
          • 3、端口所在的芯片将60h端口中的数据通过数据线送入CPU。
                      ;对0~255以内的端口进行读写in al,20h;从20h端口读入一个字节out 20h,al;往20h端口写入一个字节;对256~65535的端口进行读写时,端口放在dx中mov dx,3f8h;将端口号3f8h送入dxin al,dx;从3f8端口读入一个字节out 3f8h,al;往3f8h端口写入一个字节

    14.2 CMOS RAM芯片

    • CMOA RAM特征:

      • 1、包含一个实时钟和一个有128个存储单元的RAM存储器。(早期的计算机位64个字节)。
      • 2、该芯片靠电池供电。因此关机后其内部的实时钟仍可正常工作,RAM中的信息不会丢失。
      • 3、128个字节的RAM,内部实时钟占用0~0dh单元来保存时间信息默契与大部分单元用于保存系统配置信息,供系统启动时bios程序读取。
      • bios也提供了相关的程序使用户在开机时配置CMOS RAM中的系统信息。

      • 4、该芯片内部有两个端口,端口地址为70h和71
        h。CPU通过这两个端口读写CMOS RAM。

      • 5、70h为地址端口,存放要访问的CMOS RAM单元的地址;71h为数据端口,存放从选定的CMOS RAM单元中读取的数据,或要写入到其中的数据。
    • CPU对CMOS RAM的读写分两步进行,以读2号单元为例:
      • 1、将2送入端口70h;
      • 2、从71h读出2号单元的内容。

    14.3 shl和shr指令

    • shl为逻辑左移指令功能为:

      • 1、将一个寄存器或内存单元中的数据向左移位;
      • 2、将最后移出的一位写入CF中;
      • 3、最低位用0补充。
              mov al,01001000bshl al,1;将al中的数据左移一位;执行后al的值是10010000b,CF=0
    • 如果移动位数大于1时,必须将移动位数放在cl中。
              mov al,01010001bmov cl,3shl al,cl;执行后al的值为10001000b,cf=0
    • 二进制逻辑左移一位,相当于执行x=x*2(2是进制位)
    mov al,00000001b 执行后al的值等于00000001b=1
    shl al,1 执行后al的值等于00000010b=2
    shl al,1 执行后al的值等于00000100b=4
    shl al,1 执行后al的值等于00001000b=8
    mov cl,3
    shl al,cl 执行后al的值等于01000000b=64
    • shr为逻辑左移指令功能为:

      • 1、将一个寄存器或内存单元中的数据向右移位;
      • 2、将最后移出的一位写入CF中;
      • 3、最高位用0补充。
    • 二进制逻辑右移一位,相当于执行x=x/2(2是进制位)

    14.4 CMOS RAM中存储的时间信息

    • 在CMOS RAM中以每个信息一字节存放着当前的时间信息:年09h,月08h,日07h,时04h,分02h,秒00h。这些数据以BCD码的方式存放,BCD码以4位为一位。

    • 数值26BCD码表示为0010 0110,用两个BCD码表示两位十进制,高4位表示十位,低4位表示各位。

    • 编程:在屏幕中间显示当前的月份。
        assume cs:codecode segmentstart:;向地址端口70h写入要访问的单元地址,读取CMOS RAM的信息mov al,8out 70h,alin al,71h;从数据端口中取得指定单元中的数据mov ah,al;al中为从CMOS RAM的8号端口读出数据mov cl,4shr ah,cl;ah中为月份的十位数码值and al,00001111b;ah中为月份的个位数值码add ah,30h;BCD码值+30h(字符'0')=十进制对应的ASCII码add al,30h;用BCD码表示的月份以十进制的形式显示到屏幕上。mov bx,0b800h;显存mov es,bxmov byte ptr es:[160*12+40*2],ah;显示月份的十位数码mov byte ptr es:[160*12+40*2+2],al;显示月份的个位数码mov ax,4c00hint 21hcode endsend start

    十五、外中断

    15.1接口芯片和端口

    • CPU通过端口和外设进行联系。在PC系统的接口卡和主板上,装有各种接口芯片。这些外设接口芯片的内部有若干寄存器,CCPU将这些寄存器当作端口来访问。外设的输入不直接送入内存和CPU,而是送入相关的接口芯片的端口中;CPU向外设的输出也不是直接送入到外设而是先送入端口再由相关的芯片送到外设。

    15.2外中断信息

    • 外中断源有两类:

      • 1、可屏蔽中断;

        • 可屏蔽中断时CPU可以不响应的外中断。CPU是否响应可屏蔽中断要看标志寄存器的IF位的设置。
      • 2、不可屏蔽中断
    • 当CPU检测到可屏蔽中断信息时:
      • 如果IF=1,则CPU在执行完当前指令后响应中断引发中断过程。
      • 如果IF=0,着不响应可屏蔽中断。
      • 内中断过程
    • > 可屏蔽中断所引发的中断过程,除在第一步的实现上有所不同外,基本上和内中断的中断过程相同。因为可屏蔽中断信息来自于CPU外部,中断类型码是通过数据总线送入CPU的;而内中断的中断类型码是在CPU内部产生的。在中断过程中将IF置0的原因是在进入中断处理程序后禁止其他的可屏蔽中断。
    • 8086CPU提供的设置IF的指令如下:
      • sti,设置IF=1;
      • cli,设置if=0.
    • 不可屏蔽中断是CPU必须响应的外中断。当CPU检测到不可屏蔽中断信息时,则在执行完当前指令后立即响应引发中断过程。对于8086CPU不可屏蔽的中断类型码固定为2。所以中断过程中不需要取中断类型码。几乎所有外设引发的外中断都是可屏蔽中断。
    • 不可屏蔽中断过程:
      • 1、标志寄存器入栈,IF=0,TF=0’
      • 2、CS和IP入栈;
      • 3、(IP)=(8),(CS)=(0AH)

    15.3PC机及键盘的处理过程

    • 键盘输入的处理过程:

      • 1、键盘输入产生扫描码;
      • 2、扫描码送入60h端口;
      • 3、引发9号中断;
      • 4、执行int 9中断例程。
      • > 前三步由硬件系统自动完成,第四步用户可以修改int 9中断程序。
    • 按下一个键产生的扫描码称为通码,松开一个键产生的扫描码称为断码。扫描码被送入主板上的相关接口芯片端口地址为60h的寄存器中。
    • 扫描码长度为一个字节,通码的第7位为0,断码的第7位为1。即断码=通码+80h。

    • bios提供了int 9中断例程,用来进行基本键盘输入处理,主要的工作如下:

      • 1、读出60h端口中的扫描码;
      • 2、如果是字符键的扫描码就将它和它所对应的字符码(ASCII码)送入内存中的bios键盘缓冲区;
        • 键盘的输入到达60h端口时相关的芯片就会向CPU发出中断类型码为9的可屏蔽中断信息。
        • CPU检测到该中断信息后,如果IF=1,则相应中断,引发中断过程,转去执行int 9中断例程。
        • 如果是控制键(如ctrl)和切换键(如capslock)的扫描码,则将其转变为状态字节(用为进制位记录控制键和切换键状态的字节)写入内存中存储状态字节的单元
      • 3、键盘系统进行相关的控制。如向相关芯片发出应答信息。
    • bios键盘缓冲区是系统启动后mbios用于存放int 9中断例程所接收的键盘输入的内存区。该内存可以存储15个键盘输入,在bios键盘缓冲区中一个键盘输入用一个字单元存放,高位字节存放扫描码,低位字节存放字符码。0040:17单元存储键盘状态字节该字节记录了控制键和切换键的状态
      • 0:置1表表示按下右shift键
      • 1:置1表表示按下左shift键
      • 2:置1表表示按下ctrl
      • 3:置1表表示按下alt
      • 4:置1表表示按下scroll指示灯亮
      • 5:置1表表示按下numlock,小键盘输入的是数字
      • 6:置1表表示按下capslock,输入大写字母
      • 7:置1表表示按下insert。处于删除状态

    15.4编写int 9中断

    • 键盘输入的处理过程
    • 编程:在屏幕中间依次显示让人看清的a~z,按下esc键后改变显示的颜色。
        ;显示字符
    code segmentstart:mov ax,0b800hmov es,axmov ah,'a's:mov es:[160*12+40*2],ahinc axcmp ah,'z'jna smov ax,4c00hint 21hcode ends
    end start
        ;延迟显示字符assume cs:codestack segmentdb 128 dup(0)stack endscode segmentstart:mov ax,stackmov ss,axmov sp,128mov ax,0b800hmov es,axmov ah,'a's:mov es:[160*12+40*2],ahcall delayinc ahcmp ah,'z'jna smov ax,4c00hint 21hdelay:push axpush dxmov dx,10h;循环100次,延迟的时间和CPU的计算能力成反比mov ax,0s1:sub ax,1sbb dx,0cmp ax,0jne s1cmp dx,0jne s1pop dxpop axretcode ends
    end start
        ;实现IF=0,TF=0步骤pushfpop axand ah,11111100bpush axpopf
    • int指令在执行时CPU进行的工作
    • 完整程序
    assume cs:codestack segmentdb 128 dup(0)stack endsdata segmentdw 0,0data endscode segmentstart:mov ax,stackmov ss,axmov sp,128mov ax,datamov ds,axmov ax,0mov es,axpush es:[9*4]pop ds:[0]push es:[9*4+2]pop ds:[2];将原来的int9中断例程的入口地址保存mov word ptr es:[9*4+2],offset int9mov es:[9*4+2],cs;在中断向量表中设置新的int 9中断例程的入口地址mov ax,0b800hmov es,axmov ah,'a's:mov es:[160*12+40*2],ahcall delayinc ahcmp ah,'z'jna smov ax,0mov es,axpush ds:[0]pop es:[9*4]push ds:[2]pop es:[9*4+2];将中断向量表中int9中断例程的入口恢复为原来的地址mov ax,4c00hint 21hdelay:push axpush dxmov dx,10h;循环100次,延迟的时间和CPU的计算能力成反比mov ax,0s1:sub ax,1sbb dx,0cmp ax,0jne s1cmp dx,0jne s1pop dxpop axret;新的int 9中断例程int9:push axpush bxpush esin al,60hpushfpushfpop bxand bh,11111100bpush bxpopfcall dword ptr ds:[0];对int指令进行模拟,调用原来的int9中断例程cmp al,1;esc键盘扫描码jne int9retmov ax,0b800hmov es,axinc byte ptr es:[160*12+40*2+1];改变颜色int9ret:pop espop bxpop axiretcode ends
    end start
    

    15.5安装新的int 9中断例程

    • 小甲鱼版(笔者未成功运行)
    assume cs:codestack segmentdb 128 dup(0)stack endsdata segmentdw 0,0data endscode segmentstart:mov ax,stackmov ss,axmov sp,128mov ax,datamov ds,axmov ax,0mov es,axpush es:[9*4]pop ds:[0]push es:[9*4+2]pop ds:[2];将原来的int9中断例程的入口地址保存mov word ptr es:[9*4+2],offset int9mov es:[9*4+2],cs;在中断向量表中设置新的int 9中断例程的入口地址mov ax,0b800hmov es,axmov ah,'a's:mov es:[160*12+40*2],ahcall delayinc ahcmp ah,'z'jna smov ax,0mov es,axpush ds:[0]pop es:[9*4]push ds:[2]pop es:[9*4+2];将中断向量表中int9中断例程的入口恢复为原来的地址mov ax,4c00hint 21hdelay:push axpush dxmov dx,10000h;循环100次,延迟的时间和CPU的计算能力成反比mov ax,0s1:sub ax,1sbb dx,0cmp ax,0jne s1cmp dx,0jne s1pop dxpop axret;新的int 9中断例程int9:push axpush bxpush esin al,60hpushfpushfpop bxand bh,11111100bpush bxpopfcall dword ptr ds:[0];对int指令进行模拟,调用原来的int9中断例程cmp al,1;esc键盘扫描码jne int9retmov ax,0b800hmov es,axinc byte ptr es:[160*12+40*2+1];改变颜色int9ret:pop espop bxpop axiretcode ends
    end start
    • 王爽原版(笔者未成功运行)
    assume cs:codestack segmentdb 128 dup(0)stack endscode segmentstart:mov ax,stackmov ss,axmov sp,128push cspop dsmov ax,0mov es,axmov si,offset int9;设置ds:si指向源地址mov di,204h;设置es:di指向目的地址mov cx,offset int9end - offset int9;设置cx为传输长度cld;设置传输方向rep movsbpush es:[9*4]pop es:[200h]push es:[9*4+2]pop es:[202h]climov word ptr es:[9*4],204hmov word ptr es:[9*4+2],0stimov ax,4c00hint 21hint9:push axpush bxpush cxpush esin al,60hpushfcall dword ptr cs:[200h];当此中断例程执行时(CS)=0cmp al,3bh;f1的扫描码jne int9retmov ax,0b800hmov es,axmov bx,1mov cx,2000s:inc byte ptr es:[bx]add bx,2loop sint9ret:pop espop cxpop bxpop axiretint9end:nopcode ends
    end start

    第16章 直接定址表

    16.1 描述单元长度的标号

    assume cs:code
    cod segmenta:db 1,2,3,4,5,6,7,8b:dw 0start:mov si,offset amov bx,offset bmov cx,8s:mov al,cs:[si]mov ah,0add cs:[bx],axinc siloop smov ax,4c00hint 21h
    code ends
    end start
    ;代码中的 s、start等都是标号,表示了内存的地址

    在code段中使用的标号a,b后面没有:,因此他们可以同时描述内存地址和单元长度的标号

    assume cs:code
    cod segmenta db 1,2,3,4,5,6,7,8 ;描述了地址code:0,和从这个地址开始以后的内存单元都是直接单元b dw 0 ;则b是code[8]start:mov si,0mov cx,8s:mov al,a[si] ;相当于mov al,cs:0[si]mov ah,0add b,axinc siloop smov ax,4c00hint 21h
    code ends
    end start
    

    检测点 16.1

    16.2 在其他段中使用数据标号

    • 注意:在后面加有:的地址标号只能在代码段中使用,不能在其他段中使用。

      assume cs:code,ds:data
      cod segment
      a:db 1,2,3,4,5,6,7,8
      b:dw 0
      data endsstart:mov ax,datamov ds,axmov si,0
      s:mov al,a[si]mov ah,0add b,axinc siloop smov ax,4c00hint 21h
      code ends
      end start
    • 如果现在代码段中直接用数据标号访问数据,则需要用伪指令assume将标号所在的段和一个段寄存器联系起来。 我们可以将标号当作数据来定义,此时编译器将标号所表示的地址当作数据的值。

    data segmenta db 1,2,3,4,5,6,7,8b dw 0c dw a,b;相当于 c dw offset a,offset b
    data endsdata segmenta db 1,2,3,4,5,6,7,8b dw 0c dd a,b;相当于 c dw offset a,seg a,offset b,seg b;seg操作符,功能是取得某一标号的段地址
    data ends
    

    16.3 直接定址表

    • 利用表,在两个数据集合之间建立一种映射关系,使我们可以利用查表的方法根据给出的数据得到其在另一集合中对应数据

      • 目的:

        1. 为了算法的清晰和简洁
        2. 为了加快运算速度
        3. 为了使程序易于扩充
    • 小练习,编写子程序,以十六进制的形式在屏幕中间显示给定的byte型数据。小技巧,利用映射关系,0-9数值+30h=对应字符的ascii值,10-15和A到F之间的银色关系是:数值+37h=对应字符的ascii的值

      assume cs:code
      code:segmentmov al,0ehcall showbytemov ax,4c00hint 21;子程序,用al传送要显示的数据showbyte:jmp short showtable db '1023456789ABCDEF';字符表show:push bxpush esmov ah,alshr ah,1shr ah,1shr ah,1shr ah,1;右移4位,ah中得到高4位的值and al,00001111b;al中为低4位mov bl,ahmov bh,0mov ah,table[bx];用高4位的值作为相对于table的便宜,取得对应的字符mov bx,0b800hmov es,bxmov es:[160*12+40*2],ahmov bl,almov bh,0mov al,table[bx];用低4位的值作为相对于table的偏移,取得对应的字符mov es:[160*12+40*2+2],alpop espop bxretcode ends
      end start 

    16.4 程序入口地址的直接定址表

    • 小练习

      1. 清屏:将显存中当前屏幕中的支付设为空格;
      2. 设置前景色:设置显存中当前屏幕中处于奇地址的属性字节的第0、1、2位;
      3. 设置背景色:设置显存中当前屏幕中处于奇地址的属性字节的第4、5、6位;
      4. 向上滚动一行:依次将第n+行的内容复制到第n行处,最后一行为空。
    ;================================入口函数1=====================================
    ;入口函数说明;
    ;用ah传递功能号,0是清屏,1是设置前景色,2是设置背景色,3是向上滚动一行setscreen:jmp short settable dw sub1,sub2,sub3,sub4set:push bxcmp ah,3;判断传递的功能号是否大于3ja sretmov bl,ahmov bh,0add bx,bx;根据ah中的功能号计算对应子程序的地址在table表中的偏移call word ptr table[bx];调用对应的子程序sret;pop bxiret;================================入口函数2=====================================
    ;入口函数说明;
    ;用ah传递功能号,0是清屏,1是设置前景色,2是设置背景色,3是向上滚动一行setscreen:cmp ah,0je do1cmp ah,1je do2cmp ah,2je do3cmp ah,3je do4jmp short sretdo1:call sub1jmp short sretdo2:call sub2jmp short sretdo3:call sub3jmp short sretdo4:call sub4jmp short sret;子功能==========================================================================;清屏
    sub1:push bxpush cxpush esmov bx,0b800hmov es,bxmov bx,0mov cx,2000sub1s:mov byte ptr es:[bx],''add bx,2loop sub1spop espop cxpop bxret;设置前景色
    sub2:push bxpush cxpush esmov bx,0b800hmov es,bxmov bx,1mov cx,2000sub2s:mov byte ptr es:[bx],11111000bor es:[bx],aladd bx,2loop sub2spop espop cxpop bxret;设置背景色
    sub3:push bxpush cxpush esmov cl,4shl al,clmov bx,0b800hmov es,bxmov bx,1mov cx,2000sub3s:mov byte ptr es:[bx],10001111bor es:[bx],aladd bx,2loop sub3spop espop cxpop bxret;向上滚动一行
    sub4:push cxpush sipush dipush espush dsmov si,0b800hmov es,simov ds,simov si,160;ds:si指向第n+行mov di,0;es:di指向第n行cldmov cx,24;共复制24行sub4s:push cxmov cx,160rep movsb;复制pop cxloop sub4smov cx,80mov si,0sub4s1:mov byte ptr es:[160*24+si],'';最后一行清空add si,2loop sub4s1pop dspop espop dipop sipop cxret;结束

    第十七章 使用BIOS进行键盘输入和磁盘读写

    略。。。。。。

    笔者看不下去了。。。。有兴趣的读者可以继续找相关的资料看。。。

    END

汇编入门(长文多图,流量慎入!!!)相关推荐

  1. 深度学习的几何观点:1流形分布定律、2学习能力的上限。附顾险峰教授简历(长文慎入,公号回复“深度学习流形分布”可下载PDF资料)

    深度学习的几何观点:1流形分布定律.2学习能力的上限.附顾险峰教授简历(长文慎入,公号回复"深度学习流形分布"可下载PDF资料) 原创: 顾险峰 数据简化DataSimp 今天 数 ...

  2. 关系代数至少选修两门课_高中化学:选修三or选修五——一个帮你节省三个月的复习时间的重要选择(本文无图,浮躁慎入)...

    比较符合以下情况的同学,可以选择选修五: 1.学校属本地区名校,并且没有强制要求所有学生必须选择选修三. 2.学校老师要求选择选修五,或倾向于选修五多讲,选修三少讲. 3.选修五掌握较好的同学. 这些 ...

  3. 长文慎入,如何快速开发区块链游戏

    长文慎入,如何快速开发区块链游戏 译者注: 原文: 初始发行 Enjin整合 初始整合 客户端SDK GUI 客户端SDK API Enjin的API是GraphQL Enjin推荐 使用服务器 排列 ...

  4. Golang 汇编入门知识总结

    作者:ivansli,腾讯 IEG 运营开发工程师 在深入学习 Golang 的 runtime 和标准库实现的时候发现,如果对 Golang 汇编没有一定了解的话,很难深入了解其底层实现机制.在这里 ...

  5. 使用了未赋值的局部变量_macOS上的汇编入门(七)——字面量与局部变量

    在上一篇文章中,我们分析了第一个汇编程序. # exit.s .section __TEXT,__text.globl _main _main:movq $0, %raxretq 这个汇编程序是我们所 ...

  6. 性能追击:万字长文30+图揭秘8大主流服务器程序线程模型 | Node.js,Apache,Nginx,Netty,Redis,Tomcat,MySQL,Zuul

    本文为<高性能网络编程游记>的第六篇"性能追击:万字长文30+图揭秘8大主流服务器程序线程模型". 最近拍的照片比较少,不知道配什么图好,于是自己画了一个,凑合着用,让 ...

  7. php虚拟电话号码,虚拟运营商号码慎入! 七大你要想到的事【2】

    原标题:虚拟运营商号码慎入! 七大你要想到的事 面临倒闭可能,号码如何处理 在虚拟运营商中我们发现了不少的民营企业身影,这也是工信部在最早开展移动转售业务的初衷之一,鼓励民营企业进入到移动通信行业中来 ...

  8. Windows X64汇编入门

    Windows X64汇编入门(1) tankaiha 最近断断续续接触了些64位汇编的知识,这里小结一下,一是阶段学习的回顾,二是希望对64位汇编新手有所帮助.我也是刚接触这方面知识,文中肯定有错误 ...

  9. 慎入,高并发水这么深,你能顶的住吗?

    慎入,作者高并发搞得少(没搞过),这里面水太深,什么高并发,大流量的东西都是虚拟的,作者还太年轻,没有那个经历,把握不住.系统只有几QPS,开心快乐就行,不PK,文明PK. 我关注的大佬更新了,在干货 ...

最新文章

  1. 调查报告:工人们并不担心将来会被AI取代
  2. 1024. 科学计数法 (20)
  3. wget命令出现Unable to establish SSL connection.错误
  4. 制造企业数字化转型的这些大坑不避开,上岸有点悬!
  5. 【数据结构与算法】之深入解析“课程表II”的求解思路与算法示例
  6. C++异常(exception)第一篇--综合讲解
  7. .NET 异步详解(更新)
  8. 前端学习(1905)vue之电商管理系统电商系统之根据用户id查询对应的信息
  9. 【推荐】2017年你应该了解的11款新型编程工具
  10. godaddy mysql 连接 设置 2014_GoDaddy主机数据库远程访问设置的方法
  11. 用原生js做单页应用
  12. java条件运算符类型转换_Java数据类型、运算符及类型转换
  13. 研发项目管理软件对比调研报告
  14. Computer programming and database - 方方面面入门
  15. 组件(component)技术介绍
  16. 浏览器视频文件分段缓存合并成完整的视频
  17. 《编译 - 编译杂记》GCC优化等级说明
  18. Linux之NTFS、FAT32、exFAT各种格式硬盘挂载整理
  19. 机器学习01_吴恩达版学习笔记
  20. 加州社区学院计算机专业,美国加州圣马特奥社区学院的基本介绍

热门文章

  1. acm.njupt--2026
  2. MS SQL 分类汇总参数 grouping(**)=1 rollup cubt
  3. 如何用 Python 在笔记本上分析 100GB 数据?
  4. 以太坊开发者证实 Eth2.0 不会在7月上线
  5. Minio 在Linux环境部署报错error occured ErrorResponse(code=InternalError, message=We encountered an internal
  6. AspectJ 学习笔记:Aspect的生命周期
  7. 通讯安全相关博文链接
  8. 研究生院校推荐——复旦大学工研院
  9. 【读书笔记】被讨厌的勇气之自卑感
  10. 使用基于Boost的预处理器元编程实现变长类型列表的参数化