8086汇编精讲1

学习学习

Posted by lll-yz on October 22, 2021

快速了解汇编。 之前的节奏有点慢,换个快点的。

寄存器简介

寄存器:简单的讲是CPU中可以存储数据的器件,一个CPU中有多个寄存器。

AX 是其中一个寄存器的代号,BX 是另一个寄存器的代号。

5BlKoD.png

汇编语言的组成

汇编语言由以下3类组成:

  • 汇编指令 (机器码的助记符)

  • 伪指令 (由编译器执行)

  • 其它符号 (由编译器识别)

汇编语言的核心是汇编指令,它决定了汇编语言的特性

CPU、寄存器与内存

我们知道,CPU由控制器,运算器和寄存器组成。而内存是作为程序运行时存储数据的载体,三者的关系就好比皇帝、太监和大臣的关系。

  • CPU:负责运算,指令的执行。
  • 内存:狗头军师,帮助CPU存储数据,CPU通过寻址来访问内存,进行数据读取和写入。

  • 寄存器:CPU与内存间频繁交互很影响效率,所以CPU有了自己的走狗——寄存器,寄存器充当的角色就是缓存。

内存中存放的内容

指令?数据?

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

在内存或磁盘上,指令和数据没有任何区别,都是二进制信息。

二进制信息:

  • 1000100111011000

    —>89D8H (数据)

  • 1000100111011000

    —>MOV AX,BX (程序)

存储单元

存储器被划分为若干个存储单元,每个存储单元从0开始顺序编号。

例如:一个存储器有128个存储单元,编号从0~127。

5D09sO.png

对于大容量的存储器一般还用以下单位来计量容量 :

  • 1KB = 1024Byte
  • 1MB = 1024KB
  • 1GB = 1024MB
  • 1TB = 1024GB

磁盘的容量单位同内存的一样,实际上以上单位是微机中常用的计量单位。

CPU对存储器的读写

存储单元从零开始顺序编号,这些编号可以看作存储单元在存储器中的地址。就像一条街,每个房子都有门牌号码。

CPU要从内存中读数据,首先要指定内存单元的地址。也就是说它要先确定它要读取哪一个存储单元中的数据。

另外,在一台微机中,不只有存储器这一种器件。CPU在读写数据时还要指明,它要对哪一个器件进行操作,进行哪种操作,是从中读出数据,还是向里面写入数据。可见,CPU要想进行数据的读写,必须和外部器件 (标准的说法是芯片) 进行下面3类信息的交互。

  • 存储单元的地址 (地址信息)
  • 器件的选择,读或写的命令 (控制信息)
  • 读或写的数据 (数据信息)

问:那么CPU是通过什么将地址、数据和控制信息传到存储芯片中的呢?

答:电子计算机能处理、传输的信息都是电信号,电信号当然要用导线传送。

在计算机中专门有连接CPU和其他芯片的导线,通常称为总线。

  • 物理上:一根根导线的集合;
  • 逻辑上分为:
    • 地址总线
    • 数据总线
    • 控制总线

总线在逻辑上划分图:

5DrtSI.png

我们来分别看一下它们向内存中写入数据89D8H时,是如何通过数据总线传送数据的:

  • 8088CPU:

5DRsbt.png

  • 8086CPU:

5DRLPU.png

小结

1、汇编指令是机器指令的助记符,同机器指令 一 一对应。

2、每一种CPU都有自己的汇编指令集。

3、CPU可以直接使用的信息在存储器中存放。

4、在存储器中指令和数据没有任何区别,都是二进制信息。

5、存储单元从零开始顺序编号。

6、一个存储单元可以存储8个bit(用作单位写成”b”),即8位二进制数。 (1Byte)

7、1Byte = 8 bit 1KB = 1024Byte 1MB = 1024KB 1GB = 1024MB 1TB = 1024GB

8、每个CPU芯片都有许多管脚,这些管脚和总线相连。也可以说,这些管脚引出总线。一个CPU可以引出三种总线的宽度标志了这个CPU的不同方面的性能:

  • 地址总线的宽度决定了CPU的寻址能力。
  • 数据总线的宽度决定了CPU与其它器件进行数据传送时的一次数据传送量。
  • 控制总线宽度决定了CPU对系统中其它器件的控制能力。

数据存储是以“字节”(Byte)为单位,数据传输是以“位”(bit)为单位,一个位就代表一个0或1(即二进制),每8个位(bit)组成一个字节(Byte)。8bit=1Byte 内存也是数据存储器的一种,所以内存里的空间也是以字节为单位的。

扩展知识

各类存储器:

  • 随机存储器RAM
  • 装有BIOS的ROM
  • 接口卡上RAM

