汇编语言第四版学习

第一章 基础知识

二进制和汇编语言的关系

在汇编语言中,用助记符代替机器指令的操作码,用地址符号或标号代替指令或操作数的地址。

  • 汇编语言由三部分构成:
    在这里插入图片描述

注:为了指出数据的来源、操作结果的去向及所执行的操作,一条指令一般包含操作码和操作数两部分。

  • 内存的最小单元:一个字节 = 2个十六进制位 = 8个二进制位。

在这里插入图片描述

注:1byte = 8bit;1bit = 1个二进制位

计量单位:
1KB = 1024 byte
1 KB = 2 ^ 10 byte
1 MB = 2 ^ 20 byte
1 GB = 2 ^ 30byte

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

存储单元的地址(地址信息,也叫内存编号或内存地址)
控制信息(读或写)
数据信息(读出或写入的数据)

  • 计算机处理、传输的信息都是电信号,用导线传送;计算机中专门有连接CPU和其他芯片的导线,通常称为总线。逻辑上分为:地址总线、数据总线、控制总线

  • CPU是通过地址总线来指定存储单元的

  • 寻址能力:地址总线能传送多少个不同的信息,CPU就可以对多少个存储单元进行寻址

  • 内存编号(内存地址)是从0开始的,因为物理上的限制,电路只能表示0或者1。一根导线可以传送的稳定状态只有两种,高电平或者低电平(1或0)。

  • 一个CPU有N根地址线,则可以说这个CPU的地址总线的宽度为N,这样的CPU最多可以寻找2的N次方个内存单元;数据总线的宽度决定了CPU和外界的数据传送速度

控制总线

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

数据线决定了CPU的和其他部件进行数据传送时一次性能够传送多少数据的能力

内存地址空间

  • 主板:在每一台PC机中,都有一个主板,主板上有核心器件和一些主要器件,这些器件通过总线(地址、数据、控制总线)相连

接口卡:
在这里插入图片描述

  • 存储器芯片:从读写属性上分两类:随机存储器(RAM)和只读存储器(ROM)

  • RAM :允许读取和写入,断电后指令和数据就丢失了

  • ROM :只允许读取,断电后指令和数据还存在,一般用在启动计算机上面

从功能和连接上分:
在这里插入图片描述

  • BIOS:
    在这里插入图片描述
  • PC机中各类存储器的逻辑连接
    在这里插入图片描述

内存地址空间:

计算机中存储器在物理上是独立的器件,但是他们有两点相同:

1.都和CPU总线相连
2.CPU对它们进行读或写的时候都通过控制线发出内存读写命令
在这里插入图片描述

第二章 寄存器

  • 概述:一个典型的CPU由运算器。控制器、寄存器等器件组成,这些器件靠内部总线相连

  • 区别:内部总线实现CPU内部各个器件之间的联系。外部中线实现CPU和主板上其他器件的联系

通用寄存器

  • 8086CPU有14个寄存器,分别为:AX、BX、CX、DC、SI、DI、SP、BP、IP、CS、SS、DS、ES、PSW。

  • 8086CPU所有的寄存器都是16位的,可以存放两个字节;AX、BX、CX、DX通常用来存放一般性的数据被称为通用寄存器

在这里插入图片描述

  • AX、BX、CX、DX这四个寄存器可分为两个可独立使用的8位寄存器使用:
    在这里插入图片描述

以AX为例,8086CPU的16位寄存器分为两个8位寄存器的情况如下图:
在这里插入图片描述

字在寄存器中的存储

  • 一个字可以存在一个16位寄存器中,这个字的高位字节和低位字节自然就存在这个寄存器的高8位寄存器和低8位寄存器中。

几条汇编指令:
在这里插入图片描述
注:在写一条汇编指令或一个寄存器的名称时不区分大小写

物理地址

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

16位结构的CPU

  • 16位结构描述了一个淳朴具有以下几方面的结构特征:

在这里插入图片描述
8086CPU给出物理地址的方法:

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

  • 8086CPU采用一种在内部用两个16位地址和成的方法来形成一个20位的物理地址,其相关部件的逻辑结构图:

在这里插入图片描述

在这里插入图片描述
地址加法器合成物理地址的方法:物理地址 = 段地址 x 16 + 偏移地址。(乘以16相当于左移一位)

  • 地址加法器的工作过程
    在这里插入图片描述

“段地址 x 16”有一个更为常用的说法是左移4位,计算机中的所有信息的所有信息都是以二进制的形式存储的,段地址也是,机器只能处理二进制信息,“左移4位”中的位,指的是二进制位
在这里插入图片描述

在编程时可以根据需要将若干地址连续的内存单元看作一个段,用段地址 x 16点位段的起始地址(基础地质),用偏移地址定位段中的内存单元。偏移地址为16位,16位地址的寻址能力为64KB,所以一个段的最大长度为64KB。

小结:

1.CPU访问内存单元时,必须向内存提供内存单元的物理地址。
2.8086CPU在内部用段地址和偏移地址位相加的方法形成最终的物理地址
3.CPU可以用不同的段地址和偏移地址形成同一个物理地址
在这里插入图片描述
4.偏移地址16位,变化范围为0~FFFFH,仅用偏移地址来寻址最多可寻64K个内存单元

