一、汇编环境准备
1.安装DOSbox
下载:https://www.dosbox.com/download.php?main=1
安装完毕后,配置一个根目录,修改以下配置文件:
C:UsersAdministratorAppDataLocalDOSBoxdosbox-0.74-3.conf
最后部分修改为:
[autoexec] # Lines in this section will be run at startup. # You can put your MOUNT lines here. mount c: d:asm c:
d:asm是我们的工作目录。
2.准备工具
将以下工具都放到工作目录下:
debug.exe
masm.exe
link.exe
二、初识汇编
1.汇编器(汇编编译器)和指令
机器指令:0101001110
汇编指令:MOV AX,000C 人类能识别和编写的指令
汇编器:将汇编指令翻译成机器指令的翻译器。即可以将MOV AX,000C翻译成0110110011这种二进制机器码。
在DOSBOX中输入debug -u,可以看到以下内容:
左边的16进制数据和右边的汇编指令是对等的。即汇编器将右边的汇编指令翻译成了左边的16进制(对应二进制机器码)。
最左边的 073F:0100 是内存编号,他们是连续的,最小单位是byte,所以 74 03 这个指令占2个byte。
在我们使用 debug -u 的时候,该指令将内存中的数据解析成指令,我们也可以使用 debug -d 来查看内存中的原始数据:
可以看到, 073F:0100 开始的内存数据和上一个图中指令对应数据是一致的,只是 -d 指令将其显示为普通数据而已( -u 指令解析为指令)。
三、一些概念
1.地址线
CPU从内存读取数据的时候,要告诉内存我要拿那个地址的数据。由于CPU是通过电信号来传递地址信息的,电信号只有高低电平,也就是0和1。
如果地址线只有一条路,则只能表示2个位置,一个是0,一个是1。
如果地址线有两条路,则可以表示4个位置,00、01、10、11。
如果地址线有三条路,则可以表示8个位置,000、001、010、011、100、101、110、111。
以此类推:
我们所说的32位和64位表示的就是地址线的宽度,也就是地址线有多少个通路。32位就表示用32个二进制来表示内存地址,所以能够发给内存的地址信息是0~232-1,即0~4,294,967,295,这也就是说32位地址的内存有232个格子,每个格子为1 byte,这也就是为什么32位系统只能支持4G内存(4G内存就是232byte)。
在以往的CPU型号中,地址线的数量如下:
8080:16根,寻址能力为64KB 8088:20根,寻址能力为1MB 80286:24根,寻址能力为16MB 80386:32根,寻址能力为4GB
2.数据线
与地址线类似,地址线传送的数据用来表示内存位置,而数据线就是传递的数据本身。
假设数据线有8个通道,每一个通道传递一个二进制,则8个通道一次性就可以传递一个byte的数据。
如果有16个通道,则一次性可以传递两个byte的数据。
以此类推
以往CPU支持数据线的数量:
8080:8根,一次传递一个byte 8088:8根,一次传递一个byte 8086:16根,一次传递两个byte 80286:16根,一次传递两个byte 80386:32根,一次传递四个byte
3.控制线
控制线决定了CPU对其他部件(很多部件)进行控制的能力。
4.外部设备
除了内存、显存、ROM等设备的数据是通过内存地址去访问。还有一些外部设备,例如键盘、鼠标等是通过port端口号去访问的。
我们拆开鼠标或键盘可以看到这些设备都有一个芯片。
这个芯片中也会有一个存储空间,当我们按下一个键时,对应的数据会存储在该芯片的空间中。
然后通过线缆传递到主板的一个端口,然后CPU再通过端口号读取对应端口的数据,从而实现CPU读取外设数据。
四、寄存器
1.什么是寄存器
寄存器就是CPU用来存放指令和数据的地方,通过 debug -r 可以查看:
2.AX、BX、CX、DX寄存器
这四个寄存器就做通用寄存器,用于存储数据。每个寄存器可以存储2byte大小的数据,即16bit。
这四个寄存器有个特殊的地方,就是可以分为两个8bit的寄存器:
AX = AH + AL BX = BH + BL CX = CH + CL DX = DH + DL
H代表高八位,L代表低八位。
例如8086 CPU的数据线为16bit,则一次性最大可以读取2byte的数据,也就是说可以处理两种尺寸的数据:
字节型数据 1byte 放在8位寄存器中 字型数据 2byte 放在16位寄存器中
我们也可以称一个寄存器中,一个字节是这个字型数据的高位字节,另一个字节是这个字型数据的低位字节。
使用 debug -a 来体验一下给寄存器赋值:
注意,由于我们使用mov给AX寄存器赋值,AX寄存器为16bit寄存器,所以5被翻译成了0005H。
当我们对8bit寄存器赋值时,如下所示:
可以看到,mov中的8被翻译成了08H。而且值被赋予到了AH 8位寄存器中。
我们也可以同时输入多条指令,然后一条条执行:
注意:数据大小与寄存器大小必须保证一致性,即8bit寄存器存放8bit数据,16bit寄存器存放16bit数据,否则会报错。
3.寄存器的独立性
寄存器是相互独立,互不影响的,就算是一个16位寄存器分成的H和L两个8位寄存器,也是互不影响的。
例如,我们执行8bit 的加法运算:
至于溢出的部分数据,保存在了其他地方。
4.地址寄存器
前面了解的AX、BX、CX、DX都是用于存储数据的通用寄存器。这节我们了解一下存放内存地址的寄存器。
使用 debug -u 查看内存地址时,我们可以看到两列地址信息:
其中左边部分为段地址,右边部分为偏移地址部分。这两部分分别都是16bit大小,也就是都需要一个16bit寄存器来存储。
为什么要将内存地址分为两部分?
这是因为我们提高寻址能力就是要加宽地址线的数量,例如8086CPU的地址线为20bit,那么一个寄存器已经无法满足存储这个地址信息。
于是就使用了段地址+偏移地址的形式,公式如下:
基础地址 = 段地址 * 10H # 例如段地址为F230H,向左移一位变为F2300H
物理地址 = 基础地址 + 偏移地址 #F2300H + C8H = F23C8H,即物理地址为F23C8H,刚好为20bit
这里注意,一个物理地址可能对应多个 段地址和偏移地址的组合,例如:
21F60H = 2000H * 10H + 1F60H 21F60H = 2100H * 10H + 0F60H 21F60H = 21F6H * 10H + 0000H 21F60H = 1F00H * 10H + 2F60H
也就是说,内存地址是通过物理地址来表示的,只要段地址和偏移地址的组合生成的物理地址正确,就可以取到正确的数据。
5.查看某块地址的内存
使用-d查看2000:1F60地址开始的内存数据:
使用-u查看2000:1F60地址开始的内存数据,但是解析成指令:
虽然我们可以将内存的数据显示成普通数据和指令,但对于CPU,他只会将内存中某个特殊区域的数据当成指令。下节我们对其进行探究。
6.指令存储位置
当我们使用debug -r查看寄存器时:
可以看到 MOV AL,FC 指令对应的内存地址为073F:0100。
通过观察,我们可以确定IP寄存器保存着该指令的偏移地址。
而DS、ES、SS、CS这四个寄存器中,谁保存了指令的段地址,目前无法确定。
我们可以通过修改这四个寄存器的值,来看指令是否发生变化,从而确定是哪个寄存器保存指令的段地址:
从上面的过程可以看出,当我们修改CS寄存器的值时,指令发生了改变,说明CPU将CS寄存器中存储的数据作为指令的段地址。
总结:在8086 CPU中,CPU将CS:IP地址所保存的内容,全部当做指令来执行。
7.将内存中的数据作为指令运行
首先,我们使用debug -e 2000:0000向指定内存中存入一些数据:
然后使用debug -u查看一下翻译成指令是什么样子:
查看一下当前CS:IP在什么位置:
可以看到,当前CS:IP地址为073F:0100。我们将其修改为2000:0000:
可以看到,下面的指令变为我们存入内存数据对应的指令。
此时就可以用 debug -t 来执行了:
总结:从这一节我们可以看出,数据在内存中是没有区别的,只有当CS:IP指向的内存中的数据,才会被CPU当做指令来执行。
8.寄存器种类
在前面我们已经看到了有很多种寄存器,AX~DX为通用寄存器,用于存储数据(当然也可能有特殊用途,例如BX和CX就可以用来当作偏移地址寄存器使用,而AX和DX一般用于处理数据)。
而DS、ES、SS、CS用于存储段地址,IP用于存储指令偏移地址。
除了以上这些,剩下的SP、BP、SI、DI也是用于存储偏移地址的,我们在后面的章节会对其进行了解。
这些寄存器可能都有特殊的用途,不能对其一概而论。
==