汇编网首页登录博客注册
unixandlinux的学习博客
博客首页博客互动【做检测题】论坛求助

我的博客

个人首页 |  我的文章 |  我的相册 |  我的好友 |  最新访客 |  文章收藏 |  论坛提问 |  友情链接 |  给我留言  
图片载入中
学习动态
最新评论
最新留言
好友圈
友情链接

[2023-04-09 18:25] 《汇编语言》(王爽 著) —— 学习笔记(前言、第1章、第2章)

前言

指令仅仅是学习机器基本原理和设计思想的一种实例,而逐条地讲解每一条指令的功能不是本书的职责 —— 它应该是一本指令手册的核心内容。
汇编编程的平台是硬件而不是操作系统。必须通过一定的编程实践,体验一个裸机的环境,在一个没有操作系统的环境中直接对硬件编程 —— 这样才能真正体会到汇编语言的作用和看到没有操作系统的计算机系统是怎样的。
本书的所有内容都围绕着“深入理解机器工作的基本原理”和“培养底层编程意识和思想”,舍弃了所有和这两个目标关系并不密切的内容。
本书的读者应该具备以下基础:
1. 具有计算机的使用经验。
2. 具有二进制、十六进制等基础知识。
3. 具有一门高级语言(例如 C 语言)的基本编程基础。


第 1 章 基础知识

汇编语言是直接在硬件之上工作的编程语言。
汇编课程的研究重点放在如何利用硬件系统的编程结构和指令集,有效灵活地控制系统进行工作。

1.1 机器语言

机器语言是机器指令的集合。机器指令是一台机器可以正确执行的命令。电子计算机的机器指令是一组二进制数字序列 —— 计算机将之转变为一列高低电平,以便计算机的电子器件受到驱动,进行运算 —— 这一功能,PC 机是由一个芯片来完成,这个芯片就是 CPU(Central Processing Unit,中央处理单)。CPU 是一种微处理器。每一种微处理器,由于硬件设计和内部结构不同,需要用不同的电平脉冲来控制其工作,所以就有各自不同的机器指令集,即机器语言各不相同。

1.2 汇编语言的产生

汇编语言的主体是汇编指令。汇编指令和机器指令的差别在于指令的表示方法上,它们是一一对应的关系。也就是说,每一条汇编指令都是将一条机器指令翻译为人类能够读懂的语言后的结果,反之,每一条机器指令都是将一条人类能够读懂的汇编指令翻译为机器能“读懂”的语言后的结果 —— 注意,这里的关键是每一条机器指令都与唯一的一条汇编指令相对译,反之,每一条汇编指令也只与唯一的一条机器指令相对译。
由于不同类型的 CPU 都有自己的机器指令集,而机器指令与汇编指令是一一对应的,所以每一种 CPU 都有自己的汇编指令集。
汇编指令是机器指令便于记忆的另一种书写格式。例如:
机器指令:1000100111011000
汇编指令:mov ax, bx
执行操作:寄存器 BX 的内容传送到另一个寄存器 AX 中
可见汇编语言与机器语言本质上是相同的,它们是同一种语言的两种不同写法和表现形式。也就是说,汇编语言是对机器语言的“直译”,由此可以认为 C 语言或更高级的其他编程语言则是机器语言的“意译” —— 通过这样的“翻译”后,最终让机器语言能够被人类更容易地读懂。
寄存器是 CPU 中可以存储数据的器件。
计算机能读懂的只有机器指令,因此就需要一个能够将汇编指令转换成机器指令的翻译程序 —— 编译器(实际应该称为汇编器,因为它包含了编译和链接两个过程)。程序员用汇编语言写出源程序,再用汇编编译器将其编译为机器码,由计算机最终执行。

1.3 汇编语言的组成

汇编语言由以下 3 类指令组成:
1. 汇编指令:机器码的助记符,有对应的机器码。
2. 伪指令:没有对应的机器码,由编译器执行,计算机并不执行。
3. 其他符号:如 +、-、*、/ 等,由编译器识别,没有对应的机器码。
汇编语言的核心是汇编指令,它决定了汇编语言的特性。

1.4 存储器

要让 CPU 工作,就必须向它提供指令和数据。指令和数据在存储器中存放,也就是内存。如同没有了记忆再聪明的大脑也无法思考一样,离开内存的 CPU 也无法工作。磁盘也是一种存储器,但它所存储的数据或程序必须读入内存后才能被 CPU 使用。
学习汇编编程必须了解 CPU 是如何从内存读取信息,以及向内存写入信息的。

1.5 指令和数据

指令和数据只是应用上的抽象概念,在存储器中指令和数据没有任何区别,都是二进制信息,即都是一组二进制数字序列。CPU 工作时,通过一定的机制将某些信息看作指令,而另一些信息则看作数据:同一列二进制数据在某种机制下被看作指令,在另一种机制下可能就被看作是数据。

1.6 存储单元

电子计算机的最小信息单位是 bit,即一个二进制位。8 个 bit 组成一个 Byte,即一个字节。
存储器就是用于存储这些 bit 信息的物理器件,它被人为划分成若干存储单元。微型机存储器的一个存储单元存储一个 Byte 信息,即 8 个二进制位信息。一个存储器如果有 128 个存储单元,那么它就可以存储 128 个 Byte 信息。可见 Byte 是存储空间所能够存储的信息的容量单位。
存储器的每个存储单元都有唯一的编号信息,以标记其处于存储器内的所在位置,这个编号被称为存储单元的地址。存储器中存储单元的地址从 0 开始依序编号:如果存储器有 128 个存储单元,则其地址编号范围就是0~127。
微机存储器的容量是以字节为最小单位。对于拥有 128 个存储单元的存储器,其容量就是 128 个字节,即 128 Byte(简写为 128B)容量。
1KB = 1024B        1024 字节
1MB = 1024KB1024 千字节
1GB = 1024MB1024 兆字字
1TB = 1024GB1024 千兆字节(1024 吉字节)
其中 B 代表 Byte。
一个存储单元的存储容量为 1B(1 Byte),所以 128 个存储单元的存储容量就是 128B(128 Byte);存储单元的数量只能以“个”为单位而不能以“B(Byte)”为单位,更不能称“128 个存储单元”为“128B 个存储单元”或“128 Byte 个存储单元” —— “B(Byte)”是二进制信息的容量单位,“个”是存储单元的数量单位,因此书本在 p25 第 4 行末尾的“64KB个内存单元”这一说法是错误的。

1.7 CPU 对存储器的读写

除了内存和磁盘,计算机其他构成器件中也包含有存储器,例如显卡的存储器即显存。CPU 在读写数据时必须指明操作所针对的是哪一个器件,执行的是哪种操作 —— 从中读出数据,还是向里面写入数据。CPU 在执行操作之前,必须首先指定目标存储器的存储单元地址 —— CPU 要先确定是对哪一个存储单元执行操作。
可见,CPU 要进行数据的读写,必须和外部器件(即芯片)进行下述 3 类信息的交互:
1. 存储单元的地址(地址信息)。
2. 器件的选择(是对内存、磁盘、显存或其他器件执行操作)及执行的是读还是写命令(控制信息)。
3. 读或写的数据(数据信息)。
电子计算机处理、传输的信息都是电信号,电信号要用导线传送。专门连接 CPU 和其他芯片的导线称为总线。总线从物理上讲,就是一根根导线的集合。根据传送信息的不同,总线从逻辑上分为 3 类:地址总线、控制总线和数据总线。每个 CPU 芯片都有许多管脚,这些管脚和总线相连。
要让计算机或微处理器工作,应向它输入能够驱动它进行工作的电平信息(机器码)。

1.8 地址总线

CPU 是通过地址总线来指定存储器单元的,可见地址总线上能传送多少个不同的信息,CPU 就可以对多少个存储单元进行寻址 —— 因此存储单元的数量值就标示着 CPU 的寻址能力。
在电子计算机中,一根导线可传送的稳定状态只有两种,高电平或低电平,用二进制表示就是 1 或 0。10 根导线可以传送 10 位二进制数据,而 10 位二进制可以表示 2 的 10 次方个不同的数据,最小数为 0,最大数为 1023,因此其寻址能力为 1024B(2^10 Byte,1KB,1K 字节),即 1024 个存储单元。一个 CPU 有 N 根地址线,就称其地址总线的宽度为 N,它最多可以寻找 2 的 N 次方个存储单元,其寻址能力即为 2^N 字节。
可见 CPU 寻址能力的指标即可以表述为寻址的存储单元数量(个数),也可以表述为寻址的二进制信息容量 —— 这是对同一个 CPU 指标的两种不同表述方式,两种表述方式的含义完全相同,例如,128B(128 Byte)与 128 个存储单元,所表达的意思没有任何区别,因为 1 个存储单元能够存储的二进制信息容量就是 1B(1 Byte)。
这两种表述方式只是侧重点(或切入点)不同:一个侧重于存储单元数量,从存储单元的个数角度切入 CPU 所能够搜寻到的存储单元数量范围,另一个侧重于存储单元容量,以从存储单元的存储容量角度切入 CPU 所能够搜寻到的存储单元容量范围。

1.9 数据总线

CPU 与内存或其他器件之间的数据传送是通过数据总线来进行的。数据总线的宽度决定了 CPU 和外界之间数据传送的速度。8 根数据总线一次可传送一个 8 位二进制数据(1 Byte),16 根数据总线一次可传送两个字节(2 Byte)。
8088 CPU 的数据总线宽度为 8,8086 CPU 的数据总线宽度为 16。所以当传送 2 Byte 数据(两个字节)信息时,8088 CPU 要执行两次传送操作,而 8086 CPU 只要执行一次传送操作,因此 8086 比 8088 的 CPU 数据传送速度要快。

