美文网首页
第 4 章 第一个程序

第 4 章 第一个程序

作者: mynameishl | 来源:发表于2018-09-10 23:25 被阅读0次

    之前都是在Debug中写一些指令,在Debug中执行。现在我们将开始编写完整的汇编语言程序,用编译和连接程序将它们编译连接成为可执行文件(如*.exe文件),在操作系统中运行。
    1.一个源程序从写出到执行的过程
    下图描述了一个汇编语言从写出到最终执行的简要过程。具体说明如下。


    图片1.png

    第一步:编写汇编源程序
    使用文本编辑器(如Edit、记事本等),用汇编语言编写汇编源程序。
    这一步工作的结果是产生了一个存储源程序的文本文件。
    第二步:对源程序进行编译连接。
    使用汇编语言编译程序(对汇编语言编译的程序是用汇编语言写的)对源程序文件中的源程序进行编译,产生目标文件;再用连接程序对目标文件进行连接,生成可在操作系统中直接运行的可执行文件。
    可执行文件包含两部分内容。
    (1)程序(从源程序中的汇编指令翻译过来的机器码)和数据(源程序中定义的数据)
    (2)相关的描述信息(比如,程序有多大、要占用多少内存空间等)
    这一步工作的结果:产生了一个可在操作系统运行的可执行文件。
    第三步:执行可执行文件中的程序。
    在操作系统中,执行可执行文件中的程序。
    操作系统按照可执行文件中的描述信息,将可执行文件中的机器码和数据加载入内存,并进行相关的初始化(比如设置CS:IP指向第一条要执行的指令),然后由CPU执行程序。
    2.源程序
    下图就是一段简单的汇编语言源程序。


    图片2.png
    下面对程序进行说明。
    a.伪指令
    在汇编语言源程序中,包含两种指令,一种是汇编指令,一种是伪指令。汇编指令是有对应的机器码的指令,可以被编译为机器指令,最终为CPU所执行。而伪指令没有对应的机器指令,最终不被CPU所执行。那么谁来执行伪指令呢?伪指令是由编译器来执行的指令,编译器根据伪指令来进行相关的编译工作。
    程序4.1出现了3种伪指令。

    (1)XXX segment
    :
    :
    :
    XXX ends
    segment和ends是一对成对使用的伪指令,这是在写可被编译器编译的汇编程序时,必须要用到的一对伪指令。segment和ends的功能是定义一个段,segment说明一个段开始,ends说明一个段结束。一个段必须有一个名称来标识,使用格式为:
    段名 segment

    段名 ends
    比如,程序4.1中的:
    codesg segment;定义一个段,段的名称为“codesg”,这个段从此开始

    codesg ends;名称为“codesg”的段到此结束
    一个汇编程序由多个段组成的,这些段被用来存放代码、数据或当做栈空间来使用。一个源程序中所有将被计算机所处理的信息:指令、数据、栈,被划分到了不同的段中。
    一个有意义的汇编程序中至少要有一个段,这个段用来存放代码。
    程序4.1中,在codesg segment和codesg ends之间写的汇编指令是这个段中存放的内容,这是一个代码段。
    (2)end
    end是一个汇编程序结束的标记,编译器在编译汇编程序的过程中,如果碰到了伪指令end,就结束对源程序的编译。所以,在我们写程序的时候,如果程序写完了,要在结尾处加上伪指令end。否则,编译器在编译程序时,无法知道程序在何处结束。
    (3)assume
    这段为指令的含义为“假设”。它假设某一段寄存器和程序中的某一个用segment....ends定义的段相关联。
    比如,在程序4.1中,我们用codesg segment....coedsg ends定义了一个名为codesg的段,在这个段中存放代码,所以这个段是一个代码段。在程序的开头,用assume cs:codesg将用作代码段的段codesg和CPU中的段寄存器cs联系起来。
    b.源程序中的“程序”
    以后可以将源程序文件中的所有内容称为源程序,将源程序中最终由计算机执行、处理的指令或数据,称为程序。程序最先以汇编指令的形式存在源程序中,经编译、连接后转变为机器码,存储在可执行文件中。这个过程如图4.2所示。


    图片4.png
    c.标号
    汇编程序张,除了汇编指令和伪指令外,还有一些标号,比如“codesg”。一个标号指代了一个地址。比如codesg在segment的前面,作为一个段的名称,这个段的名称最终将被编译、连接程序处理为一个段的段地址。
    d.程序的结构
    e.程序返回
    我们的程序最先以汇编指令的形式存在源程序中,经编译、连接后转变为机器码,存储在可执行文件中,那么,它怎样得到运行呢?
    下面,我们在DOS(一个单任务操作系统)的基础上,简单地讨论一下这个问题。
    一个程序P2在可执行文件中,则必须有一个正在运行的程序P1,将P2从可执行文件中加载入内存后,将CPU的控制权交给P2,P2才能得以运行。P2开始运行后,P1暂停运行。
    而当P2运行完毕后,应该将CPU的控制权交还给使它得以运行的程序P1,此后,P1继续运行。
    现在,我们知道,一个程序结束后,将CPU的控制权交还给使它得以运行的程序,我们称这个过程为:程序返回。那么,如何返回呢?应该在程序的末尾添加返回的程序段。
    程序4.1中的两条指令:
    mov ax,4c00H
    int 21H
    这两条指令所实现的功能就是程序返回。
    f.语法错误和逻辑错误
    一般来说,程序在编译时被编译器发现的错误是语法错误(比如程序中出现的拼写错误等)。
    在源程序编译后,在运行时发生的错误是逻辑错误。
    注:类似CodeBlocks软件中的“编译”和“运行”按钮,编译之后提示的错误都是语法错误,而运行之后,与预计的结果不一样,则发生的是逻辑错误。
    3.编辑源程序
    汇编语言源程序的后缀名为asm。
    注:asm为assembly的缩写
    4.编译
    完成对源程序的编辑后,得到一个源程序文件1.asm。可以对其进行编译,生成包含机器代码的目标文件。
    在编译一个源程序之前首先要找到一个相应的编译器(对汇编源程序的编译要采用汇编编译器)。
    对源程序1.asm进行编译后,会得到一个新文件:1.obj

    注意:在编译的过程中,我们提供了一个输入,即源程序文件。最多可以得到3个输出:目标文件(.obj)、列表文件(.lst)、交叉引用文件(.crf),这3个输出文件中,目标文件是我们最终要得到的结果,而另外两个只是中间结果,可以让编译器忽略对它们的生成。
    5.连接
    在对源程序进行编译得到目标文件后,我们需要对目标文件进行连接,从而得到可执行文件。
    图4.13中,连接程序提示输入库文件的名称。库文件里面包含了一些可以调用的子程序,如果程序中调用了某个库文件中的子程序,就需要在连接的时候,将这个库文件和目标文件连接在一起,生成可执行文件。但是,在这个程序中没有掉用任何子程序,所以,这里忽略库文件名的输入,直接按Enter键即可。
    我们用汇编语言编程,就要用到编辑器(Edit)、编译器(masm)、连接器(link)、调试工具(Debug)等所有工具,而这些工具都是在操作系统之上运行的程序,所以我们的学习过程必须在操作系统的环境中进行。
    我们简单地将连接的作用,连接的作用有以下几个。
    (1)当源程序很大时,可以将它分为多个源程序文件来编译,每个源程序编译成为目标文件,再用连接程序将它们连接到一起,生成一个可执行文件
    (2)程序中调用了某个库文件中的子程序,需要将这个库文件和该程序生成的目标文件连接到一起,生成一个可执行文件
    (3)一个源程序编译后,得到了存有机器码的目标文件,目标文件中的有些内容还不能直接用来生成可执行文件,连接程序将这些内容处理为最终的可执行信息。所以,在只有一个源程序文件,而不需要调用某个库中的子程序的情况下,也必须用连接程序对目标文件进行处理,生成可执行文件。
    6.以简化的方式进行编译和连接
    7.1.exe的执行
    程序是执行了,只是从屏幕上不可能看到任何运行结果,因为,我们的程序根本没有向显示器输出任何信息。程序只是做了一些将数据送入寄存器和加法的操作,而这些事情,我们不可能从显示屏上看出来。程序执行完成后,返回,屏幕上再次出现操作系统的提示符。
    8.谁将可执行文件中的程序装载进入内存并使它运行?
    问题1:
    此时,有一个正在运行的程序将1.exe中的程序加载入内存,这个正在运行的程序是什么?它将程序加载入内存后,如何使程序得以运行?
    问题2:
    程序结束后,返回到哪里?
    操作系统的外壳
    在DOS中,command处理各种输入:命令或要执行的程序的文件名。我们就是通过command来进行工作的。
    现在回答问题1和问题2中所提出的问题。
    (1)在DOS中直接执行1.exe时,是正在运行的command,将1.exe中的程序加载入内存
    (2)command设置CPU的CS:IP指向程序的第一条指令(即程序的入口),从而使程序得以运行
    (3)程序运行结束后,返回到command中,CPU继续执行command


    图片5.png
    9.程序执行过程的跟踪
    可以用Debug来跟踪一个程序的运行过程,这通常是必须要做的工作。我们写的程序在逻辑上不一定总是正确的,对于简单的错误,仔细检查一下源程序就可以发现;而对于隐藏较深的错误,就必须对程序的执行过程进行跟踪分析才容易发现。
    现在我们知道,在DOS中运行一个程序的时候,是由command将程序从可执行文件中加载入内存,并使其得以执行。但是,这样我们不能逐条指令地看到程序的执行过程,因为command的程序加载,设置CS:IP指向程序的入口的操作是连续完成的,而当CS:IP一指向程序的入口,command就放弃了CPU的控制权,CPU立即开始运行程序,直到程序结束。
    为了观察程序的运行过程,可以使用Debug。Debug可以将程序加载入内存,设置CS:IP指向程序的入口,但Debug并不放弃对CPU的控制,这样,我们就可以使用Debug的相关命令来单步执行程序,查看每一条指令的执行结果。
    具体方法如图4.18所示。
    图片6.png
    在提示符后输入“debug 1.exe”,按enter键,Debug将程序从1.exe中加载入内存,进行相关的初始化后设置CS:IP指向程序的入口。
    接下来可以用R命令看一下各个寄存器的设置情况,如图4.19所示。
    图片7.png
    可以看到,Debug将程序从可执行文件加载入内存后,cx中存放的是程序的长度。
    现在程序已从1.exe中装入内存,接下来查看一下它的内容,可是我们查看哪里的内容呢?程序被装入内存的什么地方?我们如何得知?
    这里,需要讲解一下在DOS系统中.EXE文件中的程序的加载过程。图4.20简要地展示了这个过程。
    图片8.png
    从上图我们知道以下的信息。
    (1)程序加载后,ds中存放着程序所在内存区的段地址,这个内存区的偏移地址为0,则程序所在的内存区的地址为ds:0;
    (2)这个内存区的前256的字节中存放的是PSP,DOS用来和程序进行通信的。从256字节处向后的空间存放的是程序。
    注意:Debug中默认所有数据都用十六进制表示的
    需要注意的是,在DOS中运行程序时,是command将程序加载入内存,所以程序运行结束后返回到command中,而在这里是Debug将程序加载入内存,所以程序运行结束后要返回到Debug中。
    使用Q命令退出Debug,将返回到command中,因为Debug是由command加载运行的。在DOS中用“debug 1.exe”运行Debug对1.exe进行跟踪时,程序加载的顺序是:command加载Debug,Debug加载1.exe。返回的顺序是:从1.exe中的程序返回到Debug,从Debug返回到command。

    相关文章

      网友评论

          本文标题:第 4 章 第一个程序

          本文链接:https://www.haomeiwen.com/subject/lhojgftx.html