段寄存器

段寄存器就是提供段地址的,8086CPU有4个段寄存器:CS(代码段寄存器)、DS(数据段寄存器)、SS(堆栈段寄存器)、ES(附加段寄存器)。当8086CPU要访问内存时,由这4个段寄存器提供内存单元的段地址

CS和IP

CS(代码段寄存器)IP(指令指针寄存器) 是8086CPU中两个最关键的寄存器,他们指示了CPU当前要读取指令的地址

  • 指令的执行过程:

1.CPU从CS:IP所指向的内存单元读取指令,存放到指令缓冲器中
2.IP = IP + 所读指令的长度,从而指向下一条指令
3.执行指令缓冲器中的内容,回到第一步

修改CS、IP的指令

  • 在CPU中,我们能够用指令读写的部件只有寄存器,我们可以通过改变寄存器中的内容实现对CPU的控制;CPU从何处执行指令是由CS、IP中的内容决定的,我们可以通过改变CS、IP中的内容来控制CPU执行目标指令

  • 8086CPU大部分寄存器的值,都可以用mov指令来改变,mov指令被称为传送指令。但是mov不能用于设置CS、IP的值,能够改变CS、IP的内容的指令被称为转移指令

最简单的修改CS、IP的指令:jmp指令

  • 要同时修改CS、IP的内容,可用形如“jmp 段地址 : 偏移地址”的指令完成。功能:用指令中给出的段地址修改CS,偏移地址,修改IP。

  • 如:
    在这里插入图片描述
    注:若仅想要修改IP的内容。可用形如“jmp 某一合法寄存器”的指令完成,如:

在这里插入图片描述

  • 例:

在这里插入图片描述
在这里插入图片描述

代码段

  • 可以将长度为N(N <= 64KB)的一组代码,存在一组地址连续、起始地址为16的倍数的内存单元中,这段内存是用来存放代码的,从而定义了一个代码段

  • CPU只认被CS:IP指向的内存单元中的内容分为指令,所以要将CS:IP指向所定义的代码段中的第一条指令的首地址

  • 小结:

在这里插入图片描述

debug调试工具使用

在这里插入图片描述

命令:

  • r 可以查看和改变寄存器的内容
    在这里插入图片描述

  • d 可以查看内存中的内容(可以用“d 段地址:偏移地址”的格式来查看)

在这里插入图片描述

在这里插入图片描述

  • u 将内存中的机器指令翻译成汇编指令

在这里插入图片描述

也可以从指定地址开始将机器码翻译成汇编指令

在这里插入图片描述

  • a 以汇编指令的格式在内存中写入一条机器指令

在这里插入图片描述

可以在固定的内存中写入汇编指令

在这里插入图片描述
此时写入是没用的,因为CS = 073F, IP = 0100,要将CS:IP指向2000:0

在这里插入图片描述
现在CS:IP就指向了2000:0这个位置

  • t 执行一条机器指令(当前CS:IP所指向的机器指令)

在这里插入图片描述

  • e 改写内存中的内容

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

第三章 寄存器(内存访问)

内存中字的存储

  • 在CPU中,用16位寄存器来存储一个字,高8位存放高位字节,低8位存放低位字节。在内存中存储时,由于内存单元时字节单元(一个单元存放一个字),则一个字型数据要用两个地址连续的内存单元来存放。

  • 比如用0、1两个内存单元存放数据4E20H:

在这里插入图片描述

  • 字单元,即存放一个字型数据(16位)的内存单元,由两个地址连续的内存单元组成,高地址内存单元存放字型数据的高位直接,低地址内存单元中存放字型数据的低位字节

结论:

在这里插入图片描述

DS和[address]

  • 8086CPU中有一个DS寄存器,通常用来存放要访问的数据的段地址。

比如我们读取10000H单元的内容,用如下程序段进行:

在这里插入图片描述

  • 这里是使用mov指令将一个内存单元中的内容送入一个寄存器中,需要指明从哪一个内存单元送到哪一个寄存器,寄存器用寄存器名来指明,内存单元则需要用内存单元的地址类指明。此时mov指令的格式应该是:mov 寄存器名,内存单元地址。

  • “[···]” 表示一个内存单元,“[···]” 中的0表示内存单元的偏移地址。只有偏移地址是不能定位一个内存单元的,那么指令执行时,8086CPU自动读取DS中的数据位内存单元的段地址。

注:8086CPU不支持将数据直接送入段寄存器的操作,ds是一个段寄存器

  • 将数据从寄存器送入内存单元:
    在这里插入图片描述

例题:在这里插入图片描述
用debug调试:在这里插入图片描述
用 t 命令执行写入的指令
在这里插入图片描述
在这里插入图片描述
执行结束后,ax = 1123,bx = 8833,cx = 8833

分析:在这里插入图片描述

mov、add、sub指令

  • mov指令有以下几种形式:
    在这里插入图片描述

  • 有mov 段寄存器,寄存器;也应该有mov 寄存器,段寄存器;从段寄存器向寄存器传送数据
    在这里插入图片描述

  • add和sub也有以下几种形式:
    在这里插入图片描述

