今天晚上的学习任务是用汇编写一个可执行程序,接下来开始吧
第一个程序
一个源程序从写出到执行的过程
编写汇编原程序
产生一个存储源程序的文本文件
对源程序进行编译连接
先使用汇编语言编译程序对源程序进行编译,产生目标文件;再用连接程序对目标程序进行连接,生成可在系统中运行的可执行程序
可执行程序包含两部分内容:
- 程序(从源程序中的汇编指令翻译过来的机器码)和数据(源程序中定义的数据)
- 相关的描述信息(比如,程序有多大,要占用多少内存空间)
执行可执行文件中的程序
在操作系统中,执行可执行文件中的程序
在这些步骤中,操作系统可以根据可执行文件中的描述信息,将可执行文件中的机器码和数据加载进入内存,并进行相关的初始化(比如设置CS:IP指向的第一条执行的指令),然后由CPU执行
源程序
我们以一段汇编语言源程序为例
1 | assume cs:codesg |
伪指令
在汇编语言源程序中包含两种指令。一是汇编指令,二是伪指令。
汇编指令是有对应的机器码的指令,可以被编译为机器指令,最终由CPU执行
而伪指令则是由编译器来执行的指令,编译器根据伪指令来进行相关的编译工作
我们先对这段程序中的三处伪代码进行说明:
1
2
3XXX segment
...
XXX ends
segment 和 ends是一对成对使用的伪指令,这是在写可被编译器编译的汇编程序是,必须要用到的一对伪指令
其功能是定义一个段,segment 用来定义一个段的开始,ends用来定义一个断的结束
通常一个源程序是由多个段组成的,一个程序中所有要被处理的信息:指令,数据,栈被划分到了不同的段中
1
end
end是汇编程序结束的标记,编译器在进行编译时,如果碰到了伪指令end就结束对源程序的编译
1 | assume cs:codesg |
这条伪指令的含义为”假设”。它假设某一段寄存器和程序中的某一个用
segments...ends
定义的段相关联
这一段程序的含义便是将 codesg段与 cs段寄存器相关联
程序返回
当一个程序结束后,将CPU的控制权交还给使它得以运行的程序,我们称这个过程为:程序返回
这两条指令实现的功能便是程序返回:
1 | mov ax,4c00H |
我们暂时无法理解这两句的含义,不必深究
编辑源程序
我们在edit程序中编辑程序

我们将其保存至C盘中为1.asm
编译
我们使用masm对其进行编译

我们一一分析这些信息的作用:
[.ASM]:
提示我们默认的文件拓展名是asm,当我们输入名称XXX便在当前目录下调用XXX.asm(如果要用其他拓展名则需输入完全)[1.OBJ]:
提示我们我们生成目标文件为1.obj ,我们可以在后面指定生成的路径,也可以用Enter跳过,使用当前文件夹[NUL.LST]:
提示输入列表名称,这个文件是编译器翻译源程序的过程中的中间结果,使用Enter跳过[NUL.CRF]:
提示输入交叉引用文件的名称,也是中间产物,跳过- 当出现下面的标志后,代表编译成功结束
连接
我们在得到目标文件后,需要对目标文件进行连接,从而得到可执行程序

我们接着分析这些信息:
[.OBJ]:
提示我们默认的文件拓展名是obj,当我们输入名称XXX便在当前目录下调用XXX.obj(如果要用其他拓展名则需输入完全)[1.EXE]:
提示我们我们生成目标文件为1.exe,我们可以在后面指定生成的路径,也可以用Enter跳过,使用当前文件夹[NUL.MAP]:
提示输入映像文件的名称,这个文件是目标文件生成可执行程序的中间结果,使用Enter跳过[.LIB]:
提示输入库文件的名称,库文件里面包含了一些可以调用的子程序,如果调用了某一个库文件的子程序,就需要在连接时,将这个库文件和目标文件连接在一起生成可执行程序,这里我们跳过- 最后显示出现了“没有栈段”的错误,我们直接忽视,此时连接成功
在下面我们简单的介绍以下连接的作用:
- 当源程序很大时可以分为多个源程序文件生成目标文件,最后再将目标文件连接到一起
- 程序中调用了某个库文件的子程序,需要将这个库文件和目标文件连接到一起生成可执行程序
- 一个源程序在编译后,得到了有机器码的目标文件,目标文件中的内容还不能直接生成可执行程序,所以需要连接程序处理
以简化的方式进行编译和连接

直接在命令后面加一个 ;
可以直接忽略中间产物的生成,实现快速的编译连接
程序执行过程的跟踪
汇编程序从写出到执行的过程:
1
2 编程 --> 1.asm --> 编译 --> 1.obj --> 连接 --> 1.exe --> 加载 --> 内存中的程序 --> 运行
(Edit) (masm) (Link) (command) (CPU)
我们先展示一下EXE文件中程序加载的过程:

我们可以根据这副图得到以下信息:
程序加载后ds中存放着程序所在内存区的段地址,这个内存区的偏移地址为0,则程序的内存区的地址为ds:0
这个内存区的前256个字节存放的是PSP,DOS用来用来和程序进行通信。从256个字节之后存放的是程序
因为PSP占256(100H)个字节,所以程序的物理地址是:
SA * 16 + 0 + 256 = SA * 16 + 16 * 16 + 0 = (SA + 16) * 16 + 0 = (SA + 10H) + 0
可以用段地址和偏移地址表示为 SA+10H:0
程序执行过程的跟踪
我们以刚刚的程序1.exe为例:

我们可以看到图中DS的值为075AH,则PSP的地址为075A:0 ,程序的地址为076A:0(即075A + 10:0)
同时可以看到从076A:0000~076A:000F都是我们的程序的机器码
现在我们开始单步执行跟踪:

当我们执行到
INT 21
时需要用P指令退出程序,最后再使用Q指令返回command