第3章、寄存器(内存访问)
3.1 内存中字的存储
字单元:存放一个字型数据(16位)的内存单元,由两个连续的内存单元组成,高地址内存单元放字型数据的高位字节,低地址内存单元中存放字型数据的低位字节。
将起始地址为N的字单元称为N地址字单元。
3.2 DS和[address]
DS寄存器:通常用来存放要访问数据的段地址
[address]表示一个偏移地址为address的内存单元,段地址默认放在ds中
mov指令将一个内存单元中的内容送入另一个寄存器中:
mov 寄存器名 [内存单元的偏移地址]
指令执行时,8086CPU自动取ds寄存器中的数据为内存单元的段地址。
所以我们在将一个内存单元中的内容送入一个寄存器中时,需要提前将内存单元的段地址放入ds中。
8086CPU不支持将数据直接送入段寄存器,只能通过另一个寄存器中转,先将段地址送入一个一般的寄存器,如bx,再将bx中的内容送入ds。
3.4 mov、add、sub指令
mov 寄存器,数据 比如: mov ax,8
mov 寄存器,寄存器 比如: mov ax,bx
mov 寄存器,内存单元 比如: mov ax,[0]
mov 内存单元,寄存器 比如: mov [0],ax
mov 内存单元,段寄存器 比如: mov [0],cs
mov 段寄存器,内存单元 比如: mov cs,[0]
mov 段寄存器,寄存器 比如: mov ds,ax
mov 寄存器,段寄存器 比如:mov ax,ds
mov指令几乎都可以互相传递,唯一要注意的是:不能将数据直接送入段寄存器!。
可以将内存单元的内容送入段寄存器,也可以将寄存器的内容送入段寄存器,但是唯独不能将数据直接送入段寄存器!
3.7 CPU提供的栈机制(ss和sp)
在基于8086CPU编程的时候,可以将一段内存当作栈来使用。
8086CPU的入栈和出栈操作都是以字为单位进行的。
push和pop指令执行时,CPU从SS和SP中得到栈顶的地址。
段寄存器SS,存放栈顶的段地址,SP寄存器存放栈顶的偏移地址,任意时刻,SS:SP指向栈顶元素
8086CPU中,入栈时,栈顶从高地址向低地址方向增长。
push ax
表示将寄存器ax中的数据送入栈中,由两步完成。
- SP=SP-2,SS:SP指向当前栈顶前面的单元,以当前栈顶前面的单元为新的栈顶;
- 将ax中的内容送入SS:SP指向的内存单元处,SS:SP此时指向新栈顶。
pop ax
表示从栈顶取出数据送入ax,由以下两步完成。
- 将SS:SP指向的内存单元处的数据送入ax中;
- SP=SP+2,SS:SP指向当前栈顶下面的单元,以当前栈顶下面的单元为新的栈顶。
当栈为空的时候,不存在栈顶元素,此时SS:SP指向栈的最底部单元下面的单元。该单元的偏移地址为栈最底部的字单元的偏移地址+2!
一定要注意,栈最底部的字单元的地址并不是最底部的内存单元的地址!
由于栈从栈底到栈顶是从高地址到低地址的,而字单元的地址是两个字节单元地址中的较低的那一个,所以栈最底部的字单元的地址并不是最底部的内存单元的地址!
3.8 栈顶超界的问题
栈顶超界会导致其他内存空间的指令或数据被覆盖,产生无法想象的后果。
8086CPU不保证我们对栈的操作不会超界。
我们在编程时要自己操心栈顶超界的问题。
3.9 push、pop指令
push 寄存器 将一个寄存器中的数据入栈
pop 寄存器 出栈,用一个寄存器接收出栈的数据
push 段寄存器 将一个段寄存器中的数据入栈
pop 段寄存器 出栈,用一个段寄存器接收出栈的数据
实验
- 将10000H~1000FH这段空间当作栈,初始状态栈是空的;
- 设置AX=001AH,BX=001BH;
- 将AX、BX中的数据入栈;
- 然后将AX、BX清零;
- 从栈中恢复AX、BX原来的内容。
mov ax, 1000H
mov ss, ax
mov sp, 0010H ;初始化栈顶
mov ax, 001AH
mov bx, 001BH
push ax
push bx ;ax、bx入栈
sub ax, ax ;将ax清零,也可以用mov ax,0,
;sub ax,ax的机器码为2个字节,
;mov ax,0的机器码为3个字节。
sub bx, bx
pop bx ;从栈中恢复ax、bx原来的数据
pop ax ;
将10000H~1000FH这段空间当作栈,初始状态栈是空的;
mov ax, 1000H
mov ss, ax
mov sp, 0010H
空栈时SP指向的内存单元的偏移地址为栈最底部的字单元的偏移地址+2
空栈时SP指向的内存单元的偏移地址为栈最底部的字单元的偏移地址+2
空栈时SP指向的内存单元的偏移地址为栈最底部的字单元的偏移地址+2
问题 3.12
因为push、pop在执行时只能修改SP,所以栈顶的变化范围是0~FFFFH,一个栈最大可以被设为64KB。
小于64KB的栈,栈顶超界时会覆盖别的内存单元;
而等于64KB的栈,栈顶超界时,SP会溢出,栈顶将环绕,覆盖原来栈中的内容。
段的综述
我们可以用一个段存放数据,将它定义为“数据段”;
我们可以用一个段存放代码,将它定义为“代码段”;
我们可以用一个段当作栈,将它定义为“栈段“。
对于数据段,将它的段地址放在DS中,用mov、add、sub等访问内存单元的指令时,CPU就将我们定义的数据段中的内容当作数据来访问;
对于代码段,将它的段地址放在CS中,将段中第一条指令的偏移地址放在IP中,这样CPU就将执行我们定义的代码段中的指令;
对于栈段,将它的段地址放在SS中,将栈顶单元的偏移地址放在SP中,这样CPU在需要进行栈操作的时候,比如执行push、pop 指令等,就将我们定义的栈段当作栈空间来用。
CPU将内存中的某段内容当作代码,是因CS:IP指向了那里; CPU将某段内存当作栈,是因为SS:SP指向了那里。
一段内存,可以既是代码的存储空间,又是数据的存储空间,还可以是栈空间,也可以什么也不
是。关键在于CPU中寄存器的设置,即CS、IP、SS、SP、 DS的指向。
实验 2
我们可以用“d 段地址:偏移地址 ”的方式查看指定内存单元的内容,其中的段地址存储在DS段寄存器中。
我们既可以以数据的方式直接给出段地址,也可以以段寄存器的方式给出段地址。
第4章、第一个程序
4.1 一个源程序从写出到最终执行的过程
- 使用文本编辑器用汇编语言写汇编程序,产生了一个存储源程序的文本文件
- 使用汇编语言编译程序对文件中的源程序进行编译,产生目标文件
- 使用连接程序对目标文件进行连接,产生可在操作系统中直接运行的可执行文件
- 操作系统执行可执行文件:
- OS将可执行文件中的机器码和数据加载入内存
- 进行相关的初始化(比如设置CS:IP指向第一条要执行的指令)
- 由CPU执行程序。
4.2 源程序
在汇编语言源程序中有两种指令:
- 汇编指令:有对应的机器码,可以被编译为机器指令,最终被CPU执行
- 伪指令:没有对应的机器码,由编译器来执行
1.asm
assume cs:codesg 将用作代码段的段codesg和段寄存器cs联系起来。
codesg segment 定义一个段,段的名称为“codesg”,这个段从此开始
codesg是一个标号,作为一个段的名称,最终被编译连接成一个段的段地址
mov ax, 0123H
mov bx, 0456H
add ax, bx
add ax, ax
mov ax, 4c00H
int 21H 这两条指令实现程序的返回
codesg ends 名称为“codesg”的段到此结束
end 编译器在编译汇编程序的过程中,碰到了伪指令end,结束对源程序的编译
源程序:源程序文件中的所有内容称为源程序
程序:源程序中最终由CPU执行处理的指令或数据(即源程序中去掉伪指令的部分)
程序最先以汇编指令的形式存在源程序中,经过编译、连接后变成机器码,存在可执行文件中。
可执行文件是怎么得到运行的?
一个程序P2在可执行文件中,则必须有一个正在运行的程序P1,将P2从可执行文件中加载入内存后,将CPU的控制权交给P2,P2才能得以运行。P2开始运行后,P1暂停运行。而当P2运行完毕后,应该将CPU的控制权交还给使它得以运行的程序P1,此后P1继续运行。
程序返回:一个程序结束后,将CPU的控制权交还给使它得以运行的程序
所以需要在程序的末尾添加返回的程序段。
mov ax, 4c00H
int 21H 这两条指令实现程序的返回
4.5 编译连接
首先在DOSBox Options.bat中对DOSBox中的虚拟目录和本地电脑中的真实目录挂载(mount)
如图,将DOSBox中的d:目录与本地路径的D:\DOSBox\masm挂载,即访问DOSBox中的d:目录相当于访问D:\DOSBox\masm
连接的作用:
- 当源程序很大时,可以将它分为多个源程序文件来编译,每个源程序编译成目标文件后,再用连接程序将它们连接在一起,生成一个可执行文件
- 程序中调用了某个库文件中的程序,需要将这个库文件和该程序生成的目标文件连接在一起,生成一个可执行文件
- 一个源程序编译后,得到了存有机器码的目标文件,目标文件中的有些内容还不能直接用来生成可执行文件,连接程序将这些内容处理为最终的可执行信息。所以,在只有一个源程序文件,也不需要调用某个库中的子程序的情况下,也必须用连接程序对目标文件进行处理,生成可执行文件。
4.6 以简化的方式进行编译和连接
在masm(link)后面加上被编译(连接)的源程序文件(目标文件)的路径、文件名,在结尾再加上分号,编译器就会在编译(连接)过程中自动忽略中间文件的生成。
4.7 exe的执行
在可执行文件的目录下输入可执行文件的名字即可执行(由于我将DOSBox中的d:目录与本地路径的D:\DOSBox\masm挂载,而可执行文件又在D:\DOSBox\masm下,所以此时就相当于在可执行文件所在的目录下)
4.8 谁将可执行文件中的程序装载入内存并使它运行?
程序P1要想运行,必须要有一个正在运行的程序P2,将P1从可执行文件中加载入内存,将CPU的控制权交给它,P1才能运行;P1运行完毕后,要将CPU的控制权还给使它得以运行的程序P2。
操作系统是由多个功能模块组成的庞大、复杂的软件系统,操作人员通过一个称为shell(外壳)的程序来操作计算机系统进行工作。
在DOS中有一个程序command.com,在DOS中被称为命令解释器,也就是DOS系统的shell
DOS启动时,先完成其他重要的初始化任务,然后运行command,由command执行用户输入的命令,比如:cd、dir、type。
在DOS中,command处理各种输入,命令或要执行的程序的文件名,我们就是通过command来进行工作的。
用户想要执行一个程序:
- 在command中输入该程序的可执行文件的名称
- command根据文件名找到可执行文件
- 将可执行文件中的程序加载入内存
- 设置CS:IP指向程序的入口
- command暂时停止运行,将CPU的控制权交给另一个程序
- 另一个程序运行完后,CPU控制权返回到command中,等待用户的输入。
4.9 程序执行过程的跟踪
command将程序加载入内存,CS:IP一指向程序的入口,command就放弃了CPU的控制权,直到程序结束,所以我们无法逐条指令看到程序的执行过程。
而Debug将程序加载入内存后并不放弃CPU的控制,单步执行程序。所以我们使用Debug来逐条执行指令,查看每一条指令的执行结果,跟踪执行过程。
debug a.exe,debug将程序从a.exe中加载入内存
程序被装入内存的位置:
(1) 程序加载后,ds中存放着程序所在内存区的段地址(SA),这个内存区的偏移地址为0,则程序所在的内存区的地址为ds:0;
(2) 这个内存区的前256个字节中存放的是PSP, DOS用来和程序进行通信。从256字节处向后的空间存放的是程序。
(3) 程序的物理地址是SAx16+0+256=(SA+16)x16+0,用段地址和偏移地址表示为:SA+10H:0。