数据段

  • 对于8086PC机,我们可以根据需要将一组内存单元定义为一个段(可以是代码段、数据段等)。我们可以将一组长度为N(N <= 64K)、地址连续、起始地址为16的倍数的内存单元当做专门存储数据的内存空间,从而定义了一个数据段

  • 将一段内存当作数据段,是我们在编程时的一种安排,可以在具体操作的时候,用ds存放数据段的段地址,再根据需要用相关指令访问数据段中的具体单元,这样就可以访问数据段中的数据
    在这里插入图片描述

小结

在内存和寄存器之间传送字型数据时,高地址单元和高8位寄存器、低地址单元和低8位寄存器相对应
寄存器是互相独立的

  • 栈是一种具有特殊的的访问方式的存储空间,它的特殊在于最后进入这个空间的数据,最先出去;因为栈顶的元素总是最后进入,需要出栈时,又最先被从栈中取出

  • 栈有两个基本的操作:入栈和出栈

入栈:将一个新的元素放到栈顶
出栈:从栈顶取出一个元素

  • 栈的操作规则:LIFO(先进后出)

CPU提供的栈机制

8086CPU提供入栈和出栈指令:PUSH(入栈)、POP(出栈)

push ax:将寄存器ax中的数据送入栈中;
pop ax:从栈顶取出数据送入ax

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

观察这一段指令的执行过程
在这里插入图片描述
将10000H~1000FH这段内存当做栈来使用
在这里插入图片描述

在8086CPU中,有两个寄存器:
段寄存器SS:存放栈顶的段地址;寄存器SP:存放栈顶的偏移地址。任意时刻,SS:SP指向栈顶元素

在这里插入图片描述

  • 下图描述了8086CPU对push指令的执行过程:
    在这里插入图片描述
    注:入栈时,栈顶从高地址向低地址方向增长

空栈的情况:

将10000H~1000FH这段空间当作栈,初始状态是空的,此时SS = 1000H,那么SP = 0010H
在这里插入图片描述
在这里插入图片描述

描述pop指令的功能,pop ax:

在这里插入图片描述
下图描述了pop指令的执行过程
在这里插入图片描述
注:在上图中,出栈后,SS:SP指向新的栈顶1000EH,pop操作前的栈顶元素1000CH处的2266H依然存在,但是它已经不在栈中,当再次执行push等入栈指令后,SS:SP移至1000CH,并在里面写入新的数据,它将被覆盖

栈顶超界的问题

  • 下图描述了在执行push指令后,栈顶超出栈空间的情况;将10010H~1001FH当做栈空间,容量为16字节(8字),初始为空,ss = 1000H、sp = 0020H,SS:SP指向10010H,在执行8次push ax之后,向栈中压入8个字,栈满,SS:SP指向10010H;当再次执行时,sp = sp - 2,SS:SP指向1000EH,栈顶超出了栈空间,ax中数据送入1000EH单元,将栈空间外的数据覆盖
    在这里插入图片描述

  • 下图描述了在执行pop指令后,栈顶超出栈空间的情况,将10010H~1001FH当做栈空间,容量为16字节(8字),当前状态为满,ss = 1000H、sp = 0010H,SS:SP指向10010H,在执行8次pop ax之后,向栈中弹出8个字,栈空,SS:SP指向10020H;当再次执行pop ax时,sp = sp + 2,SS:SP指向10022H,栈顶超出了栈空间,此后,如果在执行push指令,10020H、10021H中的数据将被覆盖
    在这里插入图片描述

在这里插入图片描述

  • 在编程时,要根据可能用到的最大栈空间,来安排栈的大小,防止栈的数据太多导致越界,执行出栈时,也要注意防止栈空时继续出栈导致越界

push、pop指令

push、pop是可以在寄存器和内存之间传送数据的,其指令格式有如下形式:

push	寄存器		;将一个寄存器的数据入栈
pop		寄存器		;出栈,用一个寄存器接收出栈的数据
push	段寄存器		;将一个段寄存器中的数据入栈
pop		段寄存器		;出栈,用一个段寄存器接收出栈的数据
也可以在内存单元和内存单元之间传送数据,栈操作都是以字为单位
push	内存单元		;将一个内存单元处的字入栈
pop		内存单元		;出栈,用一个内存单元接收出栈的数据

在这里插入图片描述
注:

1.当我们用栈来暂存以后需要恢复的寄存器中的内容时,出栈的顺序要和入栈的顺序相反
2.执行push时,先改变sp,向后SS:SP处传送。执行pop时,先读取SS:SP处的数据,后改变sp
3.push、pop等栈操作指令,修改的只是sp,也就是说,栈顶的变化范围最大为:0~FFFFH

  • push pop实质上就是一种内存的传送指令,可以在寄存器和内存之间传送数据,与mov指令不同的是,push和pop指令访问的内存单元的地址不是在指令只不中给出的,而是由SS:SP指出的

栈段

  • 我们可以将长度为N(N <= 64K)的一组地址连续、起始地址为16的倍数的内存单元当做栈来使用,从而定义了一个栈段
    在这里插入图片描述

