文章目录
- 前言
- 一、一个程序的诞生过程
- 1. 源程序
- 1.1 伪指令
- (1)segment
- (2)end
- (3)assume
- 1.2 源程序与实际运行的'程序'
- 1.3 标号
- 1.4 程序的结构
- 1.5 程序返回
- 1.6 语法错误和逻辑错误
- 2. 创建源程序的过程
- 2.1 编辑源程序
- 2.2 编译源程序
- (1) 编译器MASM
- 2.3 连接
- 连接的作用
- 2.4 简化编译、连接过程
- 2.5 exe可执行文件
- 二、补充
- 1. exe文件与内存
- 2. 程序执行过程的追踪
- 3. psp内存区
- 三、预习实验
- 结语
前言
点赞再看,养成习惯!
该系列博文基于王爽老师 <汇编语言 第四版> 一书,需要的同学链接自取:
链接:https://pan.baidu.com/s/1NAgD1Z15LtK1BuH92xmICA
提取码:xlzb
另外书中提到的DosBox软件不想去官网下载的小伙伴也可以自取:
链接:https://pan.baidu.com/s/1O6PnLb_hN-WUS2avicNpcw
提取码:xlzb
最后如果还没有计算机基础的同学,建议先补充下计算机相关的基础知识:
笔记目录总览
经过前几天的基础知识学习,我们已经对汇编指令有了一个宏观上的了解,我们学习了硬件,又学习了一些常见的指令,可是这些都无法真正的被叫做一个程序。不过不要紧,学习完今天的内容,我们就可以真正的做出我们的第一个程序。
一、一个程序的诞生过程
不知道小伙伴们平时有没有想过,我们平时经常使用的一些应用软件到底是如何被制作出来的?如何做到我们只需要点击一个桌面的EXE文件就可以运行?我们是如果做到将一些只有机器认识的指令转化为大家都能够使用的软件呢?
如果按照一个程序的诞生过程来讲,首先第一个步骤肯定是软件工程师们将想实现的汇编指令编写成一个指令文件。这个文件的编写只需要借助我们常用的文本编辑器(比如Edit,记事本,notepad++等等),而我们生成的文本文件的作用主要就是记录我们想要运行的汇编指令。接下来的第二个步骤,我要做的就是想办法让计算机可以认识这些汇编指令。这个时候我们就要使用汇编语言编译程序对源程序文件中的源程序进行编译,产生计算机可以识别的目标文件,接着我们还需要将目标文件关联起来,保证计算机系统可以正确的运行这些文件。第三步也就是最后我们只需要执行可执行文件中的程序即可。
总结一下:
- 编写汇编源程序
- 对源程序进行编译链接
- 执行可执行文件
如果小伙伴对这些文字概念不太理解,没关系,接下来就让我们一步一步的来创建我们第一个可执行的源程序!
1. 源程序
还记得我们之前讲过的汇编指令吗?
截止到目前为止,我们已经学习了如下指令:
- mov 赋值指令
- add 加法指令
- sup 减法指令
- push 入栈指令
- pop 出栈指令
这些语句其实都有一个统一的分类:指令语句。什么是指令语句?是指可以被翻译成机器指令直接被CPU识别的指令代码,在编译时操作系统会为这些语句分配存储单元帮助运行,这种指令每一条都代表着计算机所具有的一个基本能力,因此这些指令又被叫做可执行语句。
与之相对的,还有一部分我们之前没有接触过的语句是不能够被计算机直接识别的,它们只在编译阶段发挥作用,需要有编译器(MASM,TASM等)来翻译,其主要作用就是知识汇编程序应该如何识别汇编程序,因此这种语句又被叫做命令语句或伪指令语句。
指令语句与伪指令的区别:
指令语句: 可以翻译成机器指令被CPU直接识别,编译时系统会为其分配内存,运行期可以运行。
伪指令: 不可以被CPU直接识别,需要编译器进行翻译,操作系统不会为其分配内存,运行期不会运行。
为了保证我们今天可以写出我们的第一个程序,需要先学习几个保证程序运行的伪指令。
1.1 伪指令
(1)segment
segment再英语中有片段的意思,见名知意的角度来看,segment主要是辅助我们声明一个指令段。segment的出现一定会伴随另一个伪指令ends一起出现,segment声明一个段的开始,ends标示着一个段的结束。
segment和ends的使用格式:
段名:segment代码段 XXX
段名 ends
(2)end
end和ends不同,这个小伙伴们需要注意,end主要是用来标识一个汇编程序的结束。编译器再编译汇编程序的过程中,如果碰到了伪指令end,就会结束对汇编程序的编译,因此我们在编写程序的时候,如果程序写完了,就需要在结尾处加上伪指令end,否则编译器无法感知编译工作已经结束。
(3)assume
assume再英文中有假设的含义。
在汇编中,它假设某一段寄存器和程序中的某一个用segment…ends定义的段相互关联,通过assume伪指令说明这种关联。在需要的情况下,编译程序可以将段寄存器和某一个具体的段相互联系。assume并不是一条非要深入理解的伪指令,我们只需要在编程时,记着用assume将有特定用途的段和相关的段寄存器关联起来即可。
1.2 源程序与实际运行的’程序’
我们通常说的源程序是指用汇编语言中的汇编指令和伪指令编写的文件,而我们编程的目的是为了让计算机完成我们设定的特定任务。源程序中我们写入的汇编指令组成了最终计算机执行的程序,而相对的伪指令则是交由编译器处理,他们并不会参与我们特定目的实现的组成,所以我们常说的源程序中的’程序’实际上是指最终被计算机识别,运行的指令和数据。
1.3 标号
除了汇编指令和伪指令外,我们还会在一段源程序中发现一些标号,比如卸载segment前的代码段名称就属于标号的一种,它最终会被编译,连接程序处理为一个段的段地址。
为了方便理解 我们写个简单的源程序
assume cs:codesg
codesg segmentmov ax,0123mov bx,0456add ax,bxadd ax,axmov ax,4c00int 21
codesg ends
end
这里面的 codesg就是一个标号
1.4 程序的结构
在此之前,我们在学习指令语句的时候都是通过DOSBOS的Debug功能进行的,这样对于简短的程序确实是十分的方便,但是如果对于语句很多,逻辑很复杂的程序,显然就不能够再采取这种方式,我们需要编写一个用来编译的源程序,而相对的,这个源程序也需要有它自己的结构。
我们通过一个简单的任务实验来讲解
任务: 求2的三次方是多少首先我们需要声明一个可以用来运算的代码段
codeA segment
... 此处代表省略的代码段逻辑
codeA ends接着,我们还要为代码段中填入具体的汇编逻辑
codeA segment
mov ax,2
add ax,ax
add ax,ax
codeA ends然后我们指出程序需要在哪里结束
codeA segment
mov ax,2
add ax,ax
add ax,ax
codeA ends
end当然codeA我们假定他是个代码段,我们需要指定一个段地址寄存器与它进行关联(当然也并非一定要)
assume cs:codeA
codeA segment
mov ax,2
add ax,ax
add ax,ax
codeA ends
end
最终,这就是我们想要的程序:assume cs:codeA
codeA segment
mov ax,2
add ax,ax
add ax,ax
codeA ends
end
1.5 程序返回
当一个程序执行结束后,我们需要通知CPU并且交还它的控制权,那么我们该如何实现呢?现阶段我们接触到的概念有三种方式与结束有关,分别是:
- 段名 ends: 它标识了一个指令段的结束
- end: 写在程序结尾,标识一个程序的结束
- mov ax,4c00 int 21 :表示一个程序终端,程序返回(了解即可,现阶段无需深究)
1.6 语法错误和逻辑错误
还记得刚刚我们计算2的三次幂的程序吗:
assume cs:codeA
codeA segment
mov ax,2
add ax,ax
add ax,ax
codeA ends
end
它在运行时其实会遇到一些问题,因为这个程序并没有设定一个返回值,当然这个问题再编译的时候是没有办法被发现的,我们没有办法说这段源码是一个错误的程序。因为它的语法是没有问题的(这里其实还有一个隐藏的问题,稍后会提到),仅是逻辑上存在一些漏洞。而相对应的,如果我再该段代码中拼错单词,比如assume codeA修改为codeB,由于程序中并没有codeB的声明,因此在编译阶段就会被编译器发现,这种就是很明显的语法错误。
2. 创建源程序的过程
当我们了解完一个源程序的相关结构后,我们可以尝试的创建一个源程序出来。
2.1 编辑源程序
首先我们需要有个容器来保存我们的源程序,当然不需要很复杂,我们只需要创建一个纯文本文档即可。
然后我们将刚刚的程序粘进去(这里我们需要加上返回语句)
assume cs:codeA
codeA segment
mov ax,2
add ax,ax
add ax,axmov ax,4c00
int 21codeA ends
end
2.2 编译源程序
(1) 编译器MASM
当我们完成源文件的编写后,我们接下来就需要将源文件编译成一个包含机器指令的目标文件。
这里我们需要一个相应的编译器,在后续的博文中我们都会指定使用masm汇编编译器,文件名为masm.exe,还没有下载的同学点击下面的链接下载。
链接:https://pan.baidu.com/s/1HXkmC5rn-2DvFcIgnkKAOw
提取码:xlzb
这里讲一下编译器应该怎么用:
由于masm再win10环境下不兼容,因此需要搭配DosBox一起使用,在使用前我们先讲下关于DosBox的配置文件,让我们打开DosBox:
当我们运行DosBox的时候,我们可以看到两个窗口,其中下面这个窗口上有描述配置文件的位置
我们按照路径找到配置文件:
打开配置文件,然后添加两行配置命令
mount d: d:\Code\ASM
d:
我们来解释下这两句话是什么含义:
- mount d: d:\Code\ASM :这里面的mount命令是DosBox的内置命令,是为我们挂载默认的工作盘符,如果不做指定,我们就需要每次进入DosBox的时候都输入一次。这里的d:\Code\ASM是我新建的一个文件夹,这个文件夹地址小伙伴们可以自己选择,创建完文件夹后需要将刚刚下载的文件夹中的内容复制进去;
2. 第二行的d: 如果我们挂载了多个工作空间,就需要通过我们设定的标识符进入不同的工作空间,这里的d就是在我们启动DosBox时默认进入刚刚配置的工作空间。
配置完masm编译器我们就可以在dosbox中使用了,具体的使用方法如下:
打开dosbox,输入masm进入编译器
这里编译器提示我们输入源文件地址,我们需要将文件的全路径输入进去(小技巧,我们可以将文件放入我们刚刚挂载的工作空间内,就可以直接输入文件名):
接下来编译器要求我们输入的时编译后目标文件的名称,如果不想改名字,直接回车即可:
确认了生成的文件名后,编译器接下来询问我们编译目标文件时是否产生列表文件,这里直接回车即可
忽略了列表文件生成后,这里编译器询问我们是否产生交叉引用文件,忽略即可
好了,到这里编译已经完成了,我们发现出现了一个语法错误,错误信息为error A2107:Non-digit in number。这里解释下,这个错误是由于我们在编写汇编文件时,并没有指定数字的进制,我们需要为我们的文件指定对应的进制,修改后如下:
assume cs:codeA
codeA segment
mov ax,2H
add ax,ax
add ax,axmov ax,4c00H
int 21H
codeA ends
end这里数字后面添加的H是英文hexadecimal的简写,表示当前数字是一个十六进制数字
修改后我们再运行一次:
此时已经运行成功了,进入对应的工作空间会发现新生成了一个OBJ后缀的文件:
2.3 连接
刚刚我们完成了codeA.asm到codeA.OBJ的转变,接下来我们需要将文件进行连接,从而得到一个可以直接被执行的.exe文件。
刚刚再百度网盘中下载的文件包含了三个
其中debug和masm我们都用过了,这里将要用到的就是最后一个Link
(1) 打开dosbox,输入link命令
这里我们输入想要连接的文件名
接着输入我们想要生成的目标文件名,采用默认的话回车即可:
这里是询问我们是否生成映射文件,现阶段忽略即可:
同样,这里是询问是否生成库文件,忽略即可。
到这里连接就结束了,我们还是查看工作空间,会发现一个新生成的EXE文件。
这里有一个warning警告,含义是没有栈段,忽略即可。
连接的作用
这里我们简单的讲一下连接的作用:
- 当源程序很大时,可以拆分为多个源程序文件编译,每个源程序文件编译成目标文件后,可以通过连接程序将他们连接在一起,生成一个可执行文件。
- 程序中调用了某个库文件中的子程序,需要将这个库文件和该程序生成的目标文件连接到一起,生成一个可执行文件。
- 一个源程序编译后,得到了存有机器指令的目标文件,目标文件中的有些信息还不能直接生成可执行文件,需要连接程序将这些信息处理为最终可运行的内容,所以再只有一个源程序文件而又不需要调用其他库的时候,也必须进行连接操作。
2.4 简化编译、连接过程
当然MASM和LINK也为我们提供了简化的方式:
总结一下格式就是: masm或link命令 + 文件盘符+分号。
2.5 exe可执行文件
这里我们已经生成了学习到这里的第一个可执行文件:codeA.exe,很遗憾win10的操作系统还是无法直接运行它,我们依然要借助DosBox运行:
是不是很奇怪,为什么程序运行后没有任何结果,仿佛没有运行一样?其实程序肯定是运行了的,只不过我们的程序中并没有任何向显示器输出信息的指令,不过不要遗憾,随着课程的进行,我们会学到更多的指令来帮助我们完成各种各样复杂的程序。
二、补充
1. exe文件与内存
在我们调用可执行文件的时候,操作系统会为EXE文件本身划分一段安全的内存,这段内存仅供当前的exe文件使用。而在DOS中,可执行文件若想要执行,必须有一个正在运行的程序将其加载入内存,在上面我们运行codeA.exe的时候,就是由DosBox程序将其加载入内存,其运行结束后再将CPU的控制权交还。
2. 程序执行过程的追踪
当然我们也可以通过debug程序配合运行我们的exe文件:
然后我们就可以通过Debug自身的命令来观察CPU各个寄存器的变化:
这里我们看到我们编写在源程序中的指令已经被加载进来了,接下来使用T命令执行即可,这里我就不再赘述了,感兴趣的可以查看《汇编语言 第四版》中相关描述:
3. psp内存区
- 当我们启动dosbox的时候,操作系统会为它规划一段起始地址为SA:0000(即起始地址偏移量为0)的容量足够的内存区。
- 这段内存区的前256个字节中,会创建一个称为程序前缀(PSP)的数据区,DOS系统需要利用PSP来和被加载进来的可执行程序进行通信。
- 从PSP内存区后面,程序将会被装入,起始地址为:SA+10H:0000 ;
- 将该内存区的段地址存入ds寄存器中,初始化其他相关寄存器后,将cs:ip指向程序的入口。
三、预习实验
这里为了接下来的学习,需要同学们将课本中的实验三完成:
结语
今天的内容就到此结束了,有疑问的小伙伴欢迎评论区留言或者私信博主,博主会在第一时间为你解答。
屏幕前努力学习的你如果想要持续了解博主最新的学习笔记或收集到的资源,可以关注博主的个人公众号。这里有很多最新的技术领域PDF电子书及好用的软件分享
码字不易,感到有收获的小伙伴记得要关注博主一键三连,不要当白嫖怪哦~
如果大家有什么意见和建议请评论区留言或私聊博主,博主会第一时间反馈的哦。