1.10 控制总线

CPU 对外部器件的控制是通过控制总线来进行的。控制总线是不同控制线的集合称。有多少根控制总线,就意味着 CPU 提供了对外部器件的多少种控制,即控制总线的宽度决定了 CPU 对外部器件的控制能力 —— 能够对外部器件执行 N+1 种控制的 CPU 与只能执行 N 种控制的 CPU 比较,则能执行 N+1 种控制的 CPU 的控制能力更强。
CPU 有专门的控制线来传送“读”操作信号,而另一根控制线则负责传送“写”操作信号。
如前所述,在存储器中无论指令还是数据都以二进制形式存放;当存储器中的二进制信息在地址总线上传送时,就被看作是一个地址值;如果同样的二进制信息在数据总线上传送,就被看作是一个值;而该二进制信息在控制总线上传送,就被看作是一个控制指令 —— 这就是 CPU 区分信息类型(同一组二进制数字序列是地址信息还是数据信息,又或是指令信息)的机制之一。
另一种对“控制能力”的理解(依据小甲鱼老师的视频):一条控制线有两种状态(1 和 0),分别表示读和写;每根控制线一次传送只能控制一个器件(例如向存储器发送 1,则表示执行从该存储器中读取数据的操作,发送 0,就表示执行向该存储器写入数据的操作),有多少根控制线就意味着一次传送最多能对多少个器件进行控制 —— 一次传送所能控制的器件越多,CPU 的控制能力就越强。

检测点 1.1

(1)
CPU 的寻址能力为 8KB,意味着 CPU 最多可以查找 8K 个存储单元(1 个存储单元的容量为 1B),因为:
8K = 8 * 1K = 2^3 * 2^10 = 2^13
也就是说 CPU 的寻址能力为 2^13 个存储单元,所以其地址总线宽度为 13。

(2)、(3)
因为 1 个存储单元可以存储 1 字节(1 Byte)数据信息,而 1KB 等于 1024 Byte(1024 字节),即 1KB 的存储器可以存储 1024 字节数据,也就是 1024 个存储单元,其存储单元的编号从 0 到 1023。如果一个存储单元包含 8 bit 位,那么 1KB 的存储器就可以存储 1024 * 8 个 bit,1024 个 Byte。

(4)
1GB、1MB、1KB 分别是 1024M、1024K、1024 Byte。

(5)
8080 的地址总线宽度为 16 根,则其寻址能力为 2^16 Byte,2^16 = 64 * 2^10 Byte,即 64KB。
8088 的地址总线宽度为 20 根,则其寻址能力为 2^20 Byte,2^20 = 2^10 * 2^10 = 2^10 KB,即 1MB。
80286 的地址总线宽度为 24 根,则其寻址能力为 2^24 Byte,2^24 = 2^4 * 2^20 = 2^4 MB,即 16MB。
80386 的地址总线宽度为 32 根,则其寻址能力为 2^32 Byte,2^32 = 2^2 * 2^30 = 2^2 GB,即 4GB。

(6)
8080 的数据总线宽度为 8 根,其一次能传送的数据量为 1B(8 bit)(一根数据线相当于 1 位 bit)。
8088 的数据总线宽度为 8 根,其一次能传送的数据量与 8080 相同,也为 1B。
8086 的数据总线宽度为 16 根,其一次能传送的数据量为 2B(16 bit)(1B 包含 8 bit)。
80286 的数据总线宽度为 16 根,其一次能传送的数据量与 8086 相同,也为 2B。
80386 的数据总线宽度为 32 根,其一次能传送的数据量为 4B(32 bit)(1B 包含 8 bit)。

(7)
从内存中读取 1024 字节(1024 Byte)的数据,由于 8086 包含的数据总线为 16 根,也就是一次能读取的数据量为 2B,则 1024B / 2B = 512,也就是需要读取 512 次;而 80386 包含的数据总线为 32 根,一次能读取的数据量为 4B,则 1024B / 4B = 256,也就是要读取 256 次。

(8)
在存储器中,数据和程序都以二进制形式存放。由此可知,当某列二进制信息在地址总线上传送时,这些二进制就被看作是一个地址值;如果同样的二进制信息在数据总线上传送时,就被看作是一个值;而该二进制信息在控制总线上传送时,就被看作是一个控制指令。

1.11 内存地址空间(概述)

一个 CPU 的地址总线宽度为 10,则其可寻址能力为 1024 个存储单元,这 1024 个存储单元就构成了 CPU 的内存地址空间(2^10 = 1024)。一个存储单元所存储的信息称为一个字节(Byte),其信息度量以 B 表示;1024 个存储单元存储的信息量就是 1024 字节,即 1024 Byte,简写为 1024B,也就是 1KB 内存地址空间。

1.12 主板

每一台 PC 机都有一个主板,主板上镶嵌着许多电子器件。这些器件通过总线(地址总线、数据总线、控制总线)相连,器件包括 CPU、存储器、外围芯片组、扩展插槽等。扩展插槽上插着 RAM 内存条和各类接口卡。

1.13 接口卡

计算机系统中所有可用程序控制其工作的设备,都必须受到 CPU 的控制。但 CPU 不能直接控制设备(显示器、音箱、打印机等),直接控制这些设备的是插在扩展插槽上的接口卡。
扩展插槽通过总线和 CPU 相连,由此接口卡也与 CPU 连接起来,CPU 便可直接控制接口卡,从而实现对外设的间接控制 —— CPU 通过总线向插在扩展插槽上的接口卡发送命令,接口卡根据该命令来控制外设进行工作。

1.14 各类存储器芯片

PC 机装有多个存储器芯片,它们在物理连接上是独立的、不同的器件,从读写属性上则可分为两类:随机存储器 RAM 和只读存储器 ROM。RAM 可读可写,但必须带电存储,断电后其存储的信息丢失;ROM 只能读不能写,断电后其存储的信息不会丢失。
存储器从功能和连接上又可分为以下几类:
1. 主随机存储器
用于存放供 CPU 使用的绝大部分程序和数据。一般由两个位置上的 RAM 组成:主板上的 RAM 和插在扩展槽上的 RAM。
2. 装有 BIOS(Basic Input Output System,基本输入/输出系统)的 ROM BIOS 是由主板和各类接口卡(例如显卡、网卡等)厂商提供的软件系统。通过 BIOS 可实现 CPU 与硬件设备之间的输入、输出操作,从而将该硬件设备作为计算机的输入输出设备来使用。
3. 接口卡上的 RAM
某些接口卡需要对大量输入、输出数据进行暂时存储,因此在其上装有 RAM。以显卡的 RAM(显存)为例,
CPU 将需要输出的信息写入显存,再由显示装置在显示器上输出。

1.15 内存地址空间

上述存储器在物理上是独立的器件,但具有以下两个共同点:
1. 都和 CPU 通过总线相连。
2. 对存储器的读写操作,都是由 CPU 通过控制线发出的内存读写命令来实现的。
CPU 把上节所述的存储器都看作内存,即把它们当作一个由若干存储单元组成的逻辑存储器总体,将其全体作为一个统一的内存地址空间。每个物理存储器在这个逻辑存储器中都占有一个地址段,即一段地址空间。CPU 在这段地址空间中读写数据,实际上就是在该地址段相对应的物理存储器中读写数据。
总结:存储器在 CPU 眼中,就是一个存储空间整体 —— 内存地址空间,而不是各个独立的物理器件;CPU 对内存地址空间进行统一编址,产生一个从 0 到 2^N-1(N 为地址总线的宽度)编号的地址表格清单,再通过某种机制为每个物理存储器分配地址段信息,即如将编号为 0 至 7FFF 的地址信息分配给存储器 A,将编号为 8000 至 9FFF 的地址信息分配给存储器 B,...;CPU 与内存地址空间的交流内容为:通过在地址总线上传递地
址信息以确定需要执行操作的存储空间位置,再通过数据总线传递的数据值信息以确定执行操作的数据值内容,以及通过控制总线传递的控制信息来确定所执行的操作是什么,从而实现 CPU 与所有存储器之间的沟通互连。
也就是说,CPU 只认识各个存储器在内存地址空间中的地址段信息(地址段就是如上所述的 0 到 7FFF 这样的一段内存空间地址信息),而不会理会该地址段对应的物理存储器器件是什么。可见,CPU 将内存地址空间视为一个整体独立的可操作存储器器件 —— 独立的统一整体逻辑器件,而不是各个分别独立的物理器件,实际的各个物理存储器器件在这个统一的内存地址空间逻辑器件中都占有一个地址段,从而成为该内存地址空间中的一段地址空间段;因此,地址段与具体的物理存储器之间就形成了一一对应关系,地址段中的具体某一个地址就与该物理存储器中的一个确定存储单元一一对应。
内存地址空间的大小受 CPU 地址总线宽度的限制。8086 CPU 的地址总线宽度为 20,可以传送 2^20 个不同的地址信息(0~2^20-1),即可以定位 2^20 个内存单元,则 8086 CPU 的内存地址空间大小为 1MB。同理,80386 CPU 的地址总线宽度为 32,其内存地址空间最大为 4GB。
在基于一个计算机硬件系统编程的时候,必须知道这个系统中的内存地址空间分配情况。因为想在某类存储器中读写数据就必须知道它的第一个单元的地址和最后一个单元的地址,才能保证读写操作是在预期的存储器中进行。比如,希望向显示器输出一段信息,那么必须将这段信息写到显存中,显卡才能将它输出到显示器上;要向显存中写入数据,就必须知道显存在内存地址空间中的地址。
不同的计算机系统的内存地址空间分配情况是不同的。
最终运行程序的是 CPU,因此用汇编语言编程时,必须要从 CPU 的角度考虑问题。