注:讲一段内存当做栈段,仅仅是我们在编程时的一种安排,CPU并不会由于这种安排,就在执行push、pop等栈操作指令时,就自动的将我们定义的栈段当做栈空间来访问

  • 如果将10000H~1FFFFH这段空间当作栈段,初始状态是空的,此时ss = 1000H,sp = 0000H
    在这里插入图片描述

在这里插入图片描述

  • 一个栈段最大可以设为64KB,所以栈顶的变化范围是0~FFFFH,从栈空时候sp = 0,一直压栈,知道栈满时sp = 0;如果再次压栈,栈顶将环绕,覆盖原来栈中的内容

第四章 第一个程序

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

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

源程序

  • 下面是一段简单的汇编源程序
    在这里插入图片描述

1.伪指令:

在汇编源程序中,包含两种指令:汇编和伪指令;汇编指令时有对应的机器码的指令,可以被编译为机器指令,最终为CPU所执行;而伪指令没有对应的机器指令,最终不被CPU所执行,伪指令是由编译器来执行的指令,编译器根据伪指令来进行相关的编译工作
此程序中出现了3中伪指令:
(1) xxx segment 和 xxx ends
在这里插入图片描述
注:一个汇编程序是由多个段组成的,这些段被用来存放代码、数据或当作栈空间来使用。一个有意义的汇编程序中至少要有一个段,这个段用来存放代码
(2)end
在这里插入图片描述
(3)assume
在这里插入图片描述

2.源程序中的“程序”

汇编源程序:
	伪指令	(编译器处理)
	汇编指令	(编译为机器码)

程序:源程序中最终由计算机执行、处理的指令或数据

注:

1.我们可以将源程序文件中的所有内容称为源程序,将源程序中最终由计算机执行处理的指令或数据,称为程序
2.程序最先以汇编指令的形式存在源程序中,经编译、连接后转变为机器码,存储在可执行文件中
在这里插入图片描述

3.标号

一个标号指代了一个地址;比如codesg:放在segment前面,作为一个段的名称,这个段的名称最终将被编译、连接程序处理为一个段的段地址

4.程序返回

DOS中的程序运行:

dos是一个单任务操作系统。
在这里插入图片描述
一个程序结束后,将CPU的控制权交还给使它得以运行的程序,我们称这个过程为:程序返回
·
如何返回?应该在程序的末尾添加返回的程序段;在上面的程序中,
mov ax,4c00H
int 21H
这两条指令所实现的功能就是程序返回;在程序的末尾使用这两条指令就可以实现程序返回
在这里插入图片描述

start伪指令:将我们设置的程序的入口地址记录在exe文件的描述信息中,然后系统通过这个描述文件中的内容去设置cs:ip,在程序中可以start可以写成其他单词
在这里插入图片描述
在这里插入图片描述
———————————————————————————————
在这里插入图片描述
在这里插入图片描述

连接的作用:
在这里插入图片描述

操作系统的外壳
在这里插入图片描述

程序执行过程的跟踪

对程序的执行过程进行跟踪分析可以发现源程序中隐藏较深的错误。

  • 在DOS中运行一个程序时,是由command将程序从可执行文件中加载入内容,并使其得以运行。为了观察程序的运行过程,可使用debug;debug可以将程序加载入内容,设置CS:IP指向程序的入口,但debug并不放弃对CPU的控制,这样我们就可以使用debug相关命令来单步执行程序,查看每一条指令的执行结果

debug将程序从可执行文件加载入内存后,cx中存放的是程序的长度:(15个字节)
在这里插入图片描述

DOS系统中.EXE文件中的程序加载过程:
在这里插入图片描述
注:
在这里插入图片描述
在这里插入图片描述

用p命令执行int 21指令
在这里插入图片描述
使用q命令退出debug,将返回到command中,因为debug是由command加载运行的

- 注:程序结束后返回到command中,这里是debug将程序加载入内存,所以程序结束后要返回到debug中

第五章 [BX]和loop指令

  • [bx] 和 [0] 类似, [0] 表示内存单元,其偏移地址是0。要完整的描述一个内存单元,需要两种信息,①内存单元的地址;②内存单元的长度(类型)。单元的长度可以由具体指令中的其他操作对象(比如寄存器)指出。

[bx] 同样也表示一个内存单元,它的偏移地址在bx中,比如:

mov		ax,[bx]
mov		al,[bx]

为了描述上的简洁,可用一个描述性的符号“()”来表示一二班寄存器或一个内存单元中的内容,“()”中的元素可以有3中类型:寄存器名、段寄存器名、内存单元的物理地址(一个20位的数据)
在这里插入图片描述

  • 约定符号idata表示常量
    在“[…]”里用一个常量0表示内存单元的偏移地址,可以用idata表示常量比如:
    mov ax,[idata] 就代表mov ax,[1]、mov ax,[2]、mov ax,[3] 等
    mov bx,idata 就代表mov bx,1、mov bx,2、mov bx,3 等
    mov ds,idata 就代表 mov ds,1、mov ds,2等,他们都是非法指令

[bx]

mov ax,[bx]
在这里插入图片描述
mov [bx],ax
在这里插入图片描述

loop指令