内存地址空间:

  • 0~7FFFH的32KB为主随机存储器地址空间。
  • 8000H~9FFFH的8KB为显存地址空间。
  • A000~FFFFH的24KB空间为各个ROM地址空间。

解刨CPU

CPU由运算器、控制器、寄存器构成。

在CPU中:

​ 运算器进行信息处理;寄存器进行信息存储;控制器控制各种器件进行工作。

内部总线连接各种器件,在它们之间进行数据的传送。

对汇编来讲,CPU中的主要部件是寄存器。寄存器是CPU中程序员可以用指令读写的部件。程序员通过改变各种寄存器中的内容来实现对CPU的控制。

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

F4组合:通用寄存器

AX, BX, CX, DX

F4常用于存放一般性数据,被称作通用寄存器,8086的寄存器都是16位的,没有例外。

由于历史原因,F4又可以拆分成高八位寄存器和低8位寄存器。

以 AX 为例,寄存器的逻辑结构:

5rqMpn.png

eg.一个16位寄存器可以存储一个16位的数据。(数据存放情况)

数据:18

二进制表示:10010

在寄存器 AX 中的存储:

5rqHAg.png

一个16位寄存器所能存储的数据的最大值为:2^16-1。

为保证兼容性,这四个寄存器都可以分为两个独立的8位寄存器使用。

  • AX 可以分为 AH 和 AL; (H: 高位,L:低位)
  • BX 可以分为 BH 和 BL;
  • CX 可以分为 CH 和 CL;
  • DX 可以分为 DH 和 DL。

以 AX 为例,8086CPU的16位寄存器分为两个8位寄存器的情况:

5rLRVU.png

AX 的低8位 (0位~7位) 构成了 AL 寄存器,高8位 (8位~15位) 构成了 AH 寄存器。

AH 和 AL 寄存器是可以独立使用的8位寄存器。

8086CPU的8位寄存器数据存储情况:

5rLvPH.png

一个8位寄存器所能存储的数据的最大值为:2^8-1。

几条汇编指令

5yVwS1.png

CPU执行下表中的程序段的每条指令后,对寄存器中的数据进行的改变:

5yZ39A.png

ax = 044CH (1044CH,这里1无法存放了,细节之后讲)

5yecRA.png

ax = 0158H

指令操作对象的一致性:

561QFP.png

物理地址

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

我们将这个唯一的地址称为物理地址。

CPU通过地址总线送入存储器的,必须是一个内存单元的物理地址。在CPU向地址总线上发出物理地址之气,必须要在内部先形成这个物理地址。不同的CPU可以有不同的形成物理地址的方式。

16位结构的CPU

概括的讲,16位结构描述了一个CPU具有以下几个方面特征:

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

8086CPU给出物理地址的方法

  • 8086有20位地址总线,可传送20位地址,寻址能力为1M。
  • 8086内部为16位结构,它只能传送16位的地址,表现出的寻址能力却只有64K。

8086CPU采用一种在内部用两个16位地址合成的方法来形成一个20位的物理地址:

5yUVmD.png

地址加法器合成物理地址的方法:

​ 物理地址 = 段地址 + 偏移地址

5yUX9I.png

“段地址 * 16” 有一个更为常用的说法就是数据左移4位。(二进制位)

5ydpxx.png

我们通过观察位移次数和各种形式数据的关系:

1、一个数据的二进制形式左移1位,相当于该数据乘以2;

2、一个数据的二进制形式左移N位,相当于该数据乘以2的N次方;

3、地址加法器如何完成段地址 * 16的运算?

​ 以二进制形式存放段地址左移4位。

经过进一步思考,我们可以得出:

1、一个数据的16进制形式左移1位,相当于乘以16;

2、一个数据的10进制形式左移1位,相当于乘以10;

3、一个数据的X进制形式左移1位,相当于乘以X。

“段地址*16 + 偏移地址 = 物理地址”的本质含义

“基础地址 + 偏移地址 = 物理地址” 比喻 ===>

5y0G2n.png

“段地址*16 + 偏移地址 = 物理地址” 比喻 ===>

觉得不怎么样,所以就不说了,阿巴阿巴阿巴……

段的概念

错误认识:

​ 内存被划分成了一个一个的段,每一个段有一个段地址。

其实:

​ 内存并没有分段,段的划分来自于CPU,由于8086CPU用 “(段地址*16) + 偏移地址 = 物理地址” 的方式给出内存单元的物理地址,使得我们可以用分段的方式来管理内存。

以后,在编程时可以根据需要,将若干地址连续的内存单元看作一个段,用段地址*16定位段的起始地址 (基础地址),用偏移地址定段中的内存单元。

两点需要注意:

