第4章 第一个程序
4.1 一个源程序从写出到执行的过程
第一步:编写汇编源程序
结果:产生了一个存储源程序的文本文件。
使用文本编辑器,用汇编语言编写汇编源程序。
第二步:对源程序进行编译连接。
结果:产生了一个可在操作系统中运行的可执行文件。
1、过程:使用汇编语言编译程序对源程序文件中的源程序进行编译,产生目标文件;再用连接程序对目标文件进行连接,生成可在操作系统中直接运行的可执行文件。
2、可执行文件的组成:
(1)程序(从源程序中的汇编指令翻译过来的机器码)和数据(源程序中定义的数据);
(2)相关的描述信息(例如程序有多大、要占用多少内存空间等)。
第三步:执行可执行文件中的程序。
操作系统依照可执行文件中的描述信息,将可执行文件中的机器码和数据加载入内存,并进行相关的初始化(比如设置CS:IP指向第一条要执行的指令),然后由CPU执行程序。
4.2 源程序
// 程序4.1
assume cs:codesg // 伪指令:将用作代码段的段codesg与CPU种的段寄存器cs联系起来
codesg segment // 伪指令:定义一个段,段的名称为"codesg",这个段从此开始,是个代码段
// 5~11行为汇编指令:
mov ax, 0123H
mov bx, 0456H
add ax, bx
add ax, ax
// 程序返回:
mov ax, 4c00H
int 21H
codesg ends // 伪指令:名称为"codesg"的段到此结束
end // 伪指令:
4.2.1 伪指令
1、在汇编语言的源程序中包含两种指令:汇编指令、伪指令。
(1)汇编指令:有对应机器码的指令,可以被编译为机器指令,最终被CPU所执行。
(2)伪指令:没有对应的机器指令,最终不被CPU所执行;伪指令由编译器执行,编译器根据伪指令来进行相关的编译工作。
2、伪指令的种类:
第一种:segment…ends
segment和ends的功能是定义一个段,segment说明一个段开始,ends说明一个段结束。
段名 segment
...
段名 ends
(1)segment和ends是一对成对使用的伪指令。
(2)segment和ends是在写可被编译器编译的汇编程序时,必须用到的一对伪指令。
(3)一个汇编程序是由多个段组成的,这些段被用来存放代码、数据或当作栈空间来使用。
(4)一个有意义的汇编程序中至少要有一个代码段。
第二种:end
end是一个汇编程序的结束标记,编译器在编译汇编程序的过程中,如果碰到了伪指令end,就结束对源程序的编译。
(1)写完程序要在结尾处加上伪指令end。否则编译器不知道程序在何处结束。
(2)区分end和ends:ends与segment成对使用,标记一个段的结束,ends可以理解为end segment
;而end的作用是标记整个程序的结束。
第三种:assume
伪指令assume的含义是“假设”。它假设某一段寄存器和程序中的某一个用segment…ends定义的段相关联。
通过assume可以使编译程序将段寄存器和某一个具体的段相联系。
4.2.2 源程序中的“程序”
源程序中的“程序”指源程序中最终由计算机执行、处理的指令或数据。
源程序指源程序文件中的所有内容。
程序最先以汇编指令的形式存在源程序中,经编译、连接后转变为机器码,存储在可执行文件中,过程如图:
4.2.3 标号
一个标号指代了一个地址。
程序4.1中"codesg"就是一个标号,codesg在segment前面,作为一个段的名称,这个段的名称最终将被编译、连接程序处理为一个段的段地址。
4.2.4 程序的结构
// 程序4.2:计算2的立方
assume cs:abc // (4)abc被当作代码段来用,所以应该将abc和cs联系起来
abc segment // (1)定义一个段,名称为abc,与结尾abc ends搭配使用
// (2)在这个段中写入汇编指令
mov ax, 2
add ax, ax
add ax, ax
// (5)程序返回
mov ax, 4c00H
int 21H
abc ends
end // (3)指出程序在何处结束
4.2.5 程序返回
Q:程序如何得到运行?
1、程序运行过程:在DOS(一个单任务操作系统)的基础上讨论
(1)一个程序P2在可执行文件中,则必须有一个正在运行的程序P1,将P2从可执行文件中加载入内存后,将CPU的控制权交给P2,P2才能得以运行;
(2)P2开始运行后,P1暂停运行;
(3)P2运行完毕后,将CPU的控制权交还给P1;
(4)P1继续运行。
2、程序返回:指一个程序结束后,将CPU的控制权交还给使它得以运行的程序的过程。
3、应该在程序的末尾添加返回的程序段,是汇编指令。
// 程序返回指令举例,本章暂时只需要记忆这两条指令即可
mov ax, 4c00H
int 21H // 必须输入H才能通过,int可以理解为interrupt
4.2.6 语法错误与逻辑错误
1、语法错误:程序在编译时被编译器发现的错误。
2、逻辑错误:在源程序编译后,运行时发生的错误。
3、语法错误容易发现,容易解决;逻辑错误通常不容易被发现。
4.2.7 与结束相关的概念总结
目的 | 相关指令 | 指令性质 | 指令执行者 |
---|---|---|---|
通知编译器一个段结束 | 段名 ends | 伪指令 | 编译时,由编译器执行 |
通知编译器程序结束 | end | 伪指令 | 编译时,由编译器执行 |
程序返回 | mov ax,4c00H int 21H | 汇编指令 | 执行时,由CPU执行 |
4.3 编辑源程序
以程序4.1为例,在任意文本编辑器中编辑源程序,保存为1.asm
。
4.4 编译
4.4.1 Dosbox的配置
Win10下,在Dosbox的配置文件末尾添加如下代码:
MOUNT D D:\code\Assembly\Dosbox // 挂载到D盘
D: // 进入D盘
// D:\code\Assembly\Dosbox目录下应有debug.exe、masm.exe等工具
4.4.2 编译过程
1、使用Dosbox运行masm.exe
,显示出版本信息。
2、输入.asm
文件的名称(不输入扩展名),与masm.exe
同目录则只输入名称,不同目录则输入路径。
3、可选修改输出的目标文件的名称,不修改则直接按Enter。
4、两个生成中间文件的过程,按Enter跳过。
如果输入文件路径,也不需要后缀名。
如D:\code\Assembly\codes\1 不写1.asm
4.4.3 编译注意事项
1、有两类错误使我们不能得到所期望的目标文件:
(1)程序中有"Severe Errors";
(2)找不到所给出的源程序文件。
2、编译过程中,最多可以得到3个输出:目标文件(.obj
)、列表文件(.lst
)、交叉引用文件(.crf
)。
(1)目标文件使我们最终要得到的结果;
(2)列表文件、交叉引用文件是中间结果,可以让编译器忽略其生成,本课程不讨论这两类文件。
4.5 连接
4.5.1 连接的过程
1、使用Dosbox运行link.exe
。
2、输入要连接的.obj
文件名(不输入扩展名),与link.exe
同目录则只输入名称,不同目录则输入路径。
3、可选修改要得到的可执行文件的名称,不修改则直接Enter。
4、中间文件,可选择不生成,按Enter直接跳过。
5、提示输入库文件的名称,本程序没有调用库文件中的子程序,因此直接Enter键跳过。
库文件中包含了一些可以调用的子程序,若程序调用了库文件中的子程序,就要在连接的时候将这个库文件和目标文件连接到一起,生成可执行文件。
4.5.2 连接的作用
连接的最终结果:得到可执行文件。
1、当源程序很大时,可以分为多个源程序来编译,每个源程序编译成目标文件后,再用连接程序将其连接到一起,生成一个可执行文件。
2、若程序调用了库文件中的子程序,就要在连接的时候将这个库文件和目标文件连接到一起,生成可执行文件。
3、一个源程序编译后,得到了存有机器码的目标文件,目标文件中有些内容还不能直接用来生成可执行文件,连接程序将这些内容处理为最终的可执行信息。
4.6 以简化的方式进行编译和连接
1、以简化的方式进行编译1.asm
:结尾加上分号,如图所示,则自动忽略中间文件的生成,在当前目录下得到1.obj
。
2、以简化的方式进行连接1.obj
:结尾加上分号,如图所示,则自动忽略中间文件的生成,在当前目录下得到1.exe
。
4.7 1.exe的执行
程序运行了,但是在屏幕上看不到任何运行结果,是因为该程序(程序4.1)并不向显示器输出任何信息。
4.8 谁将可执行文件中的程序装载进入内存并使它运行?
操作系统的外壳: 任何通用的操作系统,都要提供一个称为 shell(外壳) 的程序,用户使用这个程序来操作计算机系统进行工作。DOS中有一个程序command.com(类似于cmd.exe)是DOS系统的shell。
1、在DOS中直接执行1.exe
时,是正在运行的command
将1.exe
的程序加载入内存。
2、command设置CPU的CS:IP指向程序的第一条指令(即程序的入口),从而使程序得以运行。
3、程序运行结束后,返回到command中,CPU继续运行command。
汇编程序从写出到执行的过程:编程(Edit)-
1.asm
-编译(masm)-1.obj
-连接(link)-1.exe
-加载(command)-内存中的程序-运行(CPU)。
4.9 程序执行过程的跟踪
Debug可以将程序加载入内存,设置CS:IP指向程序的入口,但Debug并不放弃对CPU的控制,因此可以使用Debug的相关命令来单步执行程序,查看每一条指令的执行结果。
4.9.1 Debug操作步骤
1、输入debug 1.exe
将1.exe
加载入内存,进行相关初始化后设置CS:IP指向程序的入口。
2、使用R命令查看各个寄存器的设置情况。
(1)cx中存放的是程序的长度。1.exe
中程序的机器码共有15个字节,因此cx为000FH。
(2)从ds中得到PSP(程序段前缀)的段地址SA,PSP[见4.9.2]的偏移地址为0,则物理地址为SA×16+0。
3、使用U命令查看其他指令。
4、使用T命令单步执行程序中的每一条指令,并观察每条指令的执行结果,到了int 21H
,要使用P命令执行。int 21H
执行后,显示出Program terminated normally
,返回到Debug中,表示程序正常结束。
5、使用Q命令退出Debug,将返回到command中,因为Debug是由command加载运行的。
4.9.2 DOS系统中.exe程序加载过程
1、程序加载后,ds中存放着程序所在内存区的段地址,这个内存区的偏移地址为0,则程序所在的内存区的地址为ds:0。
2、这个内存区的前256个字节中存放的是PSP(程序段前缀),DOS用来与程序进行通信。从256字节处向后的空间存放的是程序。
4.9.3 程序的加载与返回顺序
在DOS中用debug 1.exe
运行Debug对1.exe
进行跟踪时:
1、程序加载的顺序:command加载Debug,Debug加载1.exe
。
2、返回的顺序:从1.exe
中的程序返回到Debug,从Debug返回到command。