loop指令的格式是:loop 标号;

  • CPU执行loop指令时两步操作:
    ①(cx) = (cx) - 1;
    ②判断cx中的值,不为0则转至标号处执行程序,如果为0则向下执行

  • 通常我们用loop指令来实现循环功能,cx中存放循环次数

例:计算2^12

 assume cs:code
 code segment
 		mov ax,2
 		mov cx,11
 s:		add ax,ax
 		loop s
 		mov ax,4c00h
 		int 21h
 code ends
 end

此程序中s是一个标号,标识了一个地址,这个地址处有一条指令:add ax,ax

cs和loop指令相配合实现循环功能的3个要点:

1.在cx中存放循环的次数
2.loop指令中的标号所标识地址要在前面
3.要循环执行的程序段,要写在标号和loop指令的中间
在这里插入图片描述

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

在这里插入图片描述
注意第一条指令,我们在书写的时候数据是以字母开头的,而在汇编源程序中,数据不能以字母开头,所以要在前面加0

  • 如果一个程序中循环次数过多,我们可以用户 g命令来达到一次执行完标号s前的指令
    在这里插入图片描述

  • 用p命令来达到将循环一次执行完的目的;将再次遇到loop指令时,使用p命令,debug会自动重复执行循环中的命令,直到(cx) = 0为止
    在这里插入图片描述

  • 也可以用g命令直接执行到循环完的那一条指令
    在这里插入图片描述

debug和汇编编译器masm对指令的不同处理

  • 在debug中,
    mov ax,2000
    mov ds,ax
    mov ax,[0]
    是将段地址为2000,偏移地址为0里面的内容送入寄存器ax中;

  • 但是在源程序中
    mov ax,2000h
    mov ds,ax
    mov ax,[0]
    是将0这个数送入ax。

  • 在源程序中,要将2000:0单元中的数据送入ax中,可将偏移地址送入bx寄存器用[bx]的方式来范文内存单元:
    mov ax,2000h
    mov ds,ax
    mov bx,0
    mov ax,[bx]

  • 在源程序中,要想像debug那样在[ ]中直接给出内存单元的偏移地址,可以在“[ ]”的前面显示的给出段地址所在的寄存器比如这样访问2000:0单元:
    mov ax,2000h
    mov ds,ax
    mov al,ds:[0]
    在这里插入图片描述

loo和[bx]的联合应用

计算ffff:0 ~ ffff:b单元中的数据和,结果存储在dx中

注意:
1.运算后的结果不能超出dx所能存储的范围:(ffff:0 ~ ffff:b中的数据是字节型数据,范围在0 ~ 255之间,12个这样的数据相加,结果不大于65535,可以在dx中存放)
2.不能将ffff:0 ~ ffff:b中数据直接累加到dx中(因为ffff:0 ~ ffff:b中数据都是8位,而dx是16位的寄存器)
3.不能将ffff:0 ~ ffff:b中的数据累加到dl中,并设置dh = 0,从而实现累加到dx中(因为dl是8位,能容纳的范围在0 ~ 255,ffff:0 ~ ffff:b中的数据也是8位,如果仅向dl中累加12个8位数据,很有可能造成进位丢失)

  • 解决方法:用一个16位寄存器做中介,将内存单元中的8位数据赋值到一个16位寄存器ax中数据将ax中的数据加到dx上,从而使两个运算对象的类型匹配并且结果不会越界。

在这里插入图片描述

段前缀

在指令mov ax,[bx]中,段地址默认是ds中,我们可以在访问内存单元的指令中显示地给出内存单元的段地址所在的段寄存器,比如:
在这里插入图片描述
这些出现在内存单元的指令中,用于显示的指明内存单元的段地址的“ds”、“cs”、“ss”、“es”,在汇编语言中称为段前缀

一段安全的空间

  • 在8086模式中,随意向一段内容写入内容是很危险的,因为这段空间中可能存放着重要的系统数据或代码;所以我们需要向内存空间写入数据的话,要使用操作系统给我们分配的内存空间

  • 系统执行exe文件,需要给它分配一段内存,这段内存是安全的,在exe文件中,除了整个程序,还包括了一些描述信息,比如,文件的大小,程序的入口,系统根据这些描述信息对寄存器进行相关设置
    在这里插入图片描述

  • 一般,dos和其他合法程序不会使用0:200 ~ 0:2ff的256个字节的空间,使用这段空间是安全的
    在这里插入图片描述

实验四

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

——————————————————
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

第六章 包含多个段的程序

编号计算以下8个数据的和,结果存放在ax中:0123h、0456h、0789h、0abch、0defh、0fedh、0cbah、0987h
在这里插入图片描述
“dw”的含义是定义字形数据,在这里dw定义了8个字形数据,所占内存空间为16个字节
·
这8个数据是存放在代码段中,所以cs存放代码段的段地址;由于dw定义的数据处于代码段的最开始,所以偏移地址为0,这8个数据在代码段的偏移0、2、4、6、8、A、C、E处;所以bx存放加2递增的偏移地址,用循环来进行累加
·
end的作用除了告诉编译器程序结束外,还可以告诉编译器程序的入口在什么地方,比如上面的程序用end指明了程序的入口在标号start处,也就是说“mov bx,0”是程序的第一条指令

