4.1 一个源程序从写出到执行的过程
一个汇编语言程序从写出到最终执行的简要过程:
- 使用文本编辑器编写汇编程序
- 对源程序进行编译,生成目标文件
- 进行连接,生成可执行文件
可执行文件包含两部分内容:
- 程序。从汇编指令汇编翻译过来的机器码和数据(源程序中定义的数据)
- 相关的描述信息。指程序大小,要占用多少内存空间等的信息
在操作系统中按照可执行文件的描述信息,将机器码和数据载入内存,并进行相关的初始化(比如设置CS:IP指向第一条指令等)。然后CPU执行程序。
4.2 源程序
assume cs:codeseg
codeseg segment
mov ax,0123H
mov bx,0456H
add ax,bx
add ax,ax
mov ax,4c00H
int 21H
codeseg ends
end
1. 伪指令
汇编语言中,包含以下两种指令:
- 汇编指令。有对应的机器码,可以被编译为机器指令,最终被CPU执行。
- 伪指令。没有对应的机器码,不被CPU执行,由编译器来执行
(1)segment ... ends
- 必须成对使用,功能是定义了一个段
- 必须有一个名称来标识,程序4.1中的"codeseg"就是一个段名
一个汇编程序至少要有一个段。这些段可以被用来存放代码、数据或者当做栈空间。
程序4.1中的codeseg段里的汇编指令,就是其存放的内容,这是一个代码段。
(2)end
汇编程序的结束标记。没有end
,编译器将不知道程序如何结束。
注意区分end
和ends
。ends
是和segment
成对使用的,标记一个段的结束。可以这样记忆,ends=end+segment
。而end
是标记整个程序的结束
(3)assume
将段寄存器和某一个具体的段相联系,现在只要记住这条指令即可。
例如程序4.1中,assume cs:codesg
将用作代码段的codesg和CPU中的段寄存器CS
联系了起来。
2. 源程序中的“程序”
以后,我们将源程序文件中的所有内容称为源程序。将源程序中最终由计算机执行处理的指令或数据,称为程序。
程序最先以汇编指令的形式存储在源程序中,经计算机编译、连接后转变为机器码,存储在可执行文件中。如图所示:
3. 标号
在源程序中,除了汇编指令和伪指令外,还有一些标号,比如“codeseg”。一个标号指代一个地址,这个段名称最终将被编译、连接程序处理为一个段的段地址。
4. 程序的结构
对于一些简短的程序,我们可以直接在Debug中写入来执行。但是对于一些大的程序,我们就需要像平常写程序一样,写在文件中。自然而然的,这样的源程序也应该具有一定的结构。
我们通过编写运算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
mov ax,ax
mov ax,ax
abc ends
end
(4)将abc代码段和cs联系起来(并非必须这样做)
assume cs:abc
abc segment
mov ax,2
add ax,ax
add ax,ax
abc ends
end
程序中有个错误,我们下一小节中会讲到。
5. 程序返回
为什么要有程序返回呢?我相信大家在学习C语言的时候已经大致了解了,这里在举一个例子:
我们写的程序P2需要执行,那么当然需要一个程序P1来帮他载入内存,当我们的P2运行完后,也理应把CPU的控制权返还给P1,因为P1可能还有其他任务。这个过程,称为程序返回。
那么如何返回呢:
mov ax,4c00H
int 21H
这两条指令实现的就是程序返回。我们暂时不需要了解为什么这两条指令可以实现程序返回,先记住即可,就像我们知道C程序中必要要有return
一样(特殊情况除外)。
至此,我们学习了很多个有关“结束”的语句,我们这里来总结下:
6. 语法错误和逻辑错误
我们写完程序后,会先进行编译,编译阶段一般都会出现一些错误,这些错误一般叫做语法错误,这是编译器帮我们找出来的。而另一个逻辑错误, 则是运行时出现的错误,比如我们写了一个死循环,没有给定义的变量初始化等等。
程序4.1因为没有返回语句,因此在运行时会出现一些错苏,但是对于编译器来说确实正确的。我们将它改成正确的:
assume cs:abc
abc segment
mov ax,2
add ax,ax
add ax,ax
mov ax,4c00H
int 21H
abc ends
end
4.3 编辑源程序
在一分钟!在VSCode中使用MASM/TASM搭建汇编环境中,教大家使用vscode来编写、编译、连接和运行汇编程序。为了更好的了解整个过程,我们只是用vscode来编写,编译连接和运行我们都将手动来执行。
在vscode中编写如写程序:
assume cs:codeseg
codeseg segment
mov ax,0123h
mov bx,0456h
add ax,bx
add ax,ax
mov ax,4c00h
int 21h
codeseg ends
end
4.4 编译
在一分钟!在VSCode中使用MASM/TASM搭建汇编环境已经说明如何编译,连接和运行汇编程序,对详细过程感兴趣的同学可以参考书中讲解,没有什么技术细节,因此这里不再赘述。下同。
4.5 连接
略
4.6 以简化的方式进行编译和连接
略
4.7 执行
略
4.8 谁将可执行文件中的程序装载进入内存并使它运行?
问
- 4.2的5中我们说到,我们要想运行我们的P2程序,那么需要一个正在运行的P1程序来载入,那么这个P1是什么?
- 我们的P2被P1载入内存后,如何得以运行?
- P2结束后,程序返回到哪里?
答
- 操作系统是由多个模块组成的软件系统。任何通用的OS,都需要提供一个被称作shell(外壳)的程序,使得操作人员可以通过这个程序来操作计算机。
- 在DOS中,这个shell的程序为
command.com
,也叫作命令解释器。DOS启动时,在完成初始化工作后,运行command,就会在屏幕上显示出当前的盘符和当前路径组成的提示符,等待用户的输入。(问题1) - 用户要执行一个程序,比如我们的P2,那么输入该程序的可执行文件的名称后,command找到这个可执行文件后,载入内存,并设置CS:IP指向程序的入口。command暂停运行,CPU开始运行程序P2。(问题2)
- 程序运行结束后,返回到command中,继续等待用户输入。(问题3)
4.9 程序执行过程的跟踪
程序执行的时候,由command将程序加载到内存中,并设置CS:IP指向程序的入口,command放弃了对CPU的控制权,执行过程是连续的,无法逐条查看指令的运行过程。
但是我们可以通过Debug功能来完成来完成这一操作:Debug不放弃对Debug的控制。
- 程序被载入内存的过程
(1)首先在内存中找到足够的空闲内存区M,其地址为SA:0
,即偏移地址为0,地址存放在了DS
中。
(2)在M的前256个字节(0-255)中,创建一个程序段前缀(PSP),DOS利用这个PSP和被加载的程序进行统信。
(3)可以知道程序的物理地址为:SA x 16 + 0 + 256 = SA x 16 + 16 x 16 = (SA+16) x 16 + 0,即SA + 10H : 0。
(4)输入R命令查看寄存器的状态
图中可以看到,DS=075C,即空闲内存的段地址,那么PSP的地址则为075C:0。那么程序的地址就是075C+10H:0,即076C:0。也是CS:IP所指向的地方。
- Debug过程
(1)使用U命令查看载入的指令:
(2)就像我们之前学习的那样,使用T命令来单步执行指令,并观察各寄存器的状态。但是,到了int 21H
时,要使用P命令来执行(不必考虑为什么,记住即可)。
(3)我们之前说,程序返回后是返回到command中,因为command将我们的程序载入到了内存中。但是这里不同,这里是Debug将我们的程序载入内存的,因此程序返回是返回到了Debug中。
(4)Q命令退出Debug,返回到conmmand中。
这一章有很多“先记住,暂时不必深究”的地方。以我自身的经验来看,确实不必深究,暂时记住即可。因为我认为学习计算机的相关知识,是需要投身到实践中去的,在我们的知识储备还不够的情况下,一味地深究一些较为深层的知识,反而会在深究之后的“不理解”“不明白”“越究越多”中浇灭了自己学习的热情,打乱了自己的学习计划和方向。因此,我的看法是先大致学习,然后深入项目,最后通过项目再反过头来深究相关内容和知识。