文章目录[隐藏]
我们将开始编写完整的汇编语言程序,用编译和连接程序将它们编译连接成为可执行文件(如*.exe文件),在操作系统中运程序。这一章中,我们将编写第一个这样的
一个源程序从写出到执行的过程
第一步:编写汇编源程序。
使用文本编辑器(如Edit.记事本等),用汇编语言编写汇编源程序。
这一步工作的结果是产生了4个存储源程序的文本文件。
第二步:对源程序进行编译连接
。
使用汇编语言编译程序对源程序文件中的源程序进行编译,产生目标文件;再用连接程序对目标文件进行连接,生成可在操作系统中直接运行的可执行文件。
可执行文件包含两部分内容。
- (1)程序(从源程序中的汇编指令翻译过来的机器码)和数据(源程序中定义的数据)
- (2)相关的描述信息(比如,程序有多大、要占用多少内存空间等)
这一步工作的结果:产生了一个可在操作系统中运行的可执行文件。
第三步:执行可执行文件中的程序。
在操作系统中,执行可执行文件中的程序。
操作系统依照可执行文件中的描述信息,将可执行文件中的机器码和数据加载入内存,并进行相关的初始化(比如设置CS:IP指向第一条要执行的指令),然后由CPU执行程序。
汇编用资源
DOSBox、debug、masm等汇编工具(点击下载)
简单的源程序
assume cs:codesg codesg segmend mov ax,0123H mov bx,0456H add ax,bx add bx,bx mov ax,4c00H int 21H codesg ends end
下面对源程序说明:
一、伪指令
在汇编语言源程序中,包含两种指令,一种是汇编指令
,一种是伪指令
。汇编指令是有对应的机器码的指令,可以被编译为机器指令,最终为CPU所执行。而伪指令没有对应的机器指令,最终不被CPU所执行。那么谁来执行伪指令呢?伪指令是由编译器来执行的指令,编译器根据伪指令来进行相关的编译工作。
三种伪指令
(1)XXX segment
XXX segment : : : XXX ends
segment和ends是一对成对使用的伪指令,在编辑文本时必须用到。
segment和ends的功能是定义一个段(这个段可以是代码段也可以是数据段),segment说明一个段的开始,ends说明一个段的结束。
段必须有段名来标识:
一个段必须有一个名称来标识,使用格式为:
段名 segment : 段名 ends
codesg segment ;定义一个段,段的名称为“codesg",这个段从此开始 : codesg ends ;名称为“codesg”的段到此结束、
一个汇编程序是由多个段组成的,这些段被用来存放代码、数据或当作栈空间来使用。我们在前面的课程中所讲解的段的概念,在汇编源程序中得到了应用与体现,一个源程序中所有将被计算机所处理的信息:指令、数据、栈,被划分到了不同的段中。
一个有意义的汇编程序中至少要有一个段,这个段用来存放代码。 我们可以看到,程序中,在codesg segment和codesg ends之间写的汇编指令是这个段中存放的内容,这是一个代码段(其中还有我们不认识的指令,后面会进行讲解)。
(2) end
end是一个汇编程序的结束标记,编译器在编译的过程中,如果碰到了伪指令end,就结束对原程序的编译。
end是一个汇编程序的结束标记,编译器在编译汇编程序的过程中,如果碰到了伪指令end,就结束对源程序的编译。所以,在我们写程序的时候,如果程序写完了,要在结尾处加上伪指令end。否则,编译器在编译程序时,无法知道程序在何处结束。
注意,不要搞混了end和ends. ends是和segment成对使用的
,标记一个段的结束,ends的含义可理解为“end segment"。我们这里讲的end的作用是标记整个程序的结束。
(3)assume
assume的含义为“假设”。它假设某一段寄存器和程序中的某一个segment…ends定义的段相关联,在有需要的情况下,编译程序可以将某一段寄存器和程序中的某一个segment…ends定义的段相关联。
assume 具体作用:
要用assume把段跟段bai寄存器对应起来的原因du是原来的DOS找到的空闲zhi内存的地址不是固定的,dao无法找到一个地址在任何时候都是空闲的。于是DOS需要可以重定位的程序,而当时的定位方式就是设置段寄存器的值使该程序能在可分配(空闲)的内存中可用。那就需要知道某个段被重定位时候需要修改哪个段寄存器的值才能正确执行。assume提供这种段和重定位代码时需要对应修改的寄存器的关系给编译器,编译器再这个信息写到二进制文件中去。比如DOS下的exe程序记录在文件头中。
这条伪指令的含义为“假设”。它假设某一段寄存器和程序中的某一个用segment...ends定义的段相关联。通过assume说明这种关联,在需要的情况下,编译程序可以将段寄存器和某一个具体的段相联系。assume并不是一条非要深入理解不可的伪指令,以后我们编程时,记着用assume将有特定用途的段和相关的段寄存器关联起来即可。
比如,在程序中,我们用codesg segment…codesg ends定义了一个名为codseg的段,在这个段中存放代码,所以这个段是一个代码段。在程序的开头,用assume cs:codesg将用作代码段的段codesg和CPU中的段寄存器cs联系起来。
二、源程序中的“程序”
用汇编语言写的源程序,包括伪指令和汇编指令,我们编程的最终目的是让计算机完成·定的任务。源程序中的汇编指令组成了最终由计算机执行的程序,而源程序中的伪指令是山编译器来处理的,它们并不实现我们编程的最终目的。这里所说的程序就是指源程序中最终山计算机执行、处理的指令或数据。
注意,以后叮以将源程序文件中的所有内容称为源程序,将源程序中最终由计算机执行、处理的指令或数据,称为程序。程序最先以汇编指令的形式存八源程序中,经编泽、连接后转变为机器码,存储在l一丁执行文件中。这个过程如图4.2所示。
三、标号
汇编源程序中,除了汇编指令和伪指令外,还有一些标号。一个标号指代了一个地址。例如前面说的codesg,作为一个段的名称,这个段的名称最终被编译、连接程序处理为一个段的段地址。
四、程序的结构
1)定义一个段,设定这个段的名称end
2)在这个段中写入汇编指令
3)程序结束(end指令)
源程序是山一些段构成的。我们可以在这些段中存放代码、数据、或将某个段当作栈空间。我们现在来一步步地完成个小程序,从这个过程中体会一下汇编程序中的基本要素和汇编程序的简单框架。
任务:编程运算2^3源程序应该怎样来写呢?
(1)我们要定义一个段,名称为abc
abc segment abc ends
(2)在这个段中写入汇编指令,来实现我们的任务。
abc segment mov ax,2 add ax,ax add ax,ax abc ends
(3)然后,要指出程序在何处结束。
abc segment mov ax,2 add ax,ax add ax,ax abc ends end
(4) abc被当作代码段来用,所以,应该将abc和cs联系起来。(当然,对于这个程序,也不是非这样做不可。)
最终如下:
assume cs:abc abc segment mov ax,2 add ax,ax add ax,ax abc ends end
五、程序返回
我们的程序最先以汇编指令的形式存在源程序中,经编译、连接后转变为机器码,存储在可执行文件中,那么,它怎样得到运行呢?
下面,我们在DOS(一个单任务操作系统)的基础上,简单地讨论一下这个问题。
一个程序P2在可执行文件中,则必须有一个正在运行的程序P1,将P2从可执行文件中加载入内存后,将CPU的控制权交给P2, P2才能得以运行。P2开始运行后,P1暂
停运行。
而当P2运行完毕后,应该将CPU的控制权交还给使它得以运行的程序P1,此后,P1继续运行。
现在,我们知道,一个程序结束后,将CPU的控制权交还给使它得以运行的程序,我们称这个过程为:程序返回。那么,如何返回呢?应该在程序的末尾添加返回的程序段。
我们回过头来,看一下程序中的两条指令:
mov ax,4c00h int 21H
这两条指令所实现的功能就是程序返回。
在目前阶段,我们不必去理解int 21H指令的含义,和为什么要在这条指令的前面加上指令mov ax,4c00H。我们只要知道,在程序的末尾使用这两条指令就可以实现程序返回。
到目前为止,我们好像己经遇到了几个和结束相关的内容:段结束、程序结束、程序返回。表4.1展示了它们的区别。
六、语法逻辑错误
可见,程序4.2在运行时会引发一些问题,因为程序没有返回。当然,这个错误在编译的时候是不能表现出来的,也就是说,程序4.2对于编译器来说是正确的程序。
一般说来,程序在编译时被编译器发现的错误是语法错误,比如将程序4.2写成如下这样就会发生语法错误:
aume cs:abc abc segment add ax,2 add ax,ax add ax,ax end
显然,程序中有编译器不能识别的段到何处结束。acme,而且编译器在编译的过程中也无法知道abc 在源程序编译后,在运行时发生的错误是逻辑错误。语法错误容易发现,也容易解决。而逻辑错误通常不容易被发现。不过,程序4.2中的错误却显而易见,我们将它改正过来:
assume cs:abc abc segment mov ax,2 add ax,ax add ax,ax mov ax,4c00h int 21H abc ends end