1、段地址*16必然是16的倍数,所以一个段的起始地址也一定是16的倍数。

2、偏移地址为16位,16位地址的寻址能力为64K,所以一个段的长度最大为64K。

CS:IP

段地址由段寄存器指定。

8086有四个段寄存器:CS、DS、SS、ES。

CS为代码段寄存器,IP为指令指针寄存器,它们指示了CPU当前要读取指令的地址。8086CPU将CS:IP指向的内容当做当前指令执行。

5683rQ.png

说明书:

1、8086CPU当前状态:CS中的内容为2000H,IP内容为0000H。

2、内存20000H~20009H单元中存放着可执行的机器码。

3、内存20000H~20009H单元中存放的机器码对应的汇编指令如下。

工作过程:

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

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

3、指向指令,转到1,重复过程。

jmp

一个忧桑的故事:

​ 很多时候,我们想改变CS和IP的值,但是又无奈CS、IP是VIP 白金会员,根本不屌mov,于是jmp就像他的名字一样,第一 个跳出来,专治各种不服 (什么,你问我凭什么不能用mov, 我就抢你,还凭什么)。

jmp 2AE3:3; 执行后:CS=2AE3H, IP=0003H,CPU从2AE33H处读指令。

jmp 3:0B16; 执行后:CS=0003H, IP=0B16H,CPU从00B46H处读指令。

jmp ax; 执行前 ax=1000H, CS=2000H, IP=0003H

​ 执行后 ax=1000H, CS=2000H, IP=1000H

检测

1、给定段地址为0001H,仅通过变化偏移地址寻址,CPU的寻址范围为: (00010H~1000FH)。

解:偏移地址为16位,变化范围为0~FFFFH

​ 物理地址=段地址(SA)*16 + 偏移地址(EA)

​ =0001H*10H + 0

​ =00010H

​ 物理地址=段地址(SA)*16 + 偏移地址(EA)

​ =0001H*10H + FFFFH

​ =1000FH

2、有一数据存放在内存20000H单元中,现给定段地址为 SA,若想用偏移地址寻到此单元。则SA应满足的条件是:最小为(1001H),最大为(2000H)。

解:偏移地址为16位,变化范围为 0~FFFFH。

​ 物理地址=段地址(SA)*16 + 偏移地址(EA)

​ 20000H = SA * 16 + EA

​ SA=(20000H-EA)/16

​ EA 取最大值时,SA得最小值,SA=2000H-FFFFH/16=1001H

​ EA 取最小值时,SA得最大值,SA=2000H-0H/16=2000H

3、如图:

5cm3ex.png

mov ax, 6622H (ax=6622H)

jmp 1000:3 (CS=1000H, IP=3H)

mov ax, 0000 (ax=0000H, CS=1000H)

mov bx, ax (bx=0000H, CS=1000H, IP=0000H)

jmp bx (bx=0000H, CS=1000H, IP=0000H)

mov ax, 0123H (ax=0123H, CS=1000H, IP=0000H)

mov ax, 0000H

…..循环去了

4、下面的3条指令执行后,CPU几次修改IP?都在什么时候?最后IP的值是多少? mov ax,bx sub ax,ax jmp ax

CPU四次修改IP。

第1次:从存储器中读取mov ax, bx指令之后,IP立即改变,指向下一条指令(sub ax, ax)的地址。 第2次:从存储器读取sub ax, ax之后(在此之前,会先执行mov ax, bx指令),IP立即改变,指向下一条指令(jmp ax)的地址。 第3次:从存储器读取jmp ax指令之后(在此之前,会先执行完sub ax, ax指令),IP立即改变,指向下一条指令的地址,这里由于下一条指令没有标明,所以不知道,反正此时IP的值是紧挨jmp ax之后的地址。 第4次:执行jmp ax指令后。jmp指令是通过修改IP的值来达到使程序执行跳转的目的的,因此执行jmp之后,IP的值变为ax的值。

字与字节

CPU中用16位来存储一个字,高八位放高位字节,低八位放低位字节。

由于内存单元是字节单元,所以一个字要用两个地址连续的内存单元存放,低位字节放在低地址单元中,高位字节放在高地址单元中。

我们提出子单元的概念:字单元。以后还会接触双字,甚至四字单元。

DS:[address]

8086CPU还有一个DS,这个DS用于存放要访问数据的段地址。

mov bx, 1000H

mov ds, bx

mov al, [0]

说明:将10000H(1000H:0H)数据读取到al中。

你认识mov,认识al,更认识0,不过可能不认识[],这个就是指偏移地址是0处的内容,但是或许你会问,段地址有很多,如何搞清楚这是哪一个,那么请记住,一般形式是 寄存器:[XXX],如果缺省,则默认为DS段,所以上面的指令相当于把DS:[0]处的内容放入al中。