第 2 章 寄存器

一个典型的 CPU 由运算器、控制器、寄存器等器件构成,它们通过 CPU 的内部总线相连接(上一章所述的总线是 CPU 外部总线,不是此处所说的 CPU 内部总线)。内部总线实现 CPU 内部各器件之间的联系,外部总线实现 CPU 与其他主板器件的联系。CPU 内部各器件的功能如下:
1. 运算器进行信息处理。
2. 寄存器进行信息存储。
3. 控制器控制各种器件的工作。
4. 内部总线连接 CPU 内部各器件以实现它们之间的数据传送。
程序员可对 CPU 内部的寄存器进行指令读写操作 —— 通过改变其所存储的内容来实现对 CPU 的控制。
不同的 CPU,其包含的寄存器数量及寄存器本身的结构都不相同。8086 CPU 有 14 个寄存器,每个寄存器都有一个名称,分别是 AX、BX、CX、DX、SI、DI、SP、BP、IP、CS、SS、DS、ES、PSW。

2.1 通用寄存器

8086 CPU 的所有寄存器都是 16 位的,可以存放两个字节。AX、BX、CX、DX 这 4 个寄存器通常用来存放一般性的数据,被称为通用寄存器。16 位的寄存器能存放的数据最大值是 2^16-1(65535)。
8086 CPU 的上一代 CPU 的寄存器都是 8 位。为保证兼容,8086 CPU 的 AX、BX、CX、DX 这 4 个寄存器可分为两个独立的 8 位寄存器来使用:
1. AX 可分为 AH 和 AL。
2. BX 可分为 BH 和 BL。
3. CX 可分为 CH 和 CL。
4. DX 可分为 DH 和 DL。
AX 的低 8 位(0~7 bit)构成了 AL 寄存器,高 8 位(8~15 bit)构成了 AH 寄存器 —— AH 和 AL 寄存器是可以独立使用的 8 位寄存器。字母 H 表示高位(High)bit,字母 L 表示低位(Low)bit。

2.2 字在寄存器中的存储

1. 字节:记为 byte,一个字节由 8 个 bit 组成,可以存储在含有 8 个位(8-bit)的寄存器中。
2. 字:记为 word,一个字由两个字节组成,这两个字节分别称为这个字的高位字节和低位字节。
一个字可以存储在一个 16 位的寄存器(16-bit)中,这个字的高位字节和低位字节自然就存储在这个寄存器的高 8 位寄存器和低 8 位寄存器中。

2.3 几条汇编指令

汇编指令示例:

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

在写一条汇编指令或寄存器名称时,是不区分大小写的。如:mov ax, 18 和 MOV AX, 18 的含义相同;bx 和 BX 的含义也相同。
在 Debug 实验环境中输入这些汇编指令时,Debug 默认所输入的数值都是十六进制数,因此在实际输入数值时无需在数值末尾添加后缀 H;如果添加了后缀 H,则会报错,因为 Debug 认为字符 H 是个十六进制数字,而十六进制数字中并不存在 H 这个数字,所以 Debug 认为输入数据有错。
在执行下述汇编指令后,寄存器中所存储的数据如下(原 AX 的值:0000H。原 BX 的值:0000H):

程序段指令        指令执行后 AX 的数据值        指令执行后 BX 的数据值
mov ax, 4E20H                4E20H                                        0000H
add ax, 1406H                6226H                                        0000H
mov bx, 2000H                6226H                                        2000H
add ax, bx                        8226H                                        2000H
mov bx, ax                        8226H                                        8226H
add ax, bx                        044CH                                        8226H

注意,在 Debug 实验环境中输入上述汇编指令时,汇编指令中的数值不允许添加后缀 H,否则 Debug 会将后缀 H 也认为是个十六进制数字,而十六进制并不存在 H 这个数字,所以 Debug 认为输入错误。

问题 2.1
最后一行指令 add ax, bx 相当于 AX = AX + BX = 8226H + 8226H = (1)044CH,括号内的数字 1 表示溢出,所以寄存器 AX 所存储的值为 044CH。

再看如下示例(原 AX 的值:0000H。原 BX 的值:0000H):

程序段指令        指令执行后 AX 的数据值        指令执行后 BX 的数据值
mov ax, 001AH                001AH                                        000H
mov bx, 0026H                001AH                                        0026H
add al, bl                        0040H                                        0026H
add ah, bl                        2640H                                        0026H
add bh, al                        2640H                                        4026H
mov ah, 0                        0040H                                        4026H
add al, 85H                        00C5H                                        4026H
add al, 93H                        0058H                                        4026H

注意 Debug 实验环境中实际输入汇编指令时,数值不允许添加后缀 H。

问题 2.2
最后一行指令 add al, 93H 相当于 AL = AL + 93H = C5H + 93H = (1)58H,括号内的数字 1 表示溢出;
由于 al 是作为一个独立的 8-bit 寄存器来使用,所以 CPU 不会将其溢出的数字加到其高位(也就是 ah)寄存器中,而将该溢出值丢弃,所以最终的 AX 寄存器存储的值为 0058H。如果执行的是 add ax, 93H,则上述溢出值 1 才会加到 ax 的高 8 位中而成为 0158H。

在进行数据传送或运算时,指令的两个操作对象的位数应当是相同的。例如下面的指令是正确的:
mov ax, bx
mov bx, cx
mov ax, 18H
mov al, 18H
add ax, bx
add ax, 20000
注意,最后一行指令中的 20000 是十进制数值,在 Debug 实验环境中实际输入时应输入其转换为十六进制后的数值 4E20。
下面的指令则是不正确的 —— 指令的两个操作对象的位数不一致:
mov ax, bl        在 8 位寄存器和 16 位寄存器之间传送数据
mov bh, ax        在 16 位寄存器和 8 位寄存器之间传送数据
mov al, 200008 位寄存器最大可存放值为 255(十进制),20000 已经超过此数值范围 add al, 1008将一个高于 8 位的数据加到一个 8 位寄存器中

检测点 2.1

(1)
mov ax, 62627        AX = 62627(即 F4A3H)
含义:将十进制数 62627 存放到 AX 寄存器。AX 寄存器是 16-bit,其能存储的数值范围是 0~2^15-1,即 0~65535,而 62627 < 65535,所以数值 62627(F4A3H) 能够存储在 AX 寄存器中。
mov ah, 31H                AX = 31A3H
含义:将十六进制数 31 存放到 AX 寄存器的高 8 位 AH 上。AH 成为独立的 8-bit 寄存器,其能存储的数值范围是 0~2^8-1,即 00H~FFH,而 31H < FFH,所以数值 31H 能够存储在 AH 寄存器中。由于 AX 寄存器原来存储的数值是 F4A3H,执行本指令后则成为 31A3H。
mov al, 23H                AX = 3123H
含义:将十六进制数 23 存放到 AX 寄存器的低 8 位 AL 上。AL 成为独立的 8-bit 寄存器,其能存储的数值范围是 0~2^8-1,即 00H~FFH,而 23H < FFH,所以数值 23H 能够存储在 AL 寄存器中。由于 AX 寄存器原来存储的数值是 31A3H,执行本指令后则成为 3123H。
add ax, ax                AX = 6246H
含义:AX = AX + AX。由于 AX 寄存器原来存储的数值是 3123H,执行本指令后则成为 6246H。
mov bx, 826CH        BX = 826CH
含义:将十六进制数 826CH 存放到 BX 寄存器。BX 寄存器是 16-bit,其能存储的数值范围是 0~2^15-1,
即 0000H~FFFFH,所以执行本指令后 BX 寄存器中将存放数值 826CH。
mov cx, ax                CX = 6246H
含义:CX = AX。由于 AX 寄存器存储的数值是 6246H,执行本指令后 CX 寄存器也存放数值 6246H。
mov ax, bx                AX = 826CH
含义:AX = BX。由于 BX 寄存器存储的数值是 826CH,执行本指令后 AX 寄存器也存放数值 826CH。
add ax, bx                AX = 04D8H
含义:AX = AX + BX。由于 AX、BX 寄存器原来存储的数值都是 826CH,执行本指令后则 AX 寄存器中将存放 826CH + 826CH = (1)04D8H。其中最高位 1(括号括住部分)是运算后的溢出,将会被丢弃,所以 AX 中实际存放的数值是 04D8H。
mov al, bh                AX = 0482H
含义:将 BX 的高 8 位存放数值赋给 AX 的低 8 位寄存器中。AX 原来的数值是 04D8H,BX 原来的数值是 826CH,因此运算会将 BH 的值 82H 赋给 AX 的低 8 位寄存器 AL,而 AX 的高 8 位未参与运算,仍然是原来的 04,所以最终的结果是 AX 寄存器中存放着的数值 0482H。
mov ah, bl                AX = 6C82H
含义:将 Bl 的低 8 位存放数值赋给 AX 的高 8 位寄存器中。AX 原来的数值是 0482H,BX 原来的数值是 826CH,因此运算会将 BL 的值 6CH 赋给 AX 的高 8 位寄存器 AH,而 AX 的低 8 位未参与运算,仍然是原来的 82,所以最终的结果是 AX 寄存器中存放着的数值 6C82H。
add ah, ah                AX = D882H
含义:执行 AH = AH + AH。AX 原来的数值是 6C82H,其高 8 位独立寄存器 AH 为 6CH,因此将执行的指令是 AH = 6CH + 6CH,因此 AH 成为 D8H,AX 的低 8 位寄存器未参与运算,仍然是 82H,因此 AX 寄存器最终存放的数值是 D882H。
add al, 6                AX = D888H
含义:将十进制数 6 与 AX 寄存器的低 8 位寄存器 AL 存放的数值相加后赋给 AL 寄存器。AX 原来存放的数值是 D882H,因此 AL 寄存器的原值为 82H,与 06H 相加的结果是 88H,因此执行指令后,AL 寄存器存储的数值为 88H,而 AH 未参与运算,所以 AX 寄存器存放的数值为 D888H。
add al, al                AX = D810H
含义:执行 AL = AL + AL。AX 原来的数值是 D888H,其低 8 位独立寄存器 AL 为 88H,因此将执行的指令是 AL = 88H + 88H,因此 AL 成为 10H,AX 的高 8 位寄存器未参与运算,仍然是 D8H,因此 AX 寄存器最终存放的数值是 D810H。
mov ax, cx                AX = 6246H
含义:AX = CX。由于 CX 寄存器存储的数值是 6246H,执行本指令后 AX 寄存器也存放数值 6246H。