在代码段中使用栈

  • 利用栈将这8个数据逆序存放:0123h、0456h、0789h、0abch、0defh、0fedh、0cbah、0987h;思路:先入栈,在出栈;首先要有一段可当做栈的内存空间,这段空间应该由系统来分配,所以我们可以在程序中通过定义数据来取得一段空间,把这段空间当作栈来使用
    在这里插入图片描述

  • 将cs:10 ~ cs:2f的内存空间当做栈来使用,初始状态下为空,所以ss:sp指向栈底:cs:30;所以dw的作用可以说用它定义数据,也可以说用它开辟内存空间比如:
    在这里插入图片描述

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

  • 注:如果数据、栈、代码需要的空间超过64KB,就不能放在一个段中(一个段的容量不能大于64KB,这是8086模式的限制,并不是所有的处理器都这样),所以我们应该考虑用多个段来存放数据、代码和段

用定义代码段一样的方法定义多个段,在这些段里面定义需要的数据,或通过定义数据来取得栈空间
在这里插入图片描述
在这里插入图片描述
在程序中,段名就相当于一个标号,它代表了段地址,所以指令“mov ax,data”的含义就是指将名称为“data”的段的段地址送入ax。一个段中的数据的段地址可由段名代表,偏移地址就要看数据在段中的位置了。注:不能用mov ds,data | mov bx,ds:[6]将内存单元中的值送入ax,因为“mov da,data”是错误的,8086CPU不允许将一个数值直接送入段寄存器
·
在源程序中,存放数据的段命名为data,存放代码code,栈stack
·
注:在源程序最后用“end start”说明了程序的入口,可执行文件中的程序被加载入内存后,CPU的CS:IP被设置指向这个入口,从而开始执行程序中的第一条指令,标号“start”在code段中,这样CPU就将code段中的内容当做指令来执行了
·
在code段中,使用指令:
mov ax,stack
mov ss,ax
mov sp,16
设置ss指向stack,设置ss:sp指向stack:16,CPU执行这些指令后,将把stack段当做栈空间来用
·
CPU处理我们定义段中的内容,完全是靠程序中具体的汇编指令和汇编指令对CS:IP、SS:SP、DS等寄存器的设置来决定的

注:如果段中的数据占N个字节,则程序加载后,该段实际占有的空间为16 * (N / 16 + 1)

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

and和or指令

  • and指令:逻辑与指令,按位进行与运算
    在这里插入图片描述
    通过该指令可将操作对象的相应位设为0,其他位不变。
    在这里插入图片描述

  • or指令:逻辑或指令,按位进行或运算
    在这里插入图片描述
    通过该指令可将操作对象的相应位设为1,其他位不变
    在这里插入图片描述

以字符形式给出的数据

  • 在汇编中,用’… …'的方式指明数据是以字符的形式给出的,编译器将把它们转化为相对应的ASCII码,程序如下:
    在这里插入图片描述
    在这里插入图片描述

  • 在上面的程序中:
    在这里插入图片描述
    data段中的内容:在这里插入图片描述

大小写转换的问题

改变一个字母的大小写,实际上就是要改变它所对应的ASCII码

将datasg中的第一个字符串转化为大写,第二个字符串转化为小写:

assume cs:codesg,ds:datasg
datasg segment
	db 'BaSiC'
	db 'iNfOrMaTiOn'
datasg ends
codesg segment
	start:

codesg ends
end start	

对比大小写的ASCII码
在这里插入图片描述
通过对比,小写比大小大20H;“a”的ASCII减去20H,就得到“A”;“A”的ASCII码加上20H,就得到“a”;
在这里插入图片描述

完整的程序:
在这里插入图片描述
在这里插入图片描述

[bx + idata]

[bx + idata]表示一个内存单元,它的偏移地址为(bx)+ idata

指令 mov ax,[bx+200] 的含义:将一个内存单元的内容送入ax,这个内存单元的长度为2个字节,存放一个字,偏移地址为bx中的数值加上200,段地址在ds中。描述为:(ax)=((ds) * 16 + (bx) + 200);该指令也可以写成如下格式:
·
mov ax,[200+bx]
mov ax,200[bx]
mov ax,[bx].200

SI 和 DI

si 和 di是8086CPU中和bx功能相近的寄存器,si 和 di 不能够分为两个8位寄存器来使用。

下面3组指令实现了相同的功能
在这里插入图片描述

例:用si和di实现将字符串'welcome to masm!'复制到它后面的数据区中
在这里插入图片描述

也可以用[bx(si或di)+idata]的方式,使程序变得简洁

在这里插入图片描述

[bx+si]和[bx+di]

在这里插入图片描述

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

在这里插入图片描述
在这里插入图片描述

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

问题:
在这里插入图片描述
思路:
在这里插入图片描述
解决:
在这里插入图片描述

  • 但是如果程序过长,寄存器被用在了程序中的话,就不能用来暂存数据,所以可以开辟一块内存来暂存我们需要的数据(用dw定义一个字来暂存cx),需要时在取出来;如果需要保存多个数据的时候,必须要记住数据放到了哪个单元中,这样可能造成混乱。一般来说,在需要暂存数据的时候,我们都应该使用栈

