第一个程序
4.1 一个源程序从写出到执行的过程
-
现在我们将开始编写完整的汇编语言程序,用编译器将他们编译成为可执行文件(如:*.exe文件),在操作系统中运行。
-
这一章,我们将编写第一个这样的程序。
-
一个源程序从写出到执行的过程:
编写——>编译连接——>可执行文件——>执行
- 编写汇编源程序:
- 使用文本编辑器(如记事本,Nodepad++,UltraEdit等),用汇编语言编写汇编源程序。
- 对源程序进行编译连接:
- 使用汇编语言编译程序(MASM.EXE)对源程序文件中的源程序进行编译,产生目标文件。
- 再用连接程序(LINK.EXE)对目标文件进行连接,生成可在操作系统中直接运行的可执行文件。
- 可执行文件:
- 可执行文件中包含两部分:
- 程序(从原程序中的汇编指令翻译过来的机器码)和数据(源程序中定义的数据)
- 相关的描述信息(比如:程序有多大,要占多少内存空间等)
- 可执行文件中包含两部分:
- 执行可执行文件在的程序:
- 在操作系统中,执行可执行文件中的程序;
- 操作系统依照可执行文件中的描述信息,将可执行文件中的机器码和数据加载到内存,并进行相关的初始化(比如:设置CS:IP指向第一条要执行的指令),然后由CPU执行程序。
- 编写汇编源程序:
4.2 源程序
-
汇编指令
有对应的机器码的指令,可以被编译为机器指令,最终为CPU所执行。
-
伪指令
没有对应的机器码的指令,最终不被CPU所执行。
-
谁来执行伪指令?
伪指令是由编译器来执行的指令,编译器根据伪指令来进行相关的编译工作。
-
XXX segment 和 XXX ends
- 定义一个段
- segment和ends是一对成对使用的伪指令,这是在写可被编译器编译的汇编程序时,必须要用到的一对伪指令。
- segment和ends的功能是定义一个段,segment说明一个段的开始,ends说明一个段的结束。
- 一个段必须有一个名称来标识,使用格式为:段名 segment , 段名 ends
- 一个汇编程序是由多个段组成的,这些段被用来存放代码,数据或当作栈空间来使用。
- 一个有意义的汇编程序中至少要有一个段,这个段用来存放代码。
-
end
- 这才是真的没了
- End是一个汇编程序的结束标记,编译器在编译汇编程序的过程中,如果碰到了伪指令end,就结束对源程序的编译。
- 如果程序写完了,要在结尾处加上伪指令end。否则,编译器在编译程序时,无法知道程序在何处结束。
- 注意:切记不要搞混end和ends
-
assume
- 寄存器与段的关联假设
- assume:含义为“假设”。
- 它假设某一段寄存器和程序中的某一个用segment….ends定义的段相关联。
- 通过assume说明这种关联,在需要的情况下,编译程序可以将段寄存器和某一个具体的段相联系。
- 在图中的意思就是假设这个代码段的名字为codesg
-
-
源程序中的”程序“:
- 汇编源程序:
- 伪指令(编译器处理)
- 汇编指令(编译为机器码)
- 程序:源程序中最终由计算机执行,处理的指令或数据。
- 汇编源程序:
-
注意:
- 我们可以将源程序文件中的所有内容称为源程序,将源程序中最终由计算机执行处理的指令或数据,称为程序。
- 程序最先以汇编指令的形式存在源程序中,经编译,连接后转变为机器码,存储在可执行文件中。
-
标号:
- 一个标号指代了一个地址。
- codesg:放在segment的前面,作为一个段的名称,这个段的名称最终被编译,连接程序处理为一个段的地址。
-
程序的结构
-
任务:编程运算2³
- 定义一个段
- 实现处理任务
- 程序结束
- 段与段寄存器关联
-
汇编程序
assume cs:abc
abc segment
mov ax,2
add ax,ax
add ax,ax
abc ends
end
-
-
程序返回
-
我们的程序最先以汇编指令的形式存在源程序中,经编译,连接后转变为机器码,存储在可执行文件中,那么,它怎么样得到运行呢?
-
DOS中的程序运行
- DOS是一个单任务操作系统。
- 一个程序P2在可执行文件中,则必须有一个正在运行的程序P1,将P2从可执行文件中加载入内存后,将CPU的控制权交给P2,P2才能得以运行。P2开始运行后,P1暂停运行。
- 而当P2运行完毕后,应该将CPU的控制权交还给使它得以运行的程序P1,此后,P1继续运行。
-
现在,我们知道一个程序结束后,将CPU的控制权交还给使它得以运行的程序,我们称这个过程为:程序返回
-
如何返回呢?
-
应该在程序的末尾添加返回的程序段。
mov ax,4c00H
int 21H
-
这两条指令所实现的功能就是程序返回。
-
-
-
段结束,程序结束,程序返回
-
语法错误和逻辑错误
-
语法错误
-
程序在编译时被编译器发现的错误;
-
容易发现:
aume cs:abc
abc segment
mov ax,2
add ax,ax
add ax,ax
end
{aume——assume}{缺少abc ends}
-
-
逻辑错误是程序在编译时不能表现出来的,在运行时发生的错误;
- 不容易发现
-
4.3 编译源程序
-
我们这里选择的是notepad++编译器进行编写
-
上机!
- 一般来说,有两类错误使得我们得不到所期望的目标文件:
- 我们程序中有“Severe Errors”;
- 找不到所给出的源程序文件;
- 一般来说,有两类错误使得我们得不到所期望的目标文件:
-
1.exe的执行
-
我们的程序没有像显示器输出任何信息。程序只是做了一些将数据送入寄存器和加法的操作,而这些事情,我们不可能从先视频上看出来
-
程序执行完成后,返回,屏幕上再次出现操作系统的提示符
-
4.4 以简化的方式进行编译和连接
例如
masm 1.asm;
link 1.obj;
加个分号会省很多麻烦,编译器就不会一直提问,可以直接运行。(相当于默认)
-
关于编译和连接
-
编译和连接的作用是什么?
-
连接的作用有以下几个:
-
当源程序很大时,可以将它分为多个源程序文件来编译,每个源程序编译成为目标文件后,再用连接程序将他们连接到一起,生成一个可执行文件。
-
程序中调用了某个库文件中的子程序,需要将这个库文件和该程序生成的目标文件连接到一起,生成一个可执行文件。
-
一个源程序编译后,得到了存有机器码的目标文件,目标文件中的有些内容还不能直接用来生成可执行文件,连接程序将这些内容处理为最终的可执行信息。
所以,在只有一个源程序文件,而又不需要调用某个库中的子程序的情况下,也必须用连接程序对目标文件进行处理,生成可执行文件。
-
注意,对于连接的过程,可执行文件是我们要得到的最终结果。
-
-
强调:我们学习汇编的主要目的,就是通过用汇编语言进行编程而深入地理解计算机底层的基本工作机理,达到可以随心所欲地控制计算机的目的。
-
我们用汇编语言编程,就要用到:编辑器(Edit),编译器(masm),连接器(link),调试工具(debug)等所有工具,而这些工具都是在操作系统之上运行的程序,所以我们的学习过程必须在操作系统的环境中进行。
-
4.5 可执行文件中的程序装入内存并运行的原理
-
在DOS中,可执行文件中的程序P1若要运行,必须有一个正在运行的程序P2,将P1从可执行文件中加载入内存,将CPU的控制权交给它,P1才能得以运行,这时P2是挂起状态;
当P1运行完毕后,应该将CPU的控制权交还给使它得以运行的P2。
-
1.exe的执行过程
- 我们在提示符”C:\ASM>“后面输入可执行文件的名字“1”,按Enter键。
- 1.exe中的程序运行
- 运行结束,返回,再次显示提示符”C:\ASM>“
-
操作系统的外壳
- 操作系统是由多个功能模块组成的庞大,复杂的软件系统。任何通用的操作系统,都要提供一个称为shell(外壳)的程序,用户(操作人员)使用这个程序来操作计算机系统工作。
- DOS中有一个程序command.com,这个程序在DOS中称为命令解释器,也就是DOS系统的shell。
-
在执行第一步操作时,有一个正在运行的程序将1.exe中的程序加载入内存,这个正在运行的程序是什么?
我们在DOS中直接执行1.exe时,是正在运行的command将1.exe中的程序加载入内存。
-
它将程序加载入内存后,如何使得程序得以运行?
command设置CPU的CS:IP指向程序的第一条指令(即程序的入口),从而使程序得以运行。
-
执行第三步操作,程序运行结束后,返回到哪里?
程序运行结束后,返回到command中,CPU继续运行command。
-
汇编程序从写出到执行的过程:
编程(edit)——>1.asm——>编译(masm)——>1.obj——>连接(link)——>1.exe——>加载(command)——>内存中的程序——>运行(CPU)
4.6 程序执行过程的跟踪
-
为了观察程序的运行过程,我们可以使用Debug
-
Debug可以将程序加载入内存,设置CS:IP指向程序的入口,但Debug并不放弃对CPU的控制,这样,我们就可以使用Debug的相关命令来单步执行程序,查看每条指令的执行结果。
-
接下来可以用R命令来看一下各个寄存器的设置情况:
-
可以看到,Debug将程序从可执行文件加载入内存后,cx中存放的是程序的长度。2.exe中程序的机器码共有15个字节
-
现在程序已从2.exe中装入内存,接下来我们查看一下它的内容,可是我们查看哪里的内容呢?
-
程序被装入内存的什么地方?
-
我们如何得知?
-
在DOS系统中.EXE文件中的程序的加载过程如下
-
总结
-
程序加载后,ds中存放着程序所在内存区的段地址,这个内存区的偏移地址为0,则程序所在的内存区的地址为:ds:0
-
这个内存区的前256个字节中存放的是PSP,dos用来和程序进行通信。
-
从256字节处向后的空间存放的是程序。
-
所以,我们从ds中可以得到PSP的段地址SA,PSP的偏移地址为0,则物理地址为SA×16+0.
-
因为PSP占256(100H)字节,所以程序的物理地址是:
SA×16+0+256=SA×16+16×16=(SA+16)×16+0
-
可用段地址和偏移地址表示为:SA+10:0
-
-
用U命令查看一下其他指令:
-
用T命令但不执行程序中的每一条指令,并观察每条指令的执行结果
-
到了int21,我们要用P命令来执行
-
int 21执行后,显示“Program terminated normally”,返回到Debug中
-
表示程序正常结束。
-
注意:用P命令来执行int 21
-
需要注意的是,在DOS中运行程序时,是command将程序加载入内存;
-
所以程序运行结束后返回到command中,而在这里是Debug将程序加载入内存,所以程序运行结束后要返回到Debug中。
-
使用Q命令退出Debug,将返回到command中,因为Debug是由command加载运行的。
-
我们在DOS中用“Debug 1.exe”运行Debug对1.exe进行跟踪时,程序加载的顺序是:command加载Debug,Debug加载1.exe。
-
返回的顺序是:从1.exe中的程序返回到Debug,从Debug返回到command