(2)
mov ax 2
add ax ax
add ax ax
add ax ax

2.4 物理地址

CPU 必须获取内存单元的地址后才能访问内存单元。所有内存单元构成的存储空间是一个一维的线性空间。
每个内存单元在这个空间中都有唯一的地址,称为物理地址。
CPU 通过地址总线向存储器传送的,必须是一个内存单元的物理地址,才能确定 CPU 要对哪个内存单元执行操作 —— 为此,CPU 必须在发出物理地址之前先在 CPU 内部生成这个物理地址信息。不同的 CPU 可以有不同的生成物理地址方式。

2.5 16 位结构的 CPU

8086 CPU 的上一代 CPU(8080\8085)是 8 位机,8086 是 16 位机,也称为 16 位结构的 CPU(字长 16 位)。16 位结构 CPU 具有以下结构特性:
1. 运算器一次最多可以处理 16 位的数据。
2. 寄存器的最大宽度为 16 位。
3. 寄存器和运算器之间的通路为 16 位。
也就是说,在 8086 CPU 内部,能够一次性处理、传输、暂时存储的信息的最大长度是 16 位。在将内存单元地址传送到地址总线之前,必须在 CPU 内部处理、传输、暂时存放地址;16 位 CPU 能一次性处理、传输、暂时存储 16 位的地址。

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

8086 CPU 地址总线宽度为 20,可传送含 20 位(bit)的地址值,每个地址值对应的存储单元可存储 1 字节(Byte)长度的 bit 信息(1 Byte = 8 bits),则其寻址能力为 1MB(2^20 Byte,2^20 个地址值)。8086 CPU 是 16 位结构,在内部一次性处理、传输、暂时存储的地址为 16 位,因此如果将地址从内部简单地发出,那么它只能送出 16 位的地址,这个 16 位的地址可以是 2^16 个地址中的 1 个,因此其表现出的寻址能力就
只有 64KB(即 CPU 一次性处理、传输、暂时存储的地址值为 64K 个地址中的一个,一个地址对应一个存储单,一个存储单元的存储容量为 1 Byte,因此 64K 个地址就相当于存储容量 64KB):
2^16 = 2^6*2^10 = 2^6 K = 64 K
注意,一个 16 位的地址对应的仍然是一个存储单元,而一个存储单元的存储容量仍然是 1 Byte —— 无论一个地址是用多少位(8 位、16 位甚至 32 位)来表示的,它都只对应一个存储单元,也就是 1 Byte 存储容量。
8086 CPU 以一种在内部用两个 16 位地址合成的方法来形成一个 20 位的物理地址。可见,8086 CPU 内部地址总线为 16 位,只表明其一次性处理、传输、暂时存储的地址是用 16-bit 来表示,但 CPU 一次仍然只传送一个地址,无论这个地址是用多少个 bit 来表示,CPU 都不会一次传送两个甚至更多个地址,一个地址只对应一个存储单元,而一个存储单元的存储容量就是 1 Byte。
当 8086 CPU 要读写内存时:
(1) CPU 中的相关部件提供两个 16 位的地址,一个称为段地址,另一个称为偏移地址。
(2) 段地址和偏移地址通过内部总线送入一个称为地址加法器的部件。
(3) 地址加法器将两个 16 位地址合成为一个 20 位的物理地址。
(4) 地址加法器通过内部总线将 20 位物理地址传到输入输出控制电路。
(5) 输入输出控制电路将 20 位物理地址传到地址总线。
(6) 20 位物理地址被地址总线传送到存储器。
地址加法器采用“物理地址 = 段地址 x 16 + 偏移地址”的方法用段地址和偏移地址合成物理地址。

由“段地址 × 16”引发的讨论:
“段地址 × 16”有个更为常用的说法,称为“左移 4 位”。计算机中的所有信息都是以二进制的形式存储,段地址当然也不例外。机器只能处理二进制信息,“左移 4 位” 中的位是指二进制位 bit。
一个数据为 2H,二进制形式为 10B,对其进行左移运算,情况如下:

左移位数        二进制                十六进制        十进制
        0                  10B                        2H                   2
        1                 100B                        4H                   4
        2                1000B                        8H                   8
        3           10000B                   10H                  16
        4          100000B                   20H                  32

观察以上移位次数和各形式数据的关系可以发现:
(1) 一个数据的二进制形式左移 1 位,相当于该数据乘以 2。
(2) 一个数据的二进制形式左移 N 位,相当于该数据乘以 2 的 N 次方。
(3) 地址加法器完成“段地址 × 16”的运算过程,就是将以二进制形式存放的段地址左移 4 位。
进一步思考:一个数据的十六进制形式左移 1 位就相当于乘以 16。一个数据的十进制形式左移 1 位,相当于乘以 10。一个 X 进制的数据左移 1 位,相当于乘以 X。

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

注意,本节讨论的是 8086 CPU 段地址和偏移地址的本质含义,而不是为解决具体问题而在本质含义之上引申的更高级的逻辑意义。无论“段地址 x 16 + 偏移地址 = 物理地址”寻址模式拥有多少种逻辑意义,都必须从其本质含义出发才能灵活应用这种寻址功能来解决和分析问题。
“段地址 × 16 + 偏移地址 = 物理地址”的本质含义:CPU 在访问内存时,用一个基础地址(段地址×16)和一个相对于基础地址的偏移地址相加,给出内存单元的物理地址。
实际上,8086 CPU 的这种寻址方式只是“基础地址 + 偏移地址 = 物理地址”寻址模式的众多实现方案中的一种。其中“段地址 × 16”可看作是基础地址。

2.8 段的概念

名称“段地址”中包含着“段”的概念,容易导致“以为内存被划分成一个一个的段,每个段有一个段地址”这样的误解。
其实内存并没有分段,段的划分来自于 CPU。8086 CPU 用“基础地址(段地址x16)+偏移地址=物理地址”的方式给出内存单元的物理地址,使得可以用分段的方式来管理内存。
例如可以认为(物理)地址 10000H~100FFH 的内存单元组成一个段,起始地址(基础地址)为 10000H,段地址为 1000H(基础地址 10000H = 段地址 1000H x 16,十六进制数乘以 16,段地址 1000H 左移 1 位成为基础地址 10000H,末尾字符 H 表明数值为十六进制数),大小为 100H(地址范围 10000H~100FFH 包含 100H 个存储单元 —— 一个地址对应一个存储单元,00H~FFH 构成偏移地址的取值范围,偏移地址在 00H~FFH 之间)。
也可认为(物理)地址 10000H~1007FH 范围和 10080H~100FFH 范围的内存单元组成两个段,起始地址(基础地址)分别为 10000H 和 10080H,段地址分别为 1000H 和 1008H,大小都为 80H(地址范围 10000H~1007FH 和 10080H~100FFH 都包含 80H 个存储单元 —— 一个地址对应一个存储单元,00H~7FH 及 80H~FFH 分别构成这两段物理地址的偏移地址的取值范围,即基础地址 10000H 的偏移地址取值范围为 00H~7FH,基础地址 10080H 的偏移地址取值范围为 80H~FFH,这两个偏移地址的取值范围大小都为 80H)。
因此,在编程时可根据需要,将若干地址连续的内存单元看作一个段,用“段地址×16”来定位段的起始地址(基础地址),用偏移地址定位段中的内存单元。需注意两点:“段地址×16”所得结果必然是 16 的倍数,所以一个段的起始地址也一定是 16 的倍数,可见,基础地址的末尾数必定为 0;偏移地址为 16 位,16 位地址的寻址能力为 64K Byte(64K 字节,简写为 64KB),所以一个段的长度最大为 64KB(64*1024=65536 Byte),
即偏移地址的取值范围是 0000H~FFFFH(16^4=65536 个地址,一个地址对应一个存储单元,因此包含 65536 个存储单元,一个存储单元为 1 Byte,因此包含 65536 Byte)。

内存单元地址小结:
CPU 访问内存单元时,必须向内存提供内存单元的物理地址。8086 CPU 在 CPU 的内部用段地址和偏移地址移位相加的方法形成最终的物理地址。
(1) CPU 可以用不同的段地址和偏移地址形成同一个物理地址。例如:

