下面以.EXE文件为例,介绍使用完整段定义格式书写的汇编语言源程序结构。.EXE文件是一种可执行文件。它是一个可重定位的装入模块,可以包含多个段。.EXE程序由文件头和程序本身的二进制代码两部分组成,文件头中含有装入程序,即把该程序装入内存时所需要的有关信息。
DOS把.EXE文件装入内存的过程简述如下:
①先为.EXE程序建立一个256B的程序段前缀PSP,PSP中包含可以被用户程序使用的DOS入口、DOS为自己所存储的信息、由DOS传递给用户程序的信息等。其中PSP:0处存放一条INT 20H指令,其功能是退出应用程序,释放所占内存并返回DOS。
②把文件头读入内存工作区。
③计算可执行模块的大小。
④计算装入的起始段地址。
⑤完成重定位。
⑦把控制权交给.EXE程序。
对段寄存器和指针寄存器的初始化情况如下:
CS:IP为主程序的入口地址(程序装入后执行的第一条指令地址);
SS为堆栈段的段基址,SP指向栈顶;
DS和ES指向PSP的段基址,以便用户能访问PSP中的信息。
下面以一个简单的程序为例,来说明汇编语言程序的一般结构。
例4-7 编程实现123+456→SUM单元的功能。要求使用.EXE文件结构。
汇编源程序1:
我们可以看到,该程序本身的功能通过行5~7、行17~19即可完成,其中行5~7定义数据并预留结果单元,行17~19完成相加并保存的功能。那么其他语句的作用是什么呢?以下逐一说明。
1.段定义伪指令
语句格式:段名 SEGMENT[定位方式][组合方式]['类别']
...
段名ENDS
功能:定义一个以SEGMENT伪指令开始,以ENDS伪指令结束,以“段名”命名的存储器段。
段名是为定义的段起的名字,它代表段的首地址。进行段定义时必须注意定义的段名与段定义结束时所用的段名一致。SEGMENT伪指令后面可带三个参数,对它们说明如下:
(1)定位方式
表示段起始边界的要求。可以是:
PARA:指定段的起始地址必须从小段边界开始,即段地址的最低一位的十六进制数位必须为0(该地址能被16整除)。为默认方式。
BYTE:该段可以从任何地址开始。
WORD:该段必须从字的边界开始,即段地址必须为偶数。
PAGE:该段必须从页的边界开始,即段地址的最低两个十六进制数位必须为0(该地址能被256整除)。
(2)组合方式
与‘类别’一起在进行模块程序连接时使用。可以是:
PUBLIC:连接时该段与其他同名、同‘类别’的段按顺序相邻地连接在一起。
COMMON:连接时该段与其他同名段有相同的起始地址,段的长度取各段中的最大长度。COMMON会产生段的覆盖。
AT表达式类型:使段的起始地址是表达式所计算出来的16位段地址。但它不能用来指定代码段。
MEMORY:指定该段应分配在所有其他被连接在一起的段的前面(在最高地址上),如果连接时有几个指定MEMORY的段,则遇到的第一段作为MEMORY段,其他段则作为COMMON段。
(3)类别(www.xing528.com)
段的“类别”是用单引号括起来的字符串,可以是任何合法的名称。连接时将“类别”相同的所有段(不一定同名)存放在连续的存储区中,先出现的在前,后出现的在后(但仍然是不同段),每个段都有自己的起始地址。
汇编源程序1中的行1和行3、行4和行8、行9和行22分别定义了堆栈段、数据段和代码段。其中,将堆栈段的组合类型指定为STACK,标志着该段为堆栈段。
2.假定伪指令
语句格式:ASSUME段寄存器:段名[,段寄存器:段名[,...]]
功能:该语句一般出现在代码段中,且在段的开始出现,用来设定段寄存器与段之间的对应关系。
使用SEGMENT伪指令并不能说明哪个段是代码段,那个段是数据段。通过ASSUME语句可以建立这种关系。如果一个段寄存器与NOTHING关联,则表示取消前面建立的对应关系。因为汇编程序必须要有代码段,所以一定要建立CS寄存器与数据段的关联,否则程序会出错。如果定义了堆栈段,那么也要建立SS寄存器与堆栈段的关联。对于数据段和附加段,可以用ASSUME语句建立它们与DS、ES的对应关系,也可以不用。如果使用,则其后语句访问这些段内的变量,均可直接使用段内寻址,如果不用,则必须加跨段前缀符来指定段。一般都使用。
需要说明的是:ASSUME语句只是将当前段的设置方案告诉宏汇编程序,但并没有将段首地址置入对应的段寄存器中。CS和SS的内容由系统自动设置,不用程序处理,但数据段和附加数据段则必须由用户程序将其段首地址置入DS和ES中。
汇编源程序1中的行11建立了CS寄存器与CODE段、DS寄存器与DATA段,SS寄存器与STSG段之间的关联,即制定了三个段分别为当前的代码段、数据段和堆栈段。
3.过程定义伪指令
语句格式:过程名 PROC属性
...
过程名 ENDP
过程名是子程序入口的符号地址,可以是任意合法的字符串。属性有近属性(NEAR)和远属性(FAR),段内调用的过程使用NEAR属性,段间调用的过程使用FAR属性。定义过程的伪指令PROC和ENDP总是成对出现。
过程是程序的一部分,通常也叫子程序。一个过程可以被其他程序调用,它的最后一条语句总是返回指令。过程调用和返回指令可用CALL和RET指令。
汇编源程序1中的行10和行21定义了MAIN过程,MAIN过程即主过程,程序由此开始执行,其类型必须定义为FAR。
4.程序结束伪指令
语句格式:END<表达式>
源程序结束伪指令是源程序的结束标志,汇编程序汇编到该伪指令结束,通常为源程序的最后一条语句。
其中表达式为可选项。表达式必须为一存储器地址,该地址为程序的启动地址,即该程序在计算机上运行时第一条被执行指令的地址。如果不带表达式,表明该程序模块不能单独运行,而作为子模块让其他程序调用。
汇编源程序1中的行23即为END伪指令,标志源程序的结束,MAIN是主程序的过程名,也表示程序开始执行的地址。
5.段寄存器的装填
从装入程序对段寄存器的初始化可以看到,DS和ES并没有指向用户自己的数据区,而是指向PSP的段基址。但在用户程序运行过程中,DS应指向用户程序自己的数据段,以便访问其中的内容,例如:例4-7中访问A、B和SUM变量。同理,ES也应设置为正确的位置。所以,编程人员应在程序中用指令为DS和ES寄存器赋值。因为立即数不能直接传送段寄存器,所以用以下语句为DS赋值:
于是DS就指向了用户自己的数据段。如果程序中用到了附加段,也要在程序中用同样的方法为它们赋值。
6.程序返回操作系统
汇编语言源程序中有两种方法能够让应用程序退出,返回DOS操作系统。
(1)利用PSP中的INT 20H返回DOS
汇编源程序1中的行10、行12~14、行20语句可以使程序执行完后正确返回DOS。即
INT 20H的功能是退出应用程序,释放所占内存并返回DOS。这是一种传统的返回DOS的方法。
(2)利用DOS的4CH系统功能调用返回DOS
目前返回DOS通常使用4CH系统功能调用,这种方法实现起来比较简单,可用以下语句实现:
上述程序段可改写为如下结构。
汇编源程序2:
关于系统功能调用将在后面的章节讲述。
这两种方法用户都可以使用。源程序1的结构使得读者对汇编语言编程及内部运行机制有更深入的了解,而源程序2使程序更简洁。目前使用较多的是源程序2的结构。但是这两种方法不能同时使用。如果采用第2种方法返回DOS,就不能采用第1种方法,否则程序不能正确运行。
免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。