
为了能够彻底理解一个完整的程序,我们将经历一个漫长的过程。
一、一个源程序从写出到执行的过程

1.1 编写汇编源程序
使用文本编辑器用汇编语言编写汇编源程序,产生了一个存储源程序的文本文件。
1.2 对源程序进行编译连接
- 是用汇编语言编译程序对源程序文件中的源程序进行编译,产生目标文件;
- 再用连接程序对目标文件进行连接,生成可在操作系统中直接运行的可执行文件
可执行程序包含两部分内容

这一步工作的结果:产生了一个可在操作系统中运行的可执行文件。
1.3 执行可执行文件中的程序
早操作系统中,执行可执行文件中的程序
操作系统依照可执行文件中的描述信息,将可执行文件中的机器码和数据加载入内存,并进行相关的初始化(比如设置CS:IP指向第一条要执行的指令),然后由CPU执行程序
二、源程序
下面通过一段源程序来学习汇编语言
assume cs:codesgcodesg segmentmov ax,0123Hmov bx,0456Hadd ax,bxadd ax,axmov ax,4c00Hint 21Hcodesg endsend
2.1 伪指令
在汇编语言源程序中,包含两种指令
- 汇编指令
- 伪指令
汇编指令有对应的机器码的指令(之前看到的mov对应B8),可以被编译为机器指令,最终为CPU所执行。而伪指令没有对应的机器指令,最终不被CPU所执行。那么谁来执行伪指令呢?伪指令是由编译器来执行的指令,编译器根据伪指令来进行相关的编译工作。
在上面的程序中,出现了3种伪指令

2.1.1 伪指令segment .. end
; 下面定义了一个段,XXX是段名XXX segment; 段中的内容XXX ends
segment和ends是一对成对使用的伪指令,这是在写可被编译器编译的汇编程序时,必须要用到的一对伪指令。segment和ends的功能是定义一个段,segment说明一个段开始,ends表示段的结束,其中XXX就是这段的名字,向上面的例子中短命就是codesegment。
一个汇编程序是由多个段组成的,这些段被用来存放代码、数据或当作栈空间来使用。我们之前课程中学的段在汇编源程序中充分体现出来,一个程序中将所有被计算机所处理的信息:指令、数据和栈被划分到了不同的段中,一个汇编程序中必须有至少一个段。
2.1.2 伪指令end
end是一个汇编程序的结束标记,编译器在编译汇编程序的过程中,如果碰到了伪指令end就结束对源程序的编译。所以,在写完程序的时候,需要在结尾处加上伪指令end。
2.1.3 伪指令assume
这条伪指令的含义是:假设某一段寄存器和程序中的某一个segment ... ends定义的段相关联。
上面的源程序中,就是将代码段codesg和CS寄存器关联起来
assume cs:codesg
2.2 将源程序转成程序
- 源程序是指下图左边的的源程序文件
- 程序是指下图右边的可执行文件

2.3 例子
任务:编程运算,源程序中应该怎么写呢?
assume cs:codesgcodesg segmentmov ax,2add ax,axadd ax,axcodesg endsend
2.4 程序结束
我们的程序最先以汇编指令的形式存在源程序中,经编译、连接后转变为机器码,存储在可执行文件中,那么,他怎么运行起来呢?
一个程序P2在可执行文件中,必须有一个正在运行的程序P1,将P2从可执行文件中加载入内存中,将CPU的控制权交给P2,P2才能得以运行。P2开始运行后,P1暂停运行。
而当P2运行完毕后,应该将CPU的控制权交还给使它得以运行的程序P1,此后,P1继续运行。
现在,知道了一个程序结束后,将CPU的控制权交还给使它得以运行的程序,这个过程称为程序返回。那么,如何返回呢?应该在程序的末尾添加返回的程序段。
上面的源程序例子中,下面两条指令实现的功能就是程序返回
mov ax,4c00Hint 21H
现在知道的关于结束的几个概念

三、程序的编译、连接、运行
我们在学习汇编语言的过程中,需要用到以下的工具来运行一个完整的汇编程序,而这些工具都是在操作系统之上运行的程序,所以我们的学习过程必须在有操作系统的环境中进行。