物理地址段地址        偏移地址
21F60H        2000H        1F60H
                2100H        0F60H
                21F0H        0060H
                21F6H        0000H 
                1F00H        2F60H

(2) 偏移地址 16 位,变化范围为 0~FFFFH,仅用偏移地址来寻址最多可寻 64K 个内存单元 —— 不能将“64K 个内存单元(存储单元)”说成“64KB 个内存单元(存储单元)”,因为 B 是 Byte 的缩写,表示一个字节,也就是一个内存单元(存储单元)所存储的信息量(1 Byte 包含 8 bit),因此也可以说“64KB 的信息容量”。
在 8086 PC 机中,存储单元的地址用两个元素来描述,即段地址和偏移地址。
“数据在 21F60H 内存单元中。”这句话对于 8086 PC 机一般不这样讲,取而代之的是两种类似的说法:
a. 数据保存在内存 2000:1F60 单元中。
b. 数据保存在内存的 2000H 段的 1F60H 单元中。这两种描述都表示“数据保存在内存的 21F60H 单元中”。
其中 2000(H) 表示段地址,1F60(H) 表示偏移地址。
可以根据需要,将地址连续、起始地址为 16 的倍数的一组内存单元定义为一个段。
注意,在 Debug 实验环境中所有数值均默认为是十六进制,因此在实际输入数值时不允许输入后缀 H,否则 Debug 会将字符 H 认作一个十六进制数,而十六进制中并不存在数字 H,因此 Debug 认为输入数据错误。

检测点 2.2

(1) 偏移地址使用 16 位总线宽度,其数值范围为 0000H~FFFFH;给定段地址 0001H,其基础地址为:
基础地址 = 段地址 x 16 = 00010H
即段地址左移 1 位。而物理地址为:
物理地址 = 基础地址 + 偏移地址 = 00010H + 0000H~FFFFH = 00010H~1000FH
也就是说,CPU 的寻址范围为 00010H 到 1000FH 之间。

(2) 数据存放的存储单元 20000H 既可以是起始地址或终止地址的存储单元,也可以是这两个地址之间的任意一个地址对应的存储单元,该范围由偏移地址来决定;使用 16 位总线宽度的偏移地址,其范围是 0000H~FFFFH。
假设给定的段地址为 XH,则基础地址和物理地址存在如下等式:
基础地址 = 段地址 x 16 = X0H
物理地址 = 基础地址 + 偏移地址 = X0H + 0000H~FFFFH = 20000H
即:基础地址 X0H = 20000H - 0000H~FFFFH = 10001H~20000H
也就是说,基础地址必须大于等于 10001H 且小于等于 20000H,相应的段地址范围为 1000H~2000H,但其中的 1000H 对应基础地址 10000 小于 10001H,而比 1000H 大且最相邻的段地址为 1001H,并且段地址 1001H 对应基础地址 10010H 大于基础地址的最小值 10001H,因此符合基础地址范围 10001H~20000H 的段地址范围就是 1001H~2000H,所以段地址 XH 的最小值为 1001H,最大值为 2000H。注意,基础地址必须是 16 的倍数,因此其末尾数必须为 0。
由上所述可知,当段地址不在 1001H~2000H 范围内,则无论 CPU 怎样变化偏移地址(偏移地址的变化范围为 0000H~FFFFH),都无法寻到 20000H 这个地址的存储单元,因此给定段地址应为 ≤ 1000H 或 > 2000H。

2.9 段寄存器

前已述,8086 CPU 在访问内存时要由相关部件提供内存单元的段地址和偏移地址,送入地址加法器以合成物理地址。那么是 CPU 的什么部件提供了段地址呢?段地址在 8086 CPU 的段寄存器中存放。8086 CPU 有 4 个段寄存器:CS、OS、SS、ES。当访问内存时,由它们向外提供内存单元的段地址。本章只涉及其中的 CS。

2.10 CS 和 IP

CS 和 IP 是 8086 CPU 中两个最关键的寄存器;内存的某块存储空间中存放着 CPU 当前要读取的指令的信息,而这块存储空间的地址就被存储在这两个寄存器中。CS 为代码寄存器,存储着段地址;IP 为指令指针寄存器,存储着偏移地址。
设 CS 中的内容为 M,IP 中的内容为 N,在任意时刻,8086 CPU 将从内存地址为 Mx16+N 的存储单元开始读取一条指令并执行。也就是说,任意时刻,8086 CPU 将 CS:IP 指向的内容当作指令执行(“CS:IP”这种表示方式参见 2.8 节“内存单元地址小结”部分所述)。
指令内容是一串机器码,特定的机器码已经在设计计算机时就被指定了具体的含义。通常机器码以十六进制数表示,在计算机内则以二进制形式存在。例如(注意,在 Debug 实验环境中,汇编指令中的数值均默认是十六进制,不允许添加后缀 H,否则 Debug 会将 H 认作一个十六进制数而报错):
1. 机器码 B8 23 01
汇编指令 mov ax, 0123H
其中 B8 表示“将指定的对象数据存储到寄存器 AX 中”。这里“被指定的对象数据”就是 B8 后面的 23 01,即 0123H 这一数值(注意,数值中的各位数字的排列顺序与它们在机器码中的排列顺序并不相同)。
2. 机器码 BB 03 00
汇编指令 mov bx, 0003H
其中 BB 表示“将指定的对象数据存储到寄存器 BX 中”。这里“被指定的对象数据”是 03 00,即 0003H。
注意,这条机器码指令是 BB(两个字母 B),上一条是 B8(字母 B 和数字 8),两者并不相同。
3. 机器码 89 D8
表示“将寄存器 BX 所存储的内容存入寄存器 AX 中”。
汇编指令 mov ax, bx
4. 机器码 01 D8
表示“寄存器 BX 和寄存器 AX 所存储的数据相加后,将结果存入寄存器 AX 中”。
汇编指令 add ax, bx
读取一条指令后,IP 中的值自动增加,以使 CPU 可以读取下一条指令;IP 所增加的值就是当前正在读取的指令的长度(指令内容占用的存储空间字节数)—— 特别注意,CPU 先增加 IP 的值后再执行当前所读取到的指令,而不是先执行当前读取到的指令后才增加 IP 的值,这个操作顺序非常重要 —— 其重要性可从下述 2.11 节后面的“问题 2.3”中显示出来。
8086 CPU 的工作过程简述如下:
(1) 从 CS:IP 指向的内存单元读取指令,将该指令传送入指令缓冲器。
(2) 执行“IP=IP+当前所读取指令的长度”,从而让 IP 指向下一条指令。
(3) 执行当前读取到的指令,转到步骤 (1),重复上述步骤。
注意,步骤 (2) 的“增加 IP 值”与步骤 (3) 的“执行当前读取到的指令”,两者的执行顺序不能颠倒。
8086 CPU 加电启动或复位后(即 CPU 刚开始工作时)CS 和 IP 被设置为 CS = FFFFH,IP = 0000H,CPU 从内存 FFFF0H 单元中读取指令;也就是说,FFFF0H 单元中的指令是 8086 PC 机开机后执行的第一条指令。
前章已述,在内存中,指令和数据没有区别,都是二进制信息,只是 CPU 在工作时通过某个机制把有的信息看作指令,而有的信息则看作数据。通过本节学习可知,CPU 将 CS:IP 指向的内存单元中的内容看作指令,
因为在任何时候,CPU 将 CS、IP 中的内容当作指令的段地址和偏移地址,用它们合成指令的物理地址,到内存中读取指令码以执行之 —— 这就是 CPU 区分指令和数据的机制。

2.11 修改 CS、IP 的指令

对于 CPU,程序员能够用指令读写的部件只有寄存器 —— 改变寄存器的存储内容以实现对 CPU 的控制。CPU 从何处执行指令是由 CS、IP 的存储内容决定的,程序员通过改变 CS、IP 的存储内容来控制 CPU 去执行的目标指令。
8086 CPU 大部分寄存器的值都可以用 mov 指令来改变,mov 指令被称为传送指令。但 mov 指令不能用于设置 CS、IP 的值,因为 8086 CPU 没有提供这样的功能 —— 它提供另外的指令来改变 CS、IP 的值。能够改变 CS、IP 的存储内容的指令被统称为转移指令。一个最简单的可以修改 CS、IP 的指令是 jmp 指令:
1. 同时修改 CS 和 IP 的 jmp 指令
jmp 段地址:偏移地址
指令功能:以指令中给出的段地址来修改 CS,以给出的偏移地址来修改 IP。注意,“段地址:偏移地址”是一种表示内存单元物理地址的方法,这是一种固定的表示内存单元物理地址的书写格式。因此段地址和偏移地址无需添加后缀 H。
示例:
jmp 2AE3:3
执行后:CS=2AE3H,IP=0003H,CPU 将从内存地址为 2AE33H 的存储单元处读取指令。
jmp 3:0B16
执行后:CS=0003H,IP=0B16H,CPU 将从内存地址为 00B46H 的存储单元处读取指令。
2. 仅修改 IP 的 jmp 指令
jmp <某一合法寄存器>
该指令的功能:用指定的寄存器中存储的值修改 IP。示例:
jmp ax
指令执行前:AX=1000H,CS=2000H,IP=0003H
指令执行后:AX=1000H,CS=2000H,IP=1000H
jmp bx
指令执行前:BX=0B16H,CS=2000H,IP=0003H
指令执行后:BX=0B16H,CS=2000H,IP=0B16H
指令“jmp ax”的含义类似于“mov IP, ax”,即将寄存器 AX 所存储的内容存放入寄存器 IP 中 —— 这里采用一种“用汇编解释汇编”的方法来使读者更好地理解汇编指令的功能;注意,这是用“已知的汇编指令的语法”进行描述,并不是用“己知的汇编指令”来描述,因为实际上并不存在“mov IP, ax”指令(该指令不合法)—— 只是借用 mov 指令的语法来说明“jmp ax”指令的功能。