再次改进程序
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

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

定义描述性的符号:reg和sreg。,用reg来表示一个寄存器,用sreg来表示一个段寄存器

reg的集合包括:ax、bx、cx、dx、ah、al、bh、bl、ch、cl、dh、dl、sp、bp、si、di
sreg的集合包括:ds、ss、cs、es

bx、si、di和bp

对bx、si、di的总结:
在这里插入图片描述
在这里插入图片描述

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

绝大部分机器指令都是进行数据处理的指令,处理大致可分为三类:读取、写入、运算;在机器指令这一层来讲,并不关心数据的值是多少,而关心指令执行前一刻,它将要处理数据所在的位置。指令在执行前,所要处理的数据可以在3个地方:CPU内部、内存、端口;比如下列的指令:
在这里插入图片描述

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

汇编语言中用3个概念来表达数据的位置:

1.立即数(idata):对于直接包含在机器指令中的数据(执行前在CPU的指令缓冲器中),在汇编语言中称为“立即数”,在汇编指令中直接给出。
在这里插入图片描述

2.寄存器:指令要处理的数据在寄存器中,在汇编指令中给出相应的寄存器名
在这里插入图片描述

3.段寄存器(SA)和偏移地址(EA):指令要处理的数据在内存中,在汇编指令中可用[x]的格式给出EA,SA在某个段寄存器中
在这里插入图片描述
在这里插入图片描述

寻址方式

  • 当数据存放在内存中的时候,我们可以用多种方式来给定这个内存单元的偏移地址,这种定位内存单元的方法一般称为寻址方式

寻址方式小结:
在这里插入图片描述

指令要处理的数据有多长

8086CPU的指令,可以处理两种尺寸的数据,byte和word。所以在机器指令中要指明,指令进行的是字操作还是字节操作,汇编语言用以下方法处理这个问题

1.通过寄存器名指明要处理的数据的尺寸
在这里插入图片描述

2.在没有寄存器名存在的情况下,用操作符X ptr指明内存单元的长度,X 在汇编指令中可以为word和byte
在这里插入图片描述

注:在没有寄存器参与的内存单元访问指令中,用word ptr 或 byte ptr 显性的指明所要访问的内存单元的长度是很有必要的。否则CPU无法得知所要访问的单元是字单元,还是字节单元

假设用debug查看内存,结果如下:
2000:1000 FF FF FF FF FF FF …
在这里插入图片描述
3.其他方法:有些指令默认了访问的是字单元还是字节单元,比如:push [1000H]就不用指明访问的是字单元还是字节单元,因为push指令只进行字操作

div指令

div是除法指令,使用div做除法的时候:
在这里插入图片描述

格式如下:
在这里插入图片描述
在这里插入图片描述
这里乘以10000H,是因为除数是16位,被除数是32位,被除数用dx和ax存放,dx存放的是32位中的高16位,ax存放的是32位中的低16位

在这里插入图片描述

伪指令 dd

db和dw分别是定义字节型数据和字型数据的,dd是用来定义dword(double word,双字,32位)型数据的,比如:

data segment
	db 1
	dw 1
	dd 1
data ends

在上面的指令中,分别定义了3个数据,第一个01H,在data:0处,占1个字节;第二个00001H,在data:1处,占1个字;第三个0000000H,在data:3处,占2个字

dup

dup是一个操作符,在汇编语言中同db,dw,dd等一样,也是由编译器识别处理的符号,它是和db、dw、dd、等数据定义伪指令配合使用的,用来进行数据的重复

比如:
在这里插入图片描述
在这里插入图片描述

dup的使用格式:

db 重复的次数 dup (重复的字节型数据)
dw 重复的次数 dup (重复的字形数据)
dd 重复的次数 dup (重复的双字型数据)

在这里插入图片描述

第九章,转移指令的原理

可以修改IP,或同时修改cs和ip的指令统称为转移指令。概括的讲,转移指令就是可以控制CPU执行内存中某处代码的指令

8086CPU的转移行为有以下几类:

只修改IP时,称为段内转移,比如jmp ax
同时修改cs和ip时,称为段间转移,比如:jmp 1000:0

由于转移指令对ip的修改范围不同,段内转移又分为:短转移和近转移

短转移ip的修改范围为:-128~127
近转移ip的修改范围为:-32768~32767

8086CPU的转移指令分为以下几类:

无条件转移指令(如jmp)
条件转移指令
循环指令(如loop)
过程
中断

操作符offset

操作符offset在汇编中是由编译器处理的符号,它的功能是取得标号的偏移地址

比如下面的指令
在这里插入图片描述

jmp指令

jmp为无条件转移指令可以只修改ip,也可以同时修改cs和ip

jmp要给出两种信息:

1.转移的目的地址
2.转移的距离(段间转移、段内短转移、段内近转移)

不同的给出目的地址的方法,和不同的转移位置,对应有不同格式的jmp指令

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

jmp short 标号(转到标号处执行指令)

在这里插入图片描述
比如下面的程序:
在这里插入图片描述