关于这些工具的安装,前往另一篇教程中查看,那里有完整的示例程序,所以这里只给出一些知识点,但是这里仍然推荐按照书上的步骤重新运行一遍,书上有好多细节(比如,link连接目标文件时出现的Library为什么忽略之类的),而教程中的例子太粗糙了。
3.1 编译
在编译的过程中,我们提供了一个输入,即源程序文件。最多能够得到3个输出:
- 目标文件(.obj)
- 列表文件(.lst)
- 交叉引用文件(.crf)
这3个输出文件中,目标文件是我们最终要得到的结果,而另外两个是中间结果。
3.2 连接
连接的作用都有什么?
- 当源程序很大时,可以将它分为多个源程序文件来编译,每个源程序编译成目标文件后,再用连接程序将它们连接到一起,生成一个可执行文件
- 程序中调用了某个库文件中的子程序,需要将这个库文件和该程序生成的目标文件连接到一起,生成一个可执行文件
- 一个源程序编译后,得到了存有机器码的目标文件,目标文件中的有些内容还不能直接用来生成可执行文件,连接程序将这些内容处理为最终的可执行信息。所以,在只有一个源程序文件,而又不需要调用某个库中的子程序的情况下,也必须使用连接程序对目标文件进行处理,生成可执行文件。
四、思考
之前学习过,在DOS中,可执行文件中的程序P1若要运行,必须有一个正在运行的程序P2,将P1从可执行文件中加载入内存,将控制权交给CPU,P1才能得以运行;当P1运行完毕以后,将CPU的控制权交还给P2.
问题1
在之前的例子中,P2是谁?
问题2
P1程序运行结束后,返回到了那里?
提示、答案
要回答上述两个问题,就需要对DOS有比较深入的了解,阅读以下内容
问题1的答案:是正在运行的command将P1的程序加载入内存,command设置CPU的CS : IP指向程序的第一条指令(即程序的入口),从而使程序得以运行
问题2的答案:程序返回结束后,返回到command中,CPU继续执行command
汇编程序从写出到执行的过程:

五、程序执行过程的跟踪
我们写的程序在逻辑上不一定总是正确的,对于简单的错误,仔细检查源程序就可以发现;对于隐藏较深的错误,就必须对程序的执行过程进行跟踪分析才能发现。而Debug是用来跟踪一个程序的运行过程的
我们知道,在DOS运行一个程序的时候,是由command将程序可执行文件中加载入内存,并使其得以执行。但是,这样我们就不能逐条指令看到程序的执行过程,因为command的程序加载,设置CS:IP指向程序入口的操作是连续完成的,而当CS:IP一指向程序的入口,command就放弃了CPU的控制权,CPU立即开始运行程序,直至程序结束。
输入,debug testass.exe后,按下Enter键,Debug将程序从testass.exe中加载入内存,进行相关初始化后设置CS:IP指向程序的入口。
5.1 r命令
通过r命令可以查看各个寄存器的设置情况。

Debug程序将可执行文件加载入内存后,cx中存放的是程序的长度为30个字节(程序机器码一共有30个,对应1E)
上述文件的载入过程如下

那么,我们的程序被装入内存的什么地方?如何得知?
- 程序加载后,ds中存放着所在内存区的段地址,这个内存区的偏移地址为0,则程序所在的内存区的地址为:ds:0
- 这个内存区的前256个字节中存放的是PSP,DOS用来和程序进行通信。从256字节处往后的空间存放的是程序。
所以,从ds中可以得到PSP的段地址SA,PSP的偏移地址为0,则物理地址为
因为PSP占256(100H)字节,所以程序的物理地址为:
%5Ctimes%2016%2B0%20%3D%20(%5Ctext%7BSA%7D%2B10%5Ctext%7BH%7D)%5Ctimes%2016%2B0%0A#card=math&code=%5Ctext%7B%E7%A8%8B%E5%BA%8F%E7%9A%84%E7%89%A9%E7%90%86%E5%9C%B0%E5%9D%80%3A%20SA%7D%5Ctimes%2016%2B0%2B256%3D%5Ctext%7BSA%7D%5Ctimes16%2B0%2B16%5Ctimes%2016%3D%28%5Ctext%7BSA%7D%2B16%29%5Ctimes%2016%2B0%20%3D%20%28%5Ctext%7BSA%7D%2B10%5Ctext%7BH%7D%29%5Ctimes%2016%2B0%0A&id=SB8kP)
可用段地址和偏移地址表示为:SA+10H : 0
上图中的DS=075A,所以数据段的地址为075A : 0,程序的地址为**(075A+10):0**即为**076A:0**
在上述程序中,CS=076A, IP=0000,CS:IP指向程序的第一条指令,正好和上面我们算出来的结果是一样的。

5.2 u命令
使用u命令看一下命令生成的机器码

5.3 t命令
使用t命令单步执行程序中的每一条指令,并根据每条指令的执行结果,到了INT 21

5.4 p命令
到达int 21后,使用p命令运行(为什么使用p命令,不需要知道,记住就行),之后程序正常结束。

这里是Debug将程序加载入内存,所以程序运行结束回到Debug中
5.5 q命令
使用1命令退出Debug,回到command中

六、实验三
6.1 实验1
将下面的程序保存为t1.asm,将其生成可执行文件t1.exe
assume cs:codesgcodesg segmentmov ax,2000Hmov ss,axmov sp,0add sp,10pop axpop bxpush axpush bxpop axpop bxmov ax,4c00Hint 21Hcodesg endsend
6.2 实验2
用Debug跟踪t1.exe的执行过程,写出每一步执行后,相关寄存器中的内容和栈顶的内容。
6.3 实验3
PSP的头两个字节是CD 20,用Debug加载t1.exe,查看PSP的内容