问题 2.3
假设 CPU 初始状态为 CS = 2000H,IP = 0000H,内存中存放的机器码和对应的汇编指令情况如下:
(注意,以下指令机器码均以十六进制表示,并且在 Debug 实验环境中所有数值均默认是十六进制,因此输入的数值不允许添加后缀 H,否则 Debug 会将 H 认作一个十六进制数而报错)

地  址内存中的机器码                对应的汇编指令
10000H        B8 23 01                mov ax, 0123H
10003H        B8 00 00                mov ax, 0000H
10006H        8B D8                        mov bx, ax
10008H        FF E3                        jmp bx
20000H        B8 22 66                mov ax, 6622H
20003H        EA 03 00 00 10        jmp 1000:3
20008H        89 C1                        mov cx, ax

CPU 对上述指令的执行过程如下:
(1) 当前 CS=2000H,IP=0000H,则 CPU 从内存地址 2000H×16+0=20000H 的存储单元处读取指令,读入的指令
是:B8 22 66(mov ax, 6622H),读入后 IP=IP+3=0003H。
(2) 指令执行后,CS=2000H,IP=0003H,则 CPU 从内存地址 2000H×16+0003H=20003H 的存储单元处读取指令,读入的指令是:EA 03 00 00 10(jmp 1000:0003),读入后IP=IP+5=0008H。
(3) 指令执行后,CS=1000H,IP=0003H,则 CPU 从内存地址 1000H×16+0003H=10003H 的存储单元处读取指令,读入的指令是:B8 00 00(mov ax, 0000),读入后 IP=IP+3=0006H。
(4) 指令执行后,CS=1000H,IP=0006H,则 CPU 从内存地址 1000H×16+0006H=10006H 的存储单元处读取指令,读入的指令是:8B D8(mov bx, ax),读入后 IP=IP+2=0008H。
(5) 指令执行后,CS=1000H,IP=0008H,则 CPU 从内存地址 1000H×16+0008H=10008H 的存储单元处读取指令,读入的指令是:FF E3(jmp bx),读入后 IP=IP+2=000AH(注意,这里“IP 增值”是在“执行此处读入的指令”之前被执行)。
(6) 指令执行后,CS=1000H,IP=0000H(指令“jmp bx”改变了 IP 存储的数据值),CPU 从内存地址 10000H 的存储单元处读取指令“B8 2H 01(mov ax, 0123H)”,读入后 IP=IP+3=0003H。... ...。
由此可知,指令执行序列如下(Debug 实验环境中不允许输入数值后缀 H,否则报错):
(1) mov ax, 6622H
(2) jmp 1000:3
(3) mov ax, 0000
(4) mov bx, ax
(5) jmp bx
(6) mov ax, 0123H
(7) 转到第 3 步执行

2.12 代码段

对于 8086 PC 机,在编程时可以根据需要将一组内存单元定义为一个段(参 2.8 节)。通过将长度为 N(N ≤ 64KB,8086 CPU 内部存储器件和总线是 16 位的,这使得其一次性处理、传送和暂时存储的数据长度是 2B,因此其一次性寻址能力最大为 64KB —— 参前面章节所述)的一组代码,存储在一组地址连续、起始地址为 16 的倍数的内存单元中,则可认为这段内存是用来存放代码的,从而定义了一个代码段。比如有以下指令:
(注意,Debug 实验环境中不允许输入数值后缀 H,否则报错)
mov ax, 0000H(B8 00 00)
add ax, 0123H(05 23 01)
mov bx, ax        (8B D8)
jmp bx                (FF E3)
这段长度为 10 个字节的指令组存放在地址为 123B0H~123B9H 的一组内存单元中,可认为 123B0H~123B9H 这段内存是用来存放代码的,是一个代码段,其段地址为 123BH,长度为 10 个字节。
将一段内存当作代码段,仅仅是在编程时的一种安排,CPU 并不会由于这种安排就自动将所定义的代码段中的机器码当作指令来执行。CPU 只认为被 CS:IP 指向的内存单元中的内容为指令。所以,要让 CPU 执行放在代码段中的指令,必须让 CS:IP 指向所定义的代码段中的第一条指令的首地址。
上述示例将一段代码存放在地址为 123B0H~123B9H 的内存单元中,要让这段代码以指令形式得到执行,需设置 CS=123BH、IP=0000H 以使 CS:IP 指向存储这段代码的内存单元首地址,这可以通过 jmp 指令实现。

2.9 节 ~ 2.12 节 小结

1. 段地址在 8086 CPU 的段寄存器中存放。当 8086 CPU 要访问内存时,由段寄存器提供内存单元的段地址。
8086 CPU 有 4 个段寄存器,其中 CS 寄存器用来存放指令的段地址。
2. CS 寄存器存放指令的段地址,IP 寄存器存放指令的偏移地址。
8086 计算机中,任意时刻,CPU 都将 CS:IP 指向的内容当作指令执行。
3. 8086 CPU 的工作过程:
(1) 从 CS:IP 指向的内存单元读取指令,将读取到的指令传入指令缓冲器。
(2) IP 指向下一条指令。
(3) 执行所读取的指令。(转到步骤 (1),重复这个过程。)
4. 8086 CPU 提供转移指令修改 CS、IP 的内容。

检测点 2.3

指令序列如下:
mov ax, bx
sub ax, ax
jmp ax
执行上述 3 条指令后,CPU 共 4 次修改 IP,其中前三次是在每次读取完一条指令后,IP 都自动进行了增值,第 4 次修改则发生在执行最后一条 jmp 指令时 —— jmp指令将 AX 寄存器所存储的值存入 IP 寄存器,所以最后 IP 中的值与 AX 中的值相同。由于第 2 条指令执行的操作是将 AX 寄存器中的值减去其自身,所得的结果为 0,然后将该结果存入 AX 寄存器,所以 AX 寄存器存储的值为 0,这使得最后的 IP 值也为 0。

实验 1 查看 CPU 和内存,用机器指令和汇编指令编程

一、预备知识:Debug 的使用

1. 什么是 Debug?

Debug 是 DOS、Windows 都提供的实模式(8086 方式)程序的调试工具。使用它,可以查看 CPU 各种寄存器中的内容、内存的情况和在机器码级跟踪程序的运行。

2. Debug 的功能

R 命令:查看、改变 CPU 寄存器的内容.
D 命令:查看内存中的内容:
E 命令:改写内存中的内容:
U 命令:将内存中的机器指令翻译成汇编指令:
T 命令:执行一条机器指令:
A 命令:以汇编指令的格式在内存中写入一条机器指令。
Debug 的命令共有 20 多个,这 6 个命令是和汇编学习密切相关的,后面还会用到一个 P 命令。

3. 进入 Debug

Debug 是在 DOS 操作系统下使用的程序。在进入 Debug 前,应先进入到 DOS 操作系统。在 Debug 实验环境中,输入的机器码和汇编指令中的数字,均默认为十六进制,因此不允许添加后缀 H,否则 Debug 会将数值后缀 H 认作是一个十六进制数字,而十六进制中并不存在 H 数字,因此被当作输入错误而报错。
(1) 重新启动计算机以进入 DOS 操作系统,这是真实的 DOS 操作系统,称为实模式 DOS。
(2) 在 Windows 操作系统中进入 DOS,此时进入的是虚拟 8086 模式的 DOS 操作系统,这不是真实的 DOS 操作系统,因此称为虚拟模式 DOS。

4. 用 R 命令查看、改变 CPU 寄存器的内容
命令格式:r [CPU 寄存器]
键入命令名 r 后直接按 Enter 键执行,则输出显示 CPU 寄存器当前所存储的内容;如果命令名 r 后接着输入某个 CPU 寄存器名称,则在下一行跳出命令提示符“:”,等待输入需要改写的内容,将以所输入的数据改写该指定 CPU 寄存器的存储内容。示例如下:
-r ax
:0AD8
-r
示例中,字符“-”和“:”是自动跳出的命令提示符,其中“-”是 Debug 命令提示符,“:”是执行 r ax 命令时自动显示的输入提示符。该示例第 1 条 r 命令将输入的数据值 0AD8H(一个十六进制数)写入 AX 寄存器,第 2 条 r 命令的功能是显示(查看)CPU 寄存器的存储内容。
注意,在输入提示符“:”处输入数据值时,不能用键盘右侧的数字小键盘来输入,否则显示 Error 错误。
r 命令还能改写 CS 和 IP 寄存器存储的内容,操作方法同上。

