寄存器(内存访问):
字单元:储存一个字型数据的单元,一个字型数据有16位,由两个连续的内存单元组成。字型数据也分为高位字节和低位字节。一般将字单元的起始地址作为字单元的名称。如n地址字单元。
DS寄存器以及字的传送:
我们可以用命令直接将一个内存单元中的内容送入到一个寄存器中,只需要给出那个内存单元的偏移地址。如:mov al,[0]
但是这样只给出了偏移地址,没有段地址。这里就要说到寄存器DS。当我们像上面那样操作时,CPU会自动把DS中的数据作为段地址。然后根据我们提供的偏移地址,找到那个内存单元,把里面的数据存入我们指定的寄存器中。注意:我们上面给的寄存器为al,这是一个八位的,所以我们用上面那个方法时,CPU也只会那个指定的内存单元中的数据。而如果我们写的是ax,CPU会将那个当成字型数据来处理,传送给ax的是一个16位的数据,是我们指定的那个内存单元以及下一个内存单元中的内容。
我们同样也可以把寄存器中的数据根据地址传入内存,如mov [0],ax 道理和上面一样。同理,指令add和指令sub也可以这样做。
栈:
栈其实也类似于一个数据段,只是它遵循一个先进后出,或者说后进先出的规则。类似于一个箱子,最上面的数据成为栈顶,最下面的成为栈底。我们可以划出一段内存,作为我们的栈,同样首地址也得是16的倍数。这个栈是空的,里面还没有存入数据,然后我们可以用指令push来向里面存入数据,如push ax 这便是将寄存器ax中的值压入了栈的最底部,但是,CPU是如何知道要将ax中的数据压到我们规定的那个栈的最底部的呢?这里我们又要用到两个寄存器,段寄存器 SS 和寄存器 SP 。这两个寄存器类似于CS和IP。SS中也是存放段地址,SP中也是存放偏移地址。当我们使用指令push时,CPU会将数据存入SS:IP指向的地址中,但要注意的是,在栈的运算中都是以字为单位的,也就是16位二进制数,两个内存单元。
所以使用指令push时,是这样的:
push指令,SP先减2,然后CPU再把数据压入SS:SP指向的那个字单元,SS:SP指向的是那个字单元的低位字节,也就是那个低位字节所代表的内存单元的地址。
所以如果我们要向一段空栈里面用push指令存入数据的话,我们可以把SP设置为栈底的偏移地址再加一。这样,当用push时,SP先减2,然后正好指向栈底的第一个字的地址。
还有一个是出栈的指令:POP。它也是根据SS:SP指向的地址来把数据输出栈的。但是它与push不同的是,它先把数据输出,然后再SP加2。用POP输出栈里面的数据后,这些数据在栈中就不存在了,但是在内存中还是存在。这是一个有点抽象的概念,就是说,用POP弹出数据后,如果修改SP再指向刚刚那个字单元的地址,对于栈来说,那里是已经没有数据了的·,但是在内存中,这些数据并没有出去,我们还可以用其他方法来使用这些数据。
我们划了一个栈段,从10000到10010。然后设置一些基本设置。
这样后,我们一直摁 t 就可以执行命令了。
我们可以看到最后ax,bx中的数据互换了,遵循了先进后出的原则。
push,pop指令后面接的也可以是寄存器,或者内存的偏移地址([...])。
指令E,A,U,D这种需要指定段地址的,段地址都可以用段寄存器来代替,此时表示的段地址就为段寄存器中的值。
Debug中执行修改寄存器ss中的 t 命令的下一条命令也会接着立刻执行。
源程序:
- 伪指令和汇编指令。源程序中有这两种指令,前者不被CPU执行,由编译器来执行。后者可以直接由计算机翻译成机器码然后执行。伪指令:segment和 ends是一对伪指令,要一起用,这一对指令定义了一个段,segment标志着开始ends标志着结束。而还有一个伪指令end标志着整个程序的结束。一个段还必须要有一个名称,写在segment的前面。 比如这样的。段里面写的都是汇编指令。最上面的伪指令assume是假设的意思,它假设某一segment.....ends段和某一段寄存器连起来。用法如上图。源程序包括汇编指令和伪指令,我们将里面的汇编指令成为程序。
- 标号。比如我们给段起的名字,这个名字最终将被编译,连接,成为这个段的地址。
- 程序返回。程序p2在可执行文件中,它要想被执行,就得先有一个p1程序把p2从可执行文件中加载进内存,然后把CPU的控制权交给p2,p2执行完后,再把控制权交给p1。 将CPU交还给使它得以运行的程序就是程序返回。程序返回由两条代码实现。
一个程序的诞生:
- 编写程序,可以文本编辑器,只要最终是纯文本文件就行。
- 连接,这里要用两个程序,一个汇编语言编译程序,一个连接程序。我们编写完源程序后,用汇编语言编译程序编译源程序生成目标文件,然后再用连接程序连接生成可执行文件。可执行文件可直接在操作系统中运行。
- 执行可执行文件
编译:
我们先用文本编辑器编写一个源程序:
扩展名改为.asm
我们在这里需要用到微软的masm5.0汇编语言编译软件。先到网上下载一个,照常解压安装。然后我们再打开DOSBox,找到我们下载的文件所在目录,然后进入masm模式
然后就出现了这样的。这里提示我们输入要编译的源文件名,源文件名默认为.asm扩展名,如果我们的文件的扩展名就是.asm那么就不用输入后缀名,如果不是就要输入完整的文件名。如果文件就在当前目录下,我们不用指明路径,如果不在,就要指明完整路径。
这里出现了新的一行,提示我们输入目标文件的名称,里面还给出了默认名称。如果我们直接摁enter,它就会在当前目录下生成这个默认名称的目标文件。当然我们还可以指定生成目标文件的路径。输入路径再输入我们的文件名,不用加后缀就可以了。
这里我们用它默认的。
这两个是在编译过程中产生的中间文件,我们不用管,然后再摁enter,结果:
出现错误了。
百度一下,有两种原因。一个是文件夹隐藏了后缀,也就是说你的完整的文件名可能是masm.asm.txt,我们可以把隐藏的选项去掉,然后再改一下文件名。还有一个就是在DOSBox中,只有MOUUNT过的盘才存在,所以可以看到我上面输的明明是D:\下面却成了C:\
所以嫌麻烦的话,直接把文件放到masm的目录下就行了,改正之后就是这样子的:
这里有两行说明,这里说明有0个错误,如果出现其中一种错误,我们都将得不到最后的目标文件。
连接:
我们上面已经将源程序编译得到了.obj目标文件,然后我们要进行连接得到.exe可执行文件。这里我们要用到MASM文件中的link程序,我们进入link程序:
显示了一些信息,提示我们输入要连接的文件名。默认文件扩展名为.obj 规则和上面一样。
也是一直摁enter就可以了,倒数第二行那个是我们这个文件连接的库文件,如果我们这个文件中的源程序有调用库文件里的某些子程序的话,我们就要把这个文件和我们的文件连接起来。因为这里我们没有调用,所以直接跳过。
然后下面说我们没有设置栈段,这里可以不理会这个错误。
但是却出现了这种错误,百度也没查到结果。然后自己看来看去,发现在当前目录下也有一个可执行文件叫masm,所以我把生成可执行文件的名字改了一下,可以了。
这样就好了,然后就可以运行我们的程序了。
但是,似乎....什么都没有发生....
但其实程序是运行了的,只是不会在显示屏上输出。
不过我们可以追踪我们的程序运行,在这之前,我们要先了解一些知识:
前面说到,我们要运行这个成绩,就要还有一个程序把我们这个程序加载进CPU,然后在我们这个程序运行完后,CPU的控制权交换给那个把我们程序加载进去的程序。那么,在DOSBox中,那个程序就是DOSBox的命令解释器,也是一个程序,叫command.com
当我们要执行一个程序,我们在DOSBox界面输入文件名,然后command根据文件名找到可执行文件,然后把它加载进内存,command设置CS:IP指向程序入口,command暂定运行,CPU运行程序,程序运行完后,CPU控制权返回command。
我们可以用Debug来把程序加载进内存,但是Debug并不放弃对CPU的控制权。所以我们可以用它来追踪程序的运行。
这里又要了解一些DOSBox中程序加载的过程:
所以程序的物理地址为:
然后我们可以用指令 debug 文件名,来运行并追踪程序:
我们用 r 指令和 u 指令查看寄存器和内存中的内容,这时CS:IP已经指向了我们的程序入口,并且一直到076A:000A都是我们的程序。一共有12个字节长,CX中存放的数据就是我们的程序长度。然后我们用 t 命令来让CPU执行命令。
我们到最后要执行 INT 21 时用了指令 p ,执行这个就要用指令p。
注意:我们执行完程序后,CPU控制权是返还给了Debug,我们要再使用 q 命令CPU控制权才返回给command。
我们创建目标文件和可执行文件其实都可以用简便方法:
直接在masm或link后面加上我们要编译或者要连接的文件路径以及名字就可以了。