注意: DS 不能直接用 立即数赋值,要用通用寄存器赋值。

mov bx, 1000H

mov ds, bx √

mov ds, 1000H ×

数据总线宽度的真谛

因为8086CPU是16位的结构,有16根数据线,所以可以一次性传送16位的数据,也就是说可以一次性传送一个字。只要在mov指令中给出16位的寄存器就可以进行16位数据的传送。

如:

mov bx, 1000H

mov ds, bx

mov ax, [0] //1000H:0H处的字形数据传送入ax

mov [0], cx //cx中的16位数据送到1000H:0H处

mov add sub

5coM8A.png

mov 寄存器,段寄存器 //可以

mov 段寄存器,内存单元 //不可以

sub 和 add 基本一致。

栈本身是一种数据结构,我们这里用到的栈其实是一段内存空间,只是比较特殊,有特殊的访问形式,达到不可思议的效果。

先进后出

push 和 pop

如今的CPU都有栈的设计,编程时,我们可以将一段内存当作栈来使用。

8086CPU提供入栈和出栈指令,最基本的两个是push(入栈)和pop(出栈)。如:push ax 表示将寄存器ax中的数据放入栈中,pop ax 则表示将栈顶中数据取出放入ax中。

注:8086CPU入栈和出栈都是以为单位进行的。

看图理解:

5c7mpd.png

5c7KXt.png

由上引发的两个问题

  • CPU如何知道 10000H~1000FH 这段空间被当作栈来使用。
  • push,pop 在执行的时候,必须知道哪个单元是栈顶单元,可是,如何知道呢?

实际上,就像代码段有CS 和 IP 一样,栈也有自己的段,也有自己的 SS 和 SP。

任意时刻,SS:SP 指向栈顶元素!

push pop 详解

push ax

1、sp=sp-2,ss:sp 指向当前栈顶前面的单元,以当前栈顶前面的单元为新的栈顶。

2、将ax中的内容送入到ss:sp指向的内存单元中,ss:sp此时指向新的栈顶。

pop ax

1、ss:sp中的数据送入ax

2、sp += 2

看图说话:

5cHhxs.png

5cbZQA.png

检测

1、

5gGI6f.png

AX=2662 BX=E626 AX=E626 AX=2662 BX=D6E6 AX=FD48 AX=2C14 AX=0 AX=00E6 BX=0 BX=0026 AX=000C

2、内存的情况如图所示。

各寄存器的初始值:CS=2000H, IP=0, DS=1000H, AX=0, BX=0;

(1)写出CPU指行的指令序列 (用汇编写出)。

(2)写出CPU执行每条指令后,CS, IP和相关寄存器中的数值。

(2)再次体会:数据和程序有区别吗,如何确定内存中的信息哪些是数据,哪些是程序。

5gNjVU.png

解:

指令 CS IP DS AX BX
mov ax, 6622H 2000H 0+3   6626H  
jmp 0ff0:0100 0FF0H 0100      
mov ax, 2000H 0FF0H 0100+3   2000H  
mov ds, ax 0FF0H 0103+2 2000H    
mov ax, [0008] 0FF0H 0105+3   C389  
mov ax, [0002] 0FF0H 0108+3   EA66  

没有区别。CS和IP指示了当前要读取的程序的地址,DS指向的是数据段。

3、(1) 补全下面程序,使其可以将10000H~1000FH中的8个字,逆序复制到20000H~2000FH中。逆序复制的含义如图所示(图中内存中的数据均为假设)。

527AtU.png

mov ax,1000H

mov ds,ax

mov ax,2000H ;因为立即数不能直接送入段寄存器,需要通用寄存器中转一下

mov ss,ax ;SS存放的是栈的段地址

mov sp,10H ;SP存放的是栈的段地址SS:SP指向 20010H(即2000FH的下一个地址)

push [0] ;把0123 复制到2000E 2000F, 2000E=23 ,2000F=01

push [2] ;把2266 复制到2000C 2000D 2000C=66 2000D=22

push [4]

push [6]

push [8]

push [A]

push [C]

push [E]

(2)补全下面程序,使其可以将10000H~1000FH中的8个字,逆序复制到20000H~2000FH中。

mov ax,2000H

mov ds,ax

mov ax,1000H

mov ss,ax

mov sp,0

pop [E] ;把 ss:sp 10000H 弹入到 ds:[e] 2000EH

pop [C] ;把 ss:sp 10002H 弹入到 ds:[c] 2000CH

pop [A]

pop [8]

pop [6]

pop [4]

pop [2]

pop [0]