一个汇编语言程序从写出到最终执行的简要过程如下:
即编写->编译连接->执行
源程序:源程序文件中的所有内容
程序:源程序中最终由计算机执行、处理的指令或数据
在汇编语言源程序中,包含两种指令,一种是汇编语言,一种是伪指令。汇编指令是有对应的机器码的指令,可以编译为机器指令,最终为CPU所执行。而伪指令没有对应的机器指令,它是由编译器来执行的指令
一个源程序中所有被计算机所处理的信息:指令、数据、栈,被划分到了不同的段中
一个有意义的汇编程序中至少有一个段,这个段用来存放代码
我们由一个简单的例子来介绍一个源程序的基本结构知识
首先,我们可以将例子中的指令分为汇编指令和伪指令
汇编指令:第5、6、7、8、10、11行
伪指令:第1、3、13、15行
伪指令分析:
(1)段名 segment ...... 段名 ends
segment和ends是一对成对使用的伪指令,它们的功能是定义一个段并且一个段必须有一个名称来标识
segment说明一个段开头,ends说明一个段结束
(2)end
end是一个汇编程序的结束标记,程序结尾处必须加上伪指令end,否则编译器在编译程序中无法知道程序在何处结束
(3)assume
assume的含义为“假设”,它假设某一段寄存器和程序中的某一个用segment...ends定义的段相关联,比如说我们可以将test这个代码段和CPU中的段寄存器cs联系起来
(4)标号
汇编源程序中,除了汇编指令和伪指令外,还有一些标号,比如说“test”。一个标号代指了一个地址。比如test在segment的前面,作为一个段的名称,这个段的名称最终将被编译、连接程序处理为一个段的段地址
(5)程序返回
一个程序结束后,将CPU的控制权交还给使他得以运行的程序,我们称这个过程为:程序返回
这两条指令所实现的功能就是程序返回
在DOS(一个单任务操作系统)的基础上,一个程序P2在可执行文件中,则必须有一个正在运行的程序P1,将P2从可执行文件中加载入内存后,将CPU的控制权交给P2,P2才能得以运行。P2开始运行后,P1暂停运行
而当P2运行完毕后,应该将CPU的控制权交还给P1,此后,P1继续运行
目的 | 相关指令 | 指令性质 | 指令执行者 |
通知编译器一个段结束 | 段名 ends | 伪指令 | 编译时,由编译器执行 |
通知编译器程序结束 | end | 伪指令 | 编译时,由编译器执行 |
程序返回 | mov ax,4c00H int 21H |
汇编指令 | 执行时,由CPU执行 |
(6)语法错误和逻辑错误
当一个源程序没有程序返回时,是不会在编译的时候是不会表现出来的
一般来说,程序在编译时被编译器发现的错误是语法错误
在源程序编译后,在运行时发生的错误是逻辑错误
可以使用任意的文本编辑器编辑源程序,只要最终将其存储为纯文本文件即可
在这里我们使用DOS下的Edit
将程序保存为文件demo01.asm后,退出Edit,结束对源程序的编辑
在完成对源程序的编辑后,得到一个源程序文件demo01.asm,然后我们将其进行编译,生成包含机器代码的目标文件
进入DOS后,运行masm
运行masm后,首先显示出一些版本信息,然后提示输入将要被编译的源程序文件名称+后缀(文件后缀为.asm的直接输入文件名即可)
注意:如果没有在同一级目录下,需输入该文件的路径
在编译过程中,我们提供一个输入,即源文件。最多可以得到三个输出:目标文件(.obj)、列表文件(.lst)、交叉引用文件(.crf)。除了目标文件的另外两个文件只是中间结果,我们可以忽略其生成
得到目标文件后,我们需要将目标文件进行连接,从而得到可执行文件
在DOS下运行link
输入一个目标文件后可再输入库文件(.lib),最多得到三个文件:可执行文件(.exe)、映像文件(.map)
映像文件(.map)是连接程序将目标文件连接为可执行文件过程中产生的中间结果,在此我们忽略此文件的生成
此程序没有调用任何子程序,所以我们也忽略库文件名的输入
注意:如果没有在同一级目录下,需输入该文件的路径
这个程序中出现了一个警告错误:“没有栈段”,我们在这里不予理会
如果我们在连接过程中出现错误,是不会生成可执行文件的
简化使用即masm或link后跟文件名与分号“ ; ”
直接在此目录下执行文件名即可
由于我们没有向显示器输出任何信息,所以我们并不能明显的看到程序的运行
DOS中有一个程序command.com,这个程序在DOS中称为命令解释器,也就是DOS的shell
DOS启动时,先完成其他重要的初始化工作,然后运行command.com,command.com运行后,执行其他的相关任务后,在屏幕上显示出由当前盘符和当前路径组成的提示符(如:“c:/”)来等待用户的输入
因此,执行可执行文件时,就是由command将可执行文件加载入内存中
在我们了解到执行可执行文件时,是由另一个程序将它加载入内存并使它获得CPU的控制权
因此我们可以使用Debug来将可执行文件加载入内存,需要注意的时Debug并不会放弃对CPU的控制权,因此我们就可以使用Debug的相关命令来单步执行程序
CX中存放的是程序的长度,其机器码共有15个字节
现在我们可以开始跟踪了,用T指令执行程序中的每一条指令,到了int 21,我们要用P命令执行
DOS下文件程序加载过程结构如下:
- 找到一段起始地址为SA:0的容量足够的空闲内存区
- 在这段内存区的前256字节中,创建一个称为程序段前缀(PSP)的数据区,DOS要利用PSP来和被加载程序来进行通行
0~255字节为PSP,从256字节处开始存放程序
为了区分PSP和程序,DOS一般将它们划分到不同的段中:
- 空闲内存区:SA:0
- PSP区:SA:0
- 程序区:SA+10H:0
所以,从ds中可以得到PSP的段地址SA,PSP的偏移地址为0,则物理地址为SA×16+0
因为PSP占据256(100H)字节,所以程序的物理地址为
SA×16+0+256=SA×16+16×16+0=(SA+16)×16+0,用段地址和偏移地址为SA+10H:0