0 前言
- 知识回顾:前面几章我们学习了CPU中的寄存器及其工作原理、代码段数据段和栈段的含义及其原理、常见的一些汇编指令,此外也用debug程序进行了简单的实验。
- 现在我们将开始编写完整的汇编语言程序,用编译器将它们编译成为可执行文件(如:*.exe文件),在操作系统中运行。
1 源程序从编写到执行
1.1 第1步:编写汇编源程序
- 使用文本编辑器(如记事本、Nodepad++、UltraEdit等),用汇编语言编写汇编源程序。
- 结果:产生了一个存储源程序的文本文件。
1.2 第2步:对源程序进行编译连接
- 过程:
- 使用汇编语言编译程序(MASM.EXE)对源程序文件中的源程序进行编译,产生目标文件;
- 再用连接程序(LINK.EXE)对目标文件进行连接,生成可在操作系统中直接运行的可执行文件。
- 可执行文件中包含两部分内容:
- 程序(从原程序中的汇编指令翻译过来的机器码)和数据(源程序中定义的数据)
- 相关的描述信息(比如:程序有多大、要占多少内存空间等)
1.3 第3步:执行可执行文件中的程序
操作系统依照可执行文件中的描述信息,将可执行文件中的机器码和数据加载入内存,并进行相关的初始化(比如:设置CS:IP指向第一条要执行的指令),然后由CPU执行程序。
2 简单源程序示例及其组成
2.1 简单源程序示例
assume cs:codesg #在该程序中,codesg segment……codesg ends定义了一个名为codesg的段,在这个段中存放代码,是个代码段。
codesg segment
start: mov ax,0123H
mov bx,0456H
add ax,bx
add ax,bx
mov ax,4c00H
int 21H
codesg ends
end
2.2 伪指令
- 汇编指令与伪指令:
(1)汇编指令:有对应的机器码的指令,可以被编译为机器指令,最终为CPU所执行。
(2)伪指令:没有对应的机器码的指令,最终不被CPU所执行。
(3)谁来执行伪指令呢?伪指令是由编译器来执行的指令,编译器根据伪指令来进行相关的编译工作。
- segment和ends
(1)segment和ends是一对成对使用的伪指令,这是在写可被编译器编译的汇编程序时,必须要用到的一对伪指令。
(2)segment和ends的功能是定义一个段,segment说明一个段开始,ends 说明一个段结束。
(3)一个段必须有一个名称来标识,使用格式为:
段名 segment
段名 ends
- end
(1)end 是一个汇编程序的结束标记,编译器在编译汇编程序的过程中,如果碰到了伪指令 end,就结束对源程序的编译。
如果程序写完了,要在结尾处加上伪指令end 。否则,编译器在编译程序时,无法知道程序在何处结束。
(2)切记:不要搞混了end和ends。
- assume
(1)含义:“假设”。它假设某一段寄存器和程序中的某一个用 segment … ends 定义的段相关联。
(2)作用:通过assume说明这种关联,在需要的情况下 ,编译程序可以将段寄存器和某一个具体的段相联系。
2.3 源程序与程序
我们可以将源程序文件中的所有内容称为源程序,将源程序中最终由计算机执行处理的指令或数据 ,称为程序。
2.4 标号
- 一个标号指代了一个地址。
- 例如codesg:放在segment的前面,作为一个段的名称,这个段的名称最终将被编译、连接程序处理为一个段的段地址。
2.5 程序返回
- 定义:一个程序结束后,将CPU的控制权交还给使它得以运行的程序。
- 程序返回的理解:DOS是一个单任务操作系统。
(1)一个程序P2在可执行文件中,则必须有一个正在运行的程序P1,将P2从可执行文件中加载入内存后,将CPU的控制权交给P2,P2才能得以运行。P2开始运行后,P1暂停运行。
(2)而当P2运行完毕后,应该将CPU的控制权交还给使它得以运行的程序P1,此后,P1继续运行。
- 程序返回的实现:应该在程序的末尾添加返回的程序段。
mov ax,4c00H
int 21H
- 与程序结束相关的指令
目的 |
相关指令 |
指令性质 |
指令执行者 |
通知编译器一个段结束 |
段名 ends |
伪指令 |
编译时,由编译器执行 |
通知编译器程序结束 |
end |
伪指令 |
编译时,由编译器执行 |
程序返回 |
mov ax,4c00H int 21H |
汇编指令 |
执行时,由CPU执行 |
2.6 错误类型
- 主要包含语法错误和逻辑错误两种。
- 语法错误:在编译时,出现编译器无法理解的关键字或语句,容易发现。
- 逻辑错误:在运行时发生的错误,难以发现。
3 上机实验
3.1 编辑源程序
- 虚拟机中安装DOSBox,参考《汇编语言03】第2章 寄存器》
- 在本机打开txt文件编辑以下代码,并重命名为“1.asm”。
assume cs:codesg
codesg segment
start: mov ax,0123H
mov bx,0456H
add ax,bx
add ax,bx
mov ax,4c00H
int 21H
codesg ends
end
-
在DOSBox文件夹中新建一个文件夹,命名为“1”,将该文件复制黏贴到该文件夹下,如下图。
-
启动DOSBox程序,并输入命令mount c d:\DOSBox
挂载所需程序,其中d:\DOSBox
是文件夹存放的路径,c
是挂载到虚拟盘符为c,如下图所示表示挂载成功,输入命令c:
切换至虚拟符c。
-
输入命令dir
可以看到当前路径下的文件,可以看到挂载下的各个文件夹。
3.2 编译
3.2.1 概述
在编译过程中,我们提供了一个输入,即源程序文件。最多可以得到3个输出:目标文件(.obj)、列表文件(.lst)和交叉引用文件(.crf)。这3个输出文件中,目标文件是我们最终要得到的结果,另外两个只是中间结果,可以让编译器不生成,在本课程中不讨论这两类文件的作用。
3.2.2 编译操作
- 先启动masm程序,如果在该程序当前所在目录,则输入命令
masm
启动程序,如果不在该程序所在目录,则需要输入路径。
- 运行masm后,首先显示的实一些版本信息,然后提示输入将要被编译的源程序文件的名称,如上图。
(1)[,ASM]提示我们,默认的文件扩展名是asm,如果要输入其他后缀的文件,则需要完整输入文件名和后缀,如“1.txt”。
(2)要知名文件所在的路径,如果是在当前路径下,只需要输入文件名即可,如果在其他目录,则需要输入路径。
(3)在本程序中,要编译的文件不在masm当前目录,使用命令c:\1\1
指定要编译的文件路径,后缀名asm可以省略。
- 在上面步骤后,程序继续提示我们输入要编译出的目标文件名,目标文件是我们对一个源程序进行编译要得到的最终结果。
(1)屏幕提示[1.OBJ],是因为我们输入的源程序文件名为1.asm,则编译程序默认要输出的文件名为1.obj,直接按enter生成该文件。
(2)如果要在指定目录下生成指定文件,则可以输入“路径+文件名”的格式。
- 在上图中,编译程序提示输入列表文件的名称,这个文件是编译器将源程序编译为目标文件的过程中产生的中间结果,可以让编译器不生成该文件,直接按enter即可。
- 编程程序提示输入交叉引用文件的名称,这个文件同列表文件一样,是中间结果,直接按enter不生成。
- 可以看到编译过程没有出错。在当前文件夹下找到生成的1.obj文件。如果要让该文件在源程序所在文件夹中生成,则在生成该文件的步骤需要输入“路径+文件名”来指定。
3.2.3 简易的编译操作
像上面操作过程,如果我们无需生成各个中间文件,则可以使用简易的编译操作,直接输入命令 masm 路径+文件名;
,在后面以分好结束,按enter,编译器就会对源程序文件进行编译,在当前路径下生成目标文件,并在编译过程中自动忽略中间文件的生成。
3.3 连接
3.3.1 概述
- 连接:从目标文件(.obj)→可执行文件(.exe)的过程。
- 连接的作用有以下几个:
(1)当源程序很大时,可以将它分为多个源程序文件来编译,每个源程序编译成为目标文件后,再用连接程序将它们连接到一起,生成一个可执行文件;
(2)程序中调用了某个库文件中的子程序,需要将这个库文件和该程序生成的目标文件连接到一起,生成一个可执行文件;
(3)一个源程序编译后,得到了存有机器码的目标文件,目标文件中的有些内容还不能直接用来生成可执行文件,连接程序将这此内容处理为最终的可执行信息。
3.3.2 连接操作
- 启动link程序。LINK.EXE在当前目录下,直接输入命令
link.exe
启动该程序。
- 提示输入要被连接的目标文件名。因为编译生成的文件就在当前目录下,因此此处输入
1
即可指定该文件。如果是在其他文件夹,则需要补充路径信息,如果是其他后缀,则需要补充后缀信息,如“1.bin”。
- 程序提示输入要生成的可执行文件名,默认与obj文件同名,直接输入enter使用默认文件名。如果要指定在其他文件夹下生成,则需要补充路径信息。
- 提示输入映像文件名,这个中间结果,可以不生成,直接按enter。
- 程序提示指定库文件名,库文件里面包含了一些可以调用的子程序,如果程序中调用了某一个库文件中的子程序,就需要在连接的时候,将这个库文件和目标文件连接到一起,生成可执行文件。在这个示例文件中,没有调用任何子程序,忽略库文件名的输入,直接按enter。
- 可以看到成功生成可执行文件。
3.3.3 简易的连接操作
像上面操作过程,如果我们无需生成各个中间文件,则可以使用简易的连接操作,直接输入命令 link 路径+文件名;
,在后面以分好结束,按enter,连接程序就会对目标文件进行处理,在当前路径下生成可执行文件,并在过程中自动忽略中间文件的生成。
3.4 程序的执行与跟踪
3.4.1 操作系统的外壳
- 操作系统是由多个功能模块组成的庞大 、复杂的软件系统。任何通用的操作系统 ,都要提供一个称为shell(外壳)的程序 ,用户(操作人员)使用这个程序来操作计算机系统工作。
- DOS中有一个程序command.com ,这个程序在 DOS 中称为命令解释器,也就是DOS系统的shell。
- DOS启动时,先完成其他重要的初始化工作,然后运行command.com,运行后,执行完其他的相关任务后,在屏幕上显示出由当前盘符和当前路径组成的提示符,比如:“c:\”等,然后等待用户输入。
- 用户可以输入所需要执行的命令,如cd、dir、type等,这些命令由command执行,command执行完这些命令后,再次显示由当前盘符和当前路径组成的提示符,等待用户输入。
- 如果用户要执行一个程序,则输入该程序的可执行文件的名称,command首先根据文件名找到可执行文件,然后将这个可执行文件的程序载入内存,设置SS:SP指向程序的入口。此后,command暂停运行,CPU运行程序。程序运行结束后,返回到command中,command再次显示由当前盘符和当前路径组成的提示符,等待用户输入。
3.4.2 执行
双击所生成的可执行程序,在win7 64位系统上运行出现问题,设置了兼容性依然不行,将可执行程序复制到winXP中,可执行,打开后瞬间消失。
3.4.3 跟踪
- 为观察程序的运行过程,可以使用debug程序。debug可以将程序加载入内存,设置CS:IP指向程序的入口,但debug并不放弃对CPU的控制,这样,我们就可以使用debug的相关命令来单步执行程序,查看每一条指令的执行结果。
- 在本实验中,目前debug程序和可执行程序均位于当前目录下,可以使用命令
dubug 1.exe
,如果不是在当前目录下,则需要补充路径信息。
- 使用
R
命令来查看各个寄存器的设置情况。debug将程序从可执行文件加载入内存后,cx中存放的实程序的长度,“000F”表示该程序共有15个字节,结合前面章节内容,理解各个寄存器的含义。
- 从上图可以看出,CP:IP指向第一个指令,地址为
076A:0000
,指令为mov ax,0123
,使用debug命令U
查看其他命令。
- 使用debug命令
T
单步执行每一个命令,观察各寄存器变化。
- 到了
INT 21
指令,需要使用debug命令P
来执行,表示终止程序。
- 使用debug命令
Q
,退出debug,返回到command。
4 小结
- 学习汇编的目的,是通过用汇编语言进行编程而深度地理解计算机基层的基本工作机理,达到可以随心所欲地控制计算机的目的。
- 我们的编程活动,大都是直接对硬件进行的,我们系统直接对硬件进行编程,却又不希望用机器码编程,我们用汇编语言编程,就要用到编辑器(edit)、编译器(masm)、连接器(link)、调试工具(debug)等所有工具,这些攻击都是在操作系统之上运行的程序。
参考文献
- 《汇编语言(第3版) 》王爽著,主要参考书籍和学习路线。
百度云:https://pan.baidu.com/s/1TE1Egc0ZmeJfLP5zvamo0Q
提取码:3y72
- 《第一个程序01》小甲鱼零基础视频课程
- 《第一个程序02》小甲鱼零基础视频课程
- 《第一个程序03》小甲鱼零基础视频课程