5. 用 D 命令查看内存中的内容
在一进入 Debug 后用 D 命令(只输入命令名 d)直接查看,将列出 Debug 预设的内存地址(当前 CS:IP 指向的地址)处所存储的内容。D 命令可以有多种格式,这里介绍其中两种:
(1) d <段地址:偏移地址>
该格式将列出从指定内存单元开始的 128 个内存单元的存储内容。例如:
-d 1000:0
(2) d <段地址:起始偏移地址 结尾偏移地址>
该格式将查看命令名 d 后由<段地址:起始偏移地址 结尾偏移地址>所指定的内存区域的存储内容。例如:
-d 1000:0 9
这种命令格式可以通过输入相同的起始偏移地址和结尾偏移地址的方法,查看某个已知地址的内存单元所存储的信息:
-d 1000:0 0
-d 0fff:10 10
-d 0100:f000 f000
这 3 条 D 命令都可以查看地址为 10000H 的内存单元所存储的内容。这 3 条命令的段地址和偏移地址虽然各不相同,但由它们组合成的物理地址却都为 10000H。
在使用上述任一种格式查看后再次只输入命令名 d 执行,可列出内存中后续的地址存储单元的存储内容。
D 命令列出的信息由 3 部分组成:
(1) 中间部分是指定地址开始的存储单元所存储的内容,用十六进制格式输出。注意,每行的中间有一个“-”字符,它将存储内容从是中间分隔成两部分,以方便查看。
(2) 左边是每行的起始地址。
(3) 右边是每个存储单元中存储的数据所对应的可显示 ASCII 码字符 —— 如果没有对应可显示的 ASCII 字符,就用“.”字符代替。

6. 用 E 命令改写内存中的内容
下述地址,实际输入时都以“基础地址:偏移地址”方式输入,并不是以物理地址方式输入。
(1) 直接写入数据:e <起始地址 数据1 数据2 数据3 ... ...>
从指定的起始地址开始将给定的数据序列依次写入内存单元。例如:
-d 1000:0 f
1000:0000 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00
-e 1000:0 0 1 2 3 4 5 6 7 8 9
-
-d 1000:0 f
1000:0000 00 01 02 03 04 05 06 07-08 09 00 00 00 00 00 00
这种格式还可以写入字符数据:
-e 1000:0 1 'a' 2 'b' 3 'c'
-d 1000:0 f
1000:0000 01 61 02 62 03 63 06 07-08 09 00 00 00 00 00 00
注意,内存中存储的字符显示出来的是该字符的编码值(示例中显示的是字符的 ASCII 码值)。
这种格式还可以写入字符串数据:
-e 1000:0 1 "a+b" 2 "c++" 3 "IBM"
-d 1000:0 f
1000:0000 01 61 2B 62 02 63 2B 2B-03 49 42 40 00 00 00 00
同样,字符串中的字符将以其编码值显示出来(示例中显示的是字符串中各字符的 ASCII 码值)。
(2) 采用提问(等候输入)的方式逐个改写指定内存地址及其后续地址的存储单元所存储的内容:e <起始地址>
从指定的内存地址开始,逐个存储单元地输入其存储的内容,直至按 Enter 键结束执行。例如:
-d 1000:10 19
1000:0010 6D 61 72 6B 73 29 20 69-6E 20
-e 1000:10
1000:0010 6D.0 61.1 72.2 6B.1c
-d 1000:10 19
1000:0010 00 01 02 1C 73 29 20 69-6E 20
上述示例的 E 命令的执行步骤如下:
a. 输入 e 1000:10,按 Enter 键。
b. 显示起始地址 1000:0010 及该地址的存储单元所存储的内容 6D,光标停在一个“.”字符后提示(等待)输入想要存入该内存单元的数据 —— 示例中输入的是 0。然后按空格键,即用所输入的数据 0 改写当前内存单元的存储内容(将 6D 改写为 00)。注意,在“.”提示输入的字符后面不输入数据而直接按空格键,则不对当前内存单元进行改写(即保留其原来的数据 6D)—— 不论是否进行的改写,只要按下空格键,就表示对当前内存单元的处理已经完成。
c. Debug 接着显示下一个内存单元的存储数据(示例为 61),并提示修改(如前所述)。
d. 所有希望改写的内存单元都处理完毕后,按 Enter 键,E 命令执行结束。

7. 用 E 命令向内存中写入机器码,用 U 命令查看内存中机器码的含义,用 T 命令执行内存中的机器码
(1) 机器码也是数据,因此用上述 E 命令的第 1 种命令格式就可以向内存写入机器码。
例如,要从内存 1000:0 单元开始写入下面这段机器码:

机器码对应的汇编指令
b80100mov ax, 0001
b90200mov cx, 0002
01c8add ax, cx

E 命令如下:
-e 1000:0 b8 01 00 b9 02 00 01 c8
(2) 用 U 命令可以查看内存中存储的机器码对应的汇编指令:u <起始地址>
起始地址指的是机器码指令(或对应的汇编指令)存放的内存空间第 1 个存储单元的地址(指令代码所在内存空间的首地址)
U 命令执行后的输出分为 3 部分:每一条机器指令的地址、机器指令、机器指令对应的汇编指令。例如:
-u 1000:0
1000:0000 B80100MOV        AX,0001
1000:0003 B90200MOV        CX,0002
1000:0006 01C8        ADD        AX,CX
1000:0008 034942ADD        CX,[BX+DI+42]
... ...
可以看到,内存中的数据和代码没有任何区别,关键在于如何解释。
(3) 用 T 命令可以执行一条或多条指令:输入 T 命令名 t 后按 Enter 键,即可执行 CS:IP 指向的指令。
要用 T 命令控制 CPU 执行写到某个内存地址的存储空间中的指令,就必须先让 CS:IP 指向该地址空间,这通过用 R 命令修改 CS、IP 中的内容就可以实现 —— 即以 R 命令修改 CS 和 IP 寄存器所存储的数据,以使得 CS:IP 指向写入的指令代码(通过 E 命令向指定的内存地址写入指令机器码)所存储的地址空间。例如:
-d 1000:0 f
1000:0000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-e 1000:0 b8 01 00 b9 02 00 01 c8
-d 1000:0 f
1000:0000 b8 01 00 b9 02 00 01 c8 00 00 00 00 00 00 00 00
-r cs
CS 0741
:1000
-r ip
IP 0100
:0
-r
AX=0000 BX=0000 CX=0000 ...
...                        CS=1000 IP=0000 ...
1000:0000 B80100        MOV AX,0001
-t

AX=0001 BX=0000 CX=0000 ...
...                        CS=1000 IP=0003 ...
1000:0003 B90200        MOV CX,0002
-t

AX=0001 BX=0000 CX=0002 ...
...                        CS=1000 IP=0006 ...
1000:0006 01C8                MOV AX,CX
-t

AX=0003 BX=0000 CX=0002 ...
...                        CS=1000 IP=0008 ...
1000:0008 0000                ADD [BX+SI],AL

8. 用 A 命令以汇编指令的形式在内存中写入机器指令
前面用 E 命令向内存写入机器码数据,这样写入的机器指令很不直观,因此 Debug 提供了 A 命令,以直接向指定的内存空间写入汇编指令。
A 命令的格式:a [起始地址]
起始地址指 A 命令所输入汇编指令的存储空间的第 1 个存储单元的地址,表示要将所输入的汇编指令存入该内存地址空间;如果缺省该地址(即输入 A 命令名 a 后直接按 Enter 键),则表示后面输入的汇编指令存入预设的内存地址空间(当前 CS:IP 指向的地址)。
执行 A 命令写入汇编指令时,在 Debug 给出的指令起始地址后不输入汇编指令而直接按 Enter 键,则表示执行 A 命令的操作结束。示例如下:
-a 1000:0
1000:0000 mov ax,1
1000:0003 mov bx,2
1000:0006 mov cx,3
1000:0009 add ax,bx
1000:000B add ax,cx
1000:000D add ax,ax
1000:000F
-
-d 1000:0 f
1000:0000 B8 01 00 BB 02 00 B9 03-00 01 D8 01 C8 01 C0 00
-u 1000:0 f
1000:0000 B80100 MOV AX,0001
1000:0003 BB0200 MOV BX,0002
1000:0006 B90300 MOV CX,0003
1000:0009 01D8 ADD AX,BX
1000:000B 01C8 ADD AX,CX
1000:000D 01C0 ADD AX,AX
1000:000F 0000 ADD [BX+SI],AL
-

Debug 命令总结:
1. Debug 的 E 和 A 命令用于将数据写入内存,D 和 U 命令用于查看(输出显示)内存所存储的信息,T 命令用于执行 CS:IP 指向的地址所存储的指令,R 命令用于查看(输出显示)和改写 CPU 寄存器所存储的数据。
2. E 和 A 命令的区别:E 命令以机器码或字符(串)形式写入数据,A 命令则以汇编指令的形式写入数据。
3. E 和 A 命令的共同点:
(1) E 和 A 命令都有两种命令格式:
e <起始地址 数据1 数据2 数据 3 ...>
e <起始地址>
a [起始地址]
当 A 命令省略起始地址时,将把所输入的汇编指令存入预设的内存地址空间(当前 CS:IP 指向的地址)。
(2) 在使用这两个命令时,都以按 Enter 键作为结束命令的执行。
4. D 和 U 命令的区别:
(1) D 命令以机器码形式输出内存中所存储的数据,而 U 命令则以汇编指令形式输出。
(2) D 命令有 3 种命令格式而 U 命令通常只使用 1 种命令格式:
d
d <段地址:偏移地址>
d <段地址:起始偏移地址 结尾偏移地址>
u <段地址:偏移地址>
其中没有结尾偏移地址的 D 命令将显示 128 个内存单元所存储的数据,没有地址信息的 D 命令则显示预设的内存地址(当前 CS:IP 指向的地址)处 128 个内存单元所存储的数据。
5. T 命令的执行只有 1 种命令格式,运行一次 T 命令只执行一条指令。
6. R 命令有两种命令格式:
r [CPU 寄存器]
其中省略 CPU 寄存器名的 R 命令将输出显示 CPU 所有寄存器的存储信息,包含 CPU 寄存器名的 R 命令则用于改写该指定寄存器的存储内容 —— 注意,改写寄存器存储内容时,不能使用键盘右侧的数字小键盘来输入要存入寄存器的数据,否则显示 Error 错误。
7. 上述命令在输入和输出信息时,都使用十六进制数据形式;在输入数据时不区分字母大小写。

