最近学了王爽教授写的《汇编语言》,整理一下学习笔记。
在写一个程序之前我们要先弄清程序的执行过程:
第一步:编写汇编源程序。
使用文本编辑器(如:记事本)用汇编语言编写程序。
第二步:对源程序进行编译链接。
用编译程序对原程序进行编译,产生目标文件:再用连接程序对目标文件进行连接,生成可在操作系统中直接运行的可执行文件。
可执行文件包含两部分内容:
(1)程序(由源程序翻译过来的机器码)和数据(源程序中定义的数据)
(2)相关的描述信息(比如,程序有多大、要占多少内存空间等)
第三步:执行可执行文件中的程序。
之前有提到过伪指令,今天来详细说一下,在汇编源程序中,包含两种指令,一种是汇编指令,一种是伪指令。汇编指令有对应的机器码,可以被编译为机器指令,最终被CPU执行。但是伪指令没有对应的机器码,不被CPU执行。伪指令是由编译器来执行的指令,编译器根据伪指令来进行相关的编译工作。
以下面的一段程序为例:
上面的程序中出现了3种伪指令:
(1)XXX segmnet······XXX ends
segment和ends是一对成对使用的伪指令,这是在写可被编译器编译的汇编程序时必须要用到的一个伪指令。segment和ends的功能是定义一个段,segment说明一个段开始,ends说明一个段结束。使用格式为:
段名 segment
· · ·
· · ·
· · ·
段名 ends
很显然“codesg”是这个段的名称,一个汇编程序是由多个段组成的,一个源程序中所有将被计算机处理的信息:指令、数据、栈,被划分到不同的段中。
(2)end
end 是一个汇编程序的结束标记,编译器在编译时,如果碰到了伪指令end,就结束对源程序的编译。
需要注意的是end和ends,这两个不要搞混了,ends是和segment成对使用的,是标记一个段的结束,而end是标记整个程序的结束。
(3)assume
assume,假设的意思。它假设某一段寄存器和程序中的某一个用segment···ends定义的段相关联。通过这种关联,在需要的情况下,编译程序可以将段寄存器和某一个具体的段相联系。
assume cs: codesg就是将代码段codesg和CPU中的段寄存器cs联系起来。
用汇编指令编写的源程序,包括伪指令和汇编指令。伪指令是由编译器来处理,它们并不实现我们编程的最终目的,而汇编指令组成了最终由计算机执行的程序。程序是指源程序中最终由计算机执行、处理的指令或数据,程序最先以汇编指令的形式存在源程序中,经过编译、连接后转变为机器码,存储在可执行文件中。过程如下图:
汇编源程序中,除了汇编指令和伪指令外,还有一些标号,比如上面说过的“codesg”。一个标号代表了一个地址,“codesg”作为一个段的名称,最终将被编译、连接程序处理为一个段的段地址。
简单的汇编程序的编写需要一些基本要素和程序框架。
(1)定义段,名称为XXX
XXX segment
XXX ends
(2)写入汇编指令
XXX segment
· · ·
· · ·
· · ·
XXX ends
(3)指明程序在何处结束
XXX segment
· · ·
· · ·
· · ·
XXX ends
end
(4)将XXX和CS联系起来。
assume cs: XXX
XXX segment
· · ·
· · ·
· · ·
XXX ends
end
程序经编译、连接后变为机器码,存储在可执行文件中,我们在DOS(一个单任务操作系统)的基础上来说一下它运行的过程。
一个程序A在可执行文件中,必须有一个正在运行的程序B,将A从可执行文件中加载入内存后,将CPU的控制权交给A,A得以运行。A开始运行后,B暂停运行。A运行完毕之后,将CPU的控制权交给B继续运行。
一个程序结束后,将CPU的控制权交还给使它得以运行的程序,这个过程被称为:程序返回。
mov ax,4c00H
int 21H
这两条指令所实现的功能就是程序返回。
我们这里要用到DOS下的Edit来编辑源程序
(1)进入DOS,运行Edit
(2)在Edit中编辑程序
(3)将文件保存为1.asm,退出Edit
(4)回到Debug目录下,能看到保存的文件放在这里
将文件从1.asm通过编译变为1.obj。
编译源程序需要用到编译器,我们这里用到的是masm5.0汇编编译器,文件名为masm.exe。
(1)进入DOS方式,运行masm.exe
运行masm后,首先显示的是一些版本信息,然后提示输入将要编译的源程序文件名称。
需要注意的是:
<1>如果文件扩展名是asm的话只用输入文件名即可,如果文件扩展名不是asm的话,就要输入它的全名。
<2>如果文件在当前路径下,只用输入文件名即可,如果文件在其他目录当中,则要输入文件路径。
(2)输入要编译的文件的名称,按Enter键
这时候屏幕上显示[1.OBJ],编译程序默认要输出的目标文件名为1.obj。直接按Enter键,文件生成在当前目录,若想指定文件生成位置,可在后面加上想让目标文件生成位置的目录。
(3)确定目标文件的名称后,按两次Enter键
发现第一次按Enter键后,提示输入列表文件名称,第二次按Enter键后,提示输入交叉引用文件名称。这两个文件都是编译器将源程序编译为目标文件的过程中产生的中间结果,可以让编译器不生成这个文件,直接按Enter键即可。
(4)忽略这些文件后,屏幕显示如图所示
屏幕显示没有错误,说明编译成功。如果编译出现错误,那么将得不到目标文件,一般有两类错误:
(1)程序中有“Severe Errors”(一般都是在编辑时出现了错误);
(2)找不到所给出的源程序文件。
对源程序进行编译后得到目标文件,需要对目标文件进行连接,将文件从1.obj变为1.exe。
(1)进入DOS方式,运行link.exe
(2)输入要连接的目标文件名后,按Enter键
因为文件在当前目录下,所以直接输入1就行。
屏幕显示“[1.EXE]”,直接按Enter键,编译程序在当前目录下生成1.EXE文件。操作类似于上面的编译。
(3)确定了可执行文件名后,再按两次Enter键
第一次按Enter键后,程序提示输入映像文件的名称;(这个文件是连接程序将目标文件连接为可执行文件过程中产生的中间结果)
第二次按Enter键之后,程序提示输入库文件名称。(库文件里面包含了一些可以调用的子程序,如果程序中调用了某一个库文件中的子程序,就需要在连接的时候和目标文件连接到一起,生成可执行文件。但是我们的程序中没有调用任何子程序,所以这里忽略库文件名的输入)
(5)忽略了映像文件和库文件之后,屏幕显示如图所示
目标文件连接结束,连接程序输出的最后一行告诉我们,这个程序中有一个警告错误:“没有栈段”,可以不用管这个东西。如果连接出现错误,则得不到可执行文件。
简单说一下连接的作用:
(1)当源程序很大时,可以将它分为多个源程序文件进行编译,然后再用连接程序将所有的目标文件连接到一起,生成一个可执行文件。
(2)程序中调用了某个库文件中的子程序,要将这个库文件和该程序生成的目标文件连接到一起,生成一个可执行文件。
(3)源程序进行编译后,得到存有机器码的目标文件,目标文件中的有些内容还不能直接用来生成可执行文件,连接程序将这些内容处理为最终的可执行信息。
(1)在masm 后面加上被编译的源程序文件的路径、文件名,在命令行末尾加上分号。
(2)同样的方法进行连接
执行后发现看不到任何结果,和没运行差不多。但其实程序是运行了的,只是没有向显示器输出任何信息,所以我们看不到。
记得在前面提到过,A程序想要运行就要有一个正在运行的B程序将A程序从可执行文件中加载入内存,那么这个B是谁呢?
DOS中有一个程序 command.com,这个程序载DOS中称为命令解释器,也就是DOS系统中的shell。因此
(1)在DOS执行1.exe时,是正在运行的command,将1.exe中的程序加载入内存;
(2)command设置CPU的CS: IP指向程序的第一条指令(即程序的入口),从而使程序得以运行;
(3)程序运行结束后,返回到command中,CPU继续运行command。
我们写的程序不一定百分百都是正确的,简单的错误我们可以通过仔细检查源程序发现,但是对于隐藏较深的错误,就必须对程序的执行过程进行跟踪分析才容易发现。
我们可以用Debug来跟踪程序,先将程序加载入内存
将1.exe加载入内存,进行相关的初始化后设置CS: IP指向程序的入口,用R命令来看一下各个寄存器的设置情况:
我们可以看到cx中存放的是程序的长度,即1.exe中程序的机器码共为15个字节。
现在程序已经从1.exe中装入内存,当我们来查看它的内容的时候,问题来了,我们查看哪里的内容呢?程序被装在内存的哪个地方了?我们怎么知道呢?展示一下过程吧,话不多说,上图:
图中说的非常详细
(1)程序加载后,ds中存放着程序所在内存区的段地址,这个内存区的偏移地址为0,则程序所在的内存区的地址为 ds: 0;
(2)这个内存区的前256个字节中存放的是PSP,DOS用来和程序进行通信。从256字节处向后的空间存放的是程序
因为PSP占256(100H)字节,所以程序的物理地址是:SA+10H: 0。
看一下上图ds的值, ds=075A,则PSP的地址为075A: 0,程序的地址为076A: 0。
用U命令看一下其他指令
可以看到,从076A: 0000~12AE: 000E都是程序的机器码。
现在开始跟踪,用T命令单步执行程序中的每一条指令,并观察每条指令的执行结果,到了
int 21,我们要用P命令执行
int 21 执行后,显示处“Program terminated normally”,返回到Debug中,表示程序正常结束。
使用Q命令退出Debug,将返回到command中。
以上为本人学习汇编语言时的摘录总结,主要内容来源于汇编语言(第四版) 王爽 著,大家若是感兴趣可以看看原书,很值得推荐,以上内容如果有什么错误的话,还请大家指正!