注:

  1. CPU在执行jmp指令的时候并不需要转移的目的地址
  2. 机器指令中并不包含转移的目的地址
  3. 在“jmp short 标号”指令所对应的机器码中,并不包含转移的目的地址,而包含的是转移的位移,这个位移,是编译器根据汇编指令中的“标号”计算出来的

计算方法如下:
在这里插入图片描述
还有一种和“jmp short 标号”功能相近的指令格式,jmp near ptr 标号,它实现的是段内近转移
在这里插入图片描述

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

“jmp far ptr 标号”实现的是段间转移,又称为远转移,功能如下:

标号所在段的段地址在cs中,偏移地址在ip中;
far ptr指明了指令用标号的段地址和偏移地址修改cs和ip

如下面的程序:
在这里插入图片描述

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

指令格式:jmp 16位 reg
功能:(ip) = (16位 reg)(相当于把ip的值放入16位寄存器中)

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

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

1.jmp word ptr 内存单元地址(段内转移)
功能:从内存单元地址处开始存放着一个字,是转移的目的偏移地址;
内存单元地址可用寻址方式的任一格式给出
在这里插入图片描述

2.jmp dword ptr 内存单元地址(段间转移)
功能:从内存单元地址处开始存放着两个字,高地址处的字是转移的目的段地址,低地址处的是转移的目的偏移地址
·
(cs)=(内存单元地址+2)
(ip)=(内存单元地址)
内存单元地址可用寻址方式的任一格式给出
在这里插入图片描述

jcxz指令

jcxz指令为有条件转移指令,所有的有条件转移指令都是短指令,在对应的机器码中包含转移的位移,而不是目的地址。对ip的修改范围都为-128 ~ 127

指令格式:jcxz 标号(如果(cx)= 0,转移到标号处执行)
·
操作:当(cx)= 0时,(ip)=(ip)+ 8 位位移
在这里插入图片描述
当(cx)!= 0时,程序向下执行;
·
从jcxz的功能可以看出,“jcxz 标号”的功能相当于:
if((cx)== 0)jmp short 标号

在这里插入图片描述

jcxz要对cx进行判断,cx为0,则跳转,此题意为找到第一个0,则跳转到标号ok处,将其偏移地址进行保存,但是我们查找的是字节(8位),cx为16位寄存器,所以可以用ch或cl来保存找到的字节,另一个设为0,这样当我们找到第一个0的时候,cx就为0,执行jcxz指令:
·
mov ch,0
mov cl,ds:[bx]
jcxz ok
inc bx

loop指令

loop指令为循环指令,所有的循环指令都是短指令,在对应的机器码中包含转移的位移,而不是目的地址,对ip的修改范围都为:-128 ~ 127

指令格式:loop 标号((cx)=(cx)- 1),如果(cx)!= 0,转移到标号处执行。
·
操作:(cx)=(cx)- 1;
如果(cx)!= 0,(ip)=(ip)+ 8位位移
在这里插入图片描述

在这里插入图片描述
注:loop指令是先执行cx - 1,在判断cx是否为0。这里,假设找到的第一个字节就为0,(因为找之前就设置的cx为0)那么此时cx - 1 = ffffh,本应该跳出,但是显然程序出现了错误;所以此处应该将cx加上1,在由loop指令将cx减去1,在判断cx是否为0,为0,则跳出循环;所以此空应填:inc cx

根据位移进行转移的意义

jmp short 标号
jmp near ptr 标号
jcxz 标号
loop 标号

这几种汇编指令,他们对ip的修改是根据转移的目的地址和转移起始地址之间的位移来进行的,在它们对应的机器码中不包含转移的目的地址,二保焊的是目的地址的位移;这种设计方便了程序段在内存中的浮动装配
在这里插入图片描述

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

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

在这里插入图片描述

第十章 call和ret指令

call和ret都是转移指令,他们都修改ip,或同时修改cs和ip,他们经常被共同用来实现子程序的设计

ret 和retf

ret 指令用栈中的数据,修改ip的内容,从而实现近转移

retf 指令用栈中的数据,修改CS和ip的内容,从而实现远转移

CPU执行ret指令时,进行下面两步操作:
在这里插入图片描述

CPU执行retf指令时,进行下面4步操作:
在这里插入图片描述

在这里插入图片描述

call指令

call指令经常和ret指令配合使用,因此CPU执行call指令时,进行两步操作:
(1)将当前的ip或cs和ip压入栈中
(2)转移

call指令不能实现短转移,除此之外,call指令实现转移的方法和jmp指令的原理相同

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

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

CPU执行此种格式的call指令时,进行如下操作:
在这里插入图片描述

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

call far ptr 标号 实现的是段间转移;CPU执行此种格式的call指令时,进行如下操作:
在这里插入图片描述

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

指令格式:call 16位 reg
在这里插入图片描述

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

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

1.call word ptr 内存单元地址
·
用汇编语法来解释此种格式的call指令,则:CPU执行“call word ptr 内存单元地址”时,相当于进行:
push IP
jmp word ptr 内存单元地址
在这里插入图片描述

2.call dword ptr 内存单元地址
·
用汇编语法来解释此种格式的call指令,则:CPU执行“call dword ptr 内存单元地址”时,相当于进行:
push cs
push ip
jmp dword ptr 内存单元地址
在这里插入图片描述

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值