二、实验任务

1. 用 Debug 将下面的程序段写入内存,逐条执行,观察每条指令执行后 CPU 中相关寄存器中内容的变化:

机器码                汇编指令
b8 20 4e        mov ax, 4E20H
05 16 14        add ax, 1416H
bb 00 20        mov bx, 2000H
01 d8                add ax, bx
89 c3                mov bx, ax
01 d8                add ax, bx
b8 1a 00        mov ax, 001AH
bb 26 00        mov bx, 0026H
00 d8                add al, bl
00 dc                add ah, bl
00 c7                add bh, al
b4 00                mov ah, 0
00 d8                add al, bl
04 9c                add al, 9CH

提示,可用 E 命令和 A 命令以两种方式将指令写入内存。注意用 T 命令执行时,CS:IP 的指向。
用 E 命令将指令写入内存的执行过程如下:
-e 1000:0
1000:0000 00.b800.20        00.4e        00.05        00.16        00.14        00.bb        00.    
1000:0008 00.2000.01    00.d8        00.89        00.c3        00.01        00.d8        00.b8
1000:0010 00.1a00.                00.bb        00.26        00.                00.                00.d8        00.
1000:0018 00.dc00.                00.c7        00.b4        00.                00.                00.d8        00.04
1000:0020 00.9c
-d 1000:0 21
1000:0000B8 20 4E 05 16 14 BB 00-20 01 D8 89 C3 01 D8 B8
1000:00101A 00 BB 26 00 00 D8 00-DC 00 C7 B4 00 00 D8 04
1000:00209C 00
-u 1000:0 21
1000:0000 B8204E        MOV AX,4E20
1000:0003 051614        ADD AX,1416
1000:0006 BB0020        MOV BX,2000
1000:0009 01D8                ADD AX,BX
1000:000B 89C3                MOV BX,AX
1000:000D 01D8                ADD AX,BX
1000:000F B81A00        MOV AX,001A
1000:0012 BB2600        MOV BX,0026
1000:0015 00D8                ADD AL,BL
1000:0017 00DC                ADD A,BL
1000:0019 00C7                ADD B,AL
1000:001B B400                MOV A,00
1000:001D 00D8                ADD AL,BL
1000:001F 049C                ADD AL,9C
1000:0021 0000                ADD [BX+SI],AL
-
用 A 命令将指令写入内存的执行过程如下:
-a 1000:0
1000:0000 mov ax,4e20
1000:0003 add ax,1416
1000:0006 mov bx,2000
1000:0009 add ax,bx
1000:000B mov bx,ax
1000:000D add ax,bx
1000:000F mov ax,001a
1000:0012 mov bx,0026
1000:0015 add al,bl
1000:0017 add ah,bl
1000:0019 add bh,al
1000:001B mov ah,0
1000:001D add al,bl
1000:001F add al,9c
1000:0021
-d 1000:0 21
1000:0000B8 20 4E 05 16 14 BB 00-20 01 D8 89 C3 01 D8 B8
1000:00101A 00 BB 26 00 00 D8 00-DC 00 C7 B4 00 00 D8 04
1000:00209C 00
-u 1000:0 21
1000:0000 B8204E        MOV AX,4E20
1000:0003 051614        ADD AX,1416
1000:0006 BB0020        MOV BX,2000
1000:0009 01D8                ADD AX,BX
1000:000B 89C3                MOV BX,AX
1000:000D 01D8                ADD AX,BX
1000:000F B81A00        MOV AX,001A
1000:0012 BB2600        MOV BX,0026
1000:0015 00D8                ADD AL,BL
1000:0017 00DC                ADD AH,BL
1000:0019 00C7                ADD BH,AL
1000:001B B400                MOV AH,00
1000:001D 00D8                ADD AL,BL
1000:001F 049C                ADD AL,9C
1000:0021 0000                ADD [BX+SI],AL
通过使用 R 命令将 CS:IP 指向存储着上述指令的内存单元,再用 T 命令执行指令,观察 CPU 寄存器的变化情况,具体执行过程如下:
-r
AX=0000 BX=0000 ...
...                        CS=0741 IP=0100 ....
0741 0100 0000        ADD [BX+SI],AL
-r cs
CS 0741
:1000
-r ip
IP 0100
:0000
-r
AX=0000 BX=0000 ...
...                        CS=1000 IP=0000 ....
1000:0000 B8204E        MOV AX,4E20
-t

AX=4E20 BX=0000 ...
...                        CS=1000 IP=0003 ....
1000:0003 051614        ADD AX,1416
-t

AX=6236 BX=0000 ...
...                        CS=1000 IP=0006 ....
1000:0006 BB0020        MOV BX,2000
-t

AX=6236 BX=2000 ...
...                        CS=1000 IP=0009 ....
1000:0009 01D8                ADD AX,BX
-t

AX=8236 BX=2000 ...
...                        CS=1000 IP=000B ....
1000:000B 89C3                MOV BX,AX
-t

AX=8236 BX=8236 ...
...                        CS=1000 IP=000D ....
1000:000D 01D8                ADD AX,BX
-t

AX=046C BX=8236 ...
...                        CS=1000 IP=000F ....
1000:000F B81A00        MOV AX,001A
-t

AX=001A BX=8236 ...
...                        CS=1000 IP=0012 ....
1000:0012 BB2600        MOV BX,0026
-t

AX=001A BX=0026 ...
...                        CS=1000 IP=0015 ....
1000:0015 00D8                ADD AL,BL
-t

AX=0040 BX=0026 ...
...                        CS=1000 IP=0017 ....
1000:0017 00DC                ADD AH,BL
-t

AX=2640 BX=0026 ...
...                        CS=1000 IP=0019 ....
1000:0019 00C7                ADD BH,AL
-t

AX=2640 BX=4026 ...
...                        CS=1000 IP=001B ....
1000:001B B400                MOV AH,00
-t

AX=0040 BX=4026 ...
...                        CS=1000 IP=001D ....
1000:001D 00D8                ADD AL,BL
-t

AX=0066 BX=4026 ...
...                        CS=1000 IP=001F ....
1000:001F 049C                ADD AL,9C
-t

AX=0002 BX=4026 ...
...                        CS=1000 IP=0021 ....
1000:0021 0000                ADD [BX+SI],AL
-

2. 将下面 3 条指令写入从 2000:0 开始的内存单元中,利用这 3 条指令计算 2 的 8 次方:

mov ax, 1
add ax, ax
jmp 2000:0003

首先使用 A 命令将上述指令写入指定的内存单元:
-a 2000:0
2000:0000 mov ax,1
2000:0003 add ax,ax
2000:0005 jmp 2000:0003
2000:0007
-d 2000:0 0007
2000:0000 B8 01 00 01 C0 EB FC 00
-u 2000:0 0007
2000:0000 B80100MOV AX,1
2000:0003 01C0        ADD AX,AX
2000:0005 EBFC        JMP 2000:0003
2000:0007 0000        ADD [BX+SI],AL
-
通过使用 R 命令将 CS:IP 指向存储着上述指令的内存单元,再用 T 命令执行指令,观察 CPU 寄存器的变化情况,具体执行过程如下:
-r cs
CS 1000
:2000
-r ip
IP 0023
:0
-r
AX=0002 BX=4026 ...
...                        CS=2000 IP=0000 ...
2000:0000 B80100MOV AX,0001
-t

AX=0001 BX=4026 ...
...                        CS=2000 IP=0003
2000:0003 01C0        ADD AX,AX
-t

AX=0002 BX=4026 ...
...                        CS=2000 IP=0005
2000:0005 EBFC        JMP 2000:0003
-t

AX=0002 BX=4026 ...
...                        CS=2000 IP=0003
2000:0003 01C0        ADD AX,AX
-t
AX=0004 BX=4026 ...
...                        CS=2000 IP=0005
2000:0005 EBFC        JMP 2000:0003
-t
....
以上不断重复执行 T 命令,直到完成 2 的 8 次方的计算为止。

3. 查看内存中的内容
PC 机主板上的 ROM 中写有一个生产日期,在内存 FFF00H~FFFFFH 的某几个单元中,请找到这个生产日期并试图改变它。
提示,如果对实验的结果感到疑惑,请仔细阅读第 1 章中的 1.15 节。
-d FFF0:0 F
...
-e FFF0:0 F
...
-d FFF0:0 F
通过上述三条 D 和 E 命令,来查看、改写和验证,发现无法改写该日期。原因在于此日期写入了 ROM 存储器中,ROM 是只读存储器,其所存储的内容是无法用普通的方法改写的。

4. 向内存从 B8100H 开始的单元中填写数据,如:

-e B810:0000 01 01 02 02 03 03 04 04
请先填写不同的数据,观察产生的现象;再改变填写的地址,观察产生的现象。
提示,如果对实验的结果感到疑惑,请仔细阅读第 1 章中的 1.15 节。
内存地址空间 B8100~BFFFF 地址段属于显存地址空间,当使用本实验数据以 E 命令修改其存储内容时,会在屏幕的中右上部分出现一个彩色(蓝绿红)的小斑块,可以使用以下数据重做本实验(数据来自“汇编网”网站):
-e B800:0740 01 01 02 02 03 03 04 04
评论次数(0)  |  浏览次数(97)  |  类型(学习笔记) |  收藏此文  | 
 
 请输入验证码  (提示:点击验证码输入框,以获取验证码