第四章 第一个程序
# 第四章 第一个程序
# 4.1 一个源程序从写出到执行的过程
一个汇编语言程序从写出到最终执行主要经历三步:
第一步:编写汇编语言程序;
第二步:对源程序进行编译连接;
第三步:执行可执行文件中的程序。
对源程序进行编译连接生成可在操作系统中直接运行的可执行文件。可执行文件包含两部分内容。
- 程序(从源程序中的汇编指令翻译过来的机器码)和数据(源程序中定义的数据)
- 相关的描述信息(比如,程序有多大、要占用多少内存空间等)
# 4.2 源程序
一段简单的汇编语言源程序:
assume cs:codesg
codesg segment
mov ax, 0123H
mov bx, 0456H
add ax, bx
add ax, ax
mov ax, 4c00H
int 21H
codesg ends
end
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 1. 伪指令
在汇编语言源程序中,包括两种指令,一种是汇编指令,一种是伪指令。汇编指令是有对应的机器码的指令,可以被编译为机器指令,最终为 CPU 所执行。而伪指令没有对应的机器指令,最终不被 CPU 所执行。伪指令是由编译器来执行的指令,编译器根据伪指令来进行相应的编译工作。
下面介绍上面程序中所出现的几个伪指令:
(1) segment 和 ends
segment 和 ends 是一对成对使用的伪指令。功能是定义一个段。使用格式为:
段名 segment
.
.
段名 ends
2
3
4
2
3
4
一个汇编语言程序是由多个段组成的,这些段被用来存放代码(代码段)、数据(数据段)或当做栈空间(栈段)来使用。
(2)end
end 是一个汇编语言的结束标记,编译器在编译汇编程序的过程中,如果碰到了伪指令 end,就结束对源程序的编译。
(3)assume
这条伪指令的含义为 “假设”。它假设某一段寄存器和程序中的某一个用 segment...ends 定义的段相关联。即将定义的段的段地址存放在段寄存器中。
# 2. 源程序中的 “程序”
我们将源程序文件中的所有的内容称之为源程序,将源程序中最终由计算机执行、处理的指令或数据,称为程序。
# 3. 标号
汇编源程序中,除了汇编指令和伪指令外,还有一些标号,比如 “codesg”。一个标号指代了一个地址。比如 codesg 在 segment 的前面,作为一个段的名称,这个段的名称最终将被编译、连接程序处理为一个段的段地址。
# 4. 程序的结构
源程序就是由一些段组成的。我们可以在这些段中存放代码、数据、或将某个段当做栈空间。
# 5. 程序返回
一个程序结束后,将 CPU 控制权交还给使它得以运行的程序,我们称这个过程为程序返回。 程序返回指令:
mov ax, 4c00H
int 21H
2
2
当前我们不必理解这两天语句的含义。只要记住使用这两条指令可以实现程序返回。
段结束、程序结束、程序返回的区别
目的 | 相关指令 | 指令性质 | 指令执行者 |
---|---|---|---|
通知编译器一个段结束 | 段名 ends | 伪指令 | 编译时,由编译器执行 |
通知编译器程序结束 | end | 伪指令 | 编译时,由编译器执行 |
程序返回 | mov ax,4c00H int 21H | 汇编指令 | 执行时,由于 CPU 执行 |
# 6. 语法错误和逻辑错误
一般来说,程序在编译时被编译器发现的错误是语法错误,如 mov ss, 1234 。
在程序编译后,在运行时发生的错误是逻辑错误,如除零操作。
# 4.3 编辑源程序
源程序文件以.asm 作为后缀。
# 4.4 编译
我们使用微软的 masm5.0 汇编编译器进行编译,文件名为 masm.exe。我们以 c:\1.asm 为例说明编译源程序的方法步骤。
(1)进入 DOS 方式,运行 masm.exe。
...
...
Source filename [.ASM]: _
2
3
2
3
(2)输入要编译的源程序文件名,按 enter 键。
Source filename [.ASM]: c:\1.asm
Object filename [1.obj]:
2
2
输入要编译出的目标文件名,如果不输入则默认使用源程序名。
(3)确定了目标文件名称后,继续按 Enter 键(两次),忽略中间文件的生成。
最终完成对源程序的编译,生成目标文件。目标文件以.obj 作为后缀。
# 4.5 连接
在对源程序进行编译生成目标文件后,我们需要对目标文件进行连接,从而得到可执行文件。
这里我们使用微软的 Overlay Linker3.60 连接器,文件名为 link.exe。我们以 4.4 中生成的目标文件 c:\masm\1.obj 为例说明连接操作步骤。
(1)进入 DOS 方式,运行 link.exe。
...
...
Object Modules [.OBJ]: _
2
3
2
3
(2)输入目标文件名,按 enter 键。
...
...
Object Modules [.OBJ]: 1.obj
Run File [1.EXE]: _
2
3
4
2
3
4
键入生成可执行文件的名称。如果不输入则默认使用源程序名。
(3)确定了可执行文件名后,按 Enter 键(两次),忽略镜像文件的生成,忽略库文件的连接。
经过以上三步后,最终会生成以.exe 结尾的可执行文件。
连接的作用
(1)当源程序很大时,可以将它分为多个源程序文件来单独编译,然后将生成的目标文件连接在一起,节约程序编译时间。
(2)程序中调用了某个库文件的子程序,需要将这个库文件和该程序生成的目标文件连接在一起,生成一个可执行文件。
(3)一个源程序编译后,达到了存有机器码的目标文件,目标文件中的有些内容还不能直接用来生成可执行文件,连接程序将这些内容处理为最终的可执行信息。所以,在只有一个源程序文件,而又不需要调用某个库中的子程序的情况下,也必须用连接程序对目标文件进行处理,生成可执行 文件。
# 4.6 以简化的方式进行编译和连接
一键编译:
masm c:\1.asm;
一键连接:
link c:\1.obj;
# 4.7 1.exe 的执行
进入 dos 环境,直接键入.exe 可执行文件的文件名即可执行。
# 4.8 谁将可执行文件中的程序装载进入内存并使它运行
(1)在 DOS 中直接执行 1.exe 时,是正在运行的 command,将 1.exe 中的程序加载入内存;
(2)command 设置 CPU 的 CS:IP 指向程序的第一条指令(即程序入口),从而使程序得以运行。
(3)程序运行结束后,返回到 command 中,CPU 继续运行 command。
操作系统的外壳
操作系统是由多个功能模块组成的庞大、复杂的软件系统。任何通用的操作系统,都要提供一个称为 shell (外壳) 的程序,用户(操作人员)使用这个程序来操作计算机系统进行工作。
DOS 中有一个程序 command.exe,这个程序在 DOS 中称为命令解释器,也就是 DOS 系统的 shell。
DOS 启动时,先完成其他重要的初始化工作,然后运行 command.exe,command.exe 运行后,执行完其他的相关任务后,在屏幕上显示出由当前盘符和当前路径组成的提示符,比如:“c:\” 或 “c:\windows” 等,然后等待用户的输入。
用户可以输入要执行的命令,比如,cd、dir、type 等,这些命令由于 command 执行,command 执行完这些命令后,再次显示当前盘符和当前路径组成的提示符,等待用户输入。
如果用户要执行一个程序,则输入该程序可执行文件的名称,command 首先根据文件名找到可执行文件,然后将这个可执行文件中的程序加载入内存,设置 CS:IP 指向程序的入口。此后,command 暂停运行,CPU 运行程序。程序运行结束后,返回到 command 中,command 再次显示由当前盘符和当前路径组成的提示符,等待用户输入。
在 DOS 中,command 处理各种输入:命令或要执行的程序的文件名。我们就是通过 command 来进行工作的。
shell : 操作人员和 OS 之间的 API。
汇编程序从写出到执行的过程
到此,完成了一个汇编程序从写出到执行的全部过程。我们经历了这样一个历程: 编程(Edit) → 1.asm → 编译(masm) → 1.obj → 连接(link) → 1.exe → 加载(command) → 内存中的程序 → 运行(CPU)
# 4.9 程序执行过程的跟踪
DOS 系统中.exe 文件中程序的加载过程
(1)找到一段起始地址为 SA:0000(即起始地址的偏移地址为 0)的容量足够的空闲内存区;
(2)在这段内存区的前 256 个字节中,创建一个称为程序段前缀(PSP)的数据区,DOS 要利用 PSP 来和被加载程序进行通信;
(3)从这段内存区的 256 字节处开始(在 PSP 后面),将程序装入,程序的地址被设为 SA+10H:0;(空闲内存区从 SA:0 开始,0~255 字节为 PSP,从 256 字节处开始存放程序,为了更好地区分 PSP 和程序,DOS 一般将它们划分到不同的段中,所以有了这样的地址安排:
空闲内存区:SA:00
PSP 区:SA:0
程序区:SA+10:0
注意:PSP 和程序区虽然物理地址连续,却有着不同的段地址
)
(4)将该内存区的段地址存取 ds 中,初始化其它相关寄存器后,设置 CS:IP 指向程序入口。
程序加载进内存后,cx 中存放的是程序的长度,ds 存放着程序所在内存区的段地址,cs 存放可执行程序的段地址,ip 存放着可执行程序的偏移地址。
Debug 常用命令
我们使用 Debug 对程序的执行过程进行跟踪。
用 T 命令单步执行程序的每一条执行。
用 P 命令执行程序结束语句 int 21。
用 Q 命令退出 debug。