文章目录
- 前言
- 1. 示例程序
- 2. 伪指令
- 2.1 XXX segment
- 2.2 end
- 2.3 assume
- 3. 源程序中的“程序”
- 4. 标号
- 5. 程序的结构
- 6. 程序返回
- 7. 语法错误和逻辑错误
- 结语
前言
📌
汇编语言是很多相关课程(如数据结构、操作系统、微机原理)的重要基础。但仅仅从课程的角度出发就太片面了,其实学习汇编语言可以深入理解计算机底层工作原理,提升代码效率,尤其在嵌入式系统和性能优化方面有重要作用。此外,它在逆向工程和安全领域不可或缺,帮助分析软件运行机制并增强漏洞修复能力。
本专栏的汇编语言学习章节主要是依据王爽老师的《汇编语言》来写的,和书中一样为了使学习的过程容易展开,我们采用以8086CPU为中央处理器的PC机来进行学习。
1. 示例程序
下面就是一段简单的汇编语言源程序。
assume cs:codesg
codesg segment
mov ax, 0123H
mov bx, 0456H
add ax,bx
add ax ax
mov ax,4C00H
int 21H
codesg ends
end
2. 伪指令
在汇编语言源程序中,包含两种指令,一种是汇编指令,一种是伪指令。
-
汇编指令是有对应的机器码的指令,可以被编译为机器指令,最终为CPU所执行。
-
而伪指令没有对应的机器指令,最终不被CPU所执行。
那么谁来执行伪指令呢?伪指令是由编译器来执行的指令,编译器根据伪指令来进行相关的编译工作。
你现在能看出来最开始示例程序中哪些指令是伪指令吗?
示例程序中出现了3种伪指令,如下分析。
2.1 XXX segment
XXX segment
:
:
:
xxx ends
segment 和ends是一对成对使用的伪指令,这是在写可被编译器编译的汇编程序时,必须要用到的一对伪指令。
segment和ends的功能是定义一个段,segment说明一个段开始,ends说明一个段结束。一个段必须有一个名称来标识,使用格式为:
段名 segment
:
段名 ends
比如,示例代码中的:
codesg segment ;定义一个段,段的名称为“codesg”,这个段从此开始
:
codesg ends ;名称为“codesg”的段到此结束
一个汇编程序是由多个段组成的,这些段被用来存放代码、数据或当作栈空间来使用。我们在前面的文章中所讲解的段的概念,在汇编源程序中得到了应用与体现,一个源程序中所有将被计算机所处理的信息:指令、数据、栈,被划分到了不同的段中。
一个有意义的汇编程序中至少要有一个段,这个段用来存放代码。
我们可以看到,程序中,在codesg segment
和 codesg ends
之间写的汇编指令是这个段中存放的内容,这是一个代码段(其中还有我们不认识的指令,后面会进行讲解)。
2.2 end
end 是一个汇编程序的结束标记,编译器在编译汇编程序的过程中,如果碰到了伪指令end,就结束对源程序的编译。所以,在我们写程序的时候,如果程序写完了,要在结尾处加上伪指令end。否则,编译器在编译程序时,无法知道程序在何处结束。
❗注意,不要搞混了end和ends,ends是和segment 成对使用的,标记一个段的结束,ends的含义可理解为“end segment”。我们这里讲的 end 的作用是标记整个程序的结束。
2.3 assume
这条伪指令的含义为“假设”。它假设某一段寄存器和程序中的某一个用segment...ends
定义的段相关联。通过 assume 说明这种关联,在需要的情况下,编译程序可以将段寄存器和某一个具体的段相联系。
assume并不是一条非要深入理解不可的伪指令,以后我们编程时,记着用assume将有特定用途的段和相关的段寄存器关联起来即可。
比如,在示例程序中,我们用 codesg segment .. codesg ends
定义了一个名为codseg的段,在这个段中存放代码,所以这个段是一个代码段。在程序的开头,用assume cs:codesg
将用作代码段的段codesg和CPU中的段寄存器cs联系起来。
3. 源程序中的“程序”
用汇编语言写的源程序,包括伪指令和汇编指令,我们编程的最终目的是让计算机完成一定的任务。源程序中的汇编指令组成了最终由计算机执行的程序,而源程序中的伪指令是由编译器来处理的,它们并不实现我们编程的最终目的。
这里所说的程序就是指源程序中最终由计算机执行、处理的指令或数据。
❗注意,以后可以将源程序文件中的所有内容称为源程序,将源程序中最终由计算机执行、处理的指令或数据,称为程序。程序最先以汇编指令的形式存在源程序中,经编译、连接后转变为机器码,存储在可执行文件中。这个过程如下图所示。
4. 标号
汇编源程序中,除了汇编指令和伪指令外,还有一些标号,比如“codesg“一个标号指代了一个地址。比如codesg在segment的前面,作为一个段的名称,这个段的名称最终将被编译、连接程序处理为一个段的段地址。
5. 程序的结构
我们现在讨论一下汇编程序的结构。在前面的学习中,我们都是通过直接在Debug中写入汇编指令来写汇编程序,对于十分简短的程序这样做的确方便。可对于大一些的程序,就不能如此了。我们需要写出能让编译器进行编译的源程序,这样的源程序应该具备起码的结构。
源程序是由一些段构成的。我们可以在这些段中存放代码、数据、或将某个段当作栈空间。我们现在来一步步地完成一个小程序,从这个过程中体会一下汇编程序中的基本要素和汇编程序的简单框架。
任务:编程运算2^3。源程序应该怎样来写呢?
(1)我们要定义一个段,名称为abc。
abc segment
:
abc ends
(2)在这个段中写入汇编指令,来实现我们的任务。
abc segment
mov ax, 2
add ax, ax
add ax, ax
abc ends
(3)然后,要指出程序在何处结束。
abc segment
mov ax, 2
add ax, ax
add ax, ax
abc ends
end
(4)abc被当作代码段来用,所以,应该将abc和cs联系起来。(当然,对于这个程序,也不是非这样做不可。)
assume cs:abc
abc segment
mov ax, 2
add ax, ax
add ax, ax
abc ends
end
最终这样一个汇编源程序就被我们完成了。
6. 程序返回
我们的程序最先以汇编指令的形式存在源程序中,经编译、连接后转变为机器码,存储在可执行文件中,那么,它怎样得到运行呢?
下面,我们在DOS(一个单任务操作系统)的基础上,简单地讨论一下这个问题
一个程序P2在可执行文件中,则必须有一个正在运行的程序P1,将P2从可执行文件中加载入内存后,将CPU的控制权交给P2,P2才能得以运行。P2开始运行后,P1暂停运行。
而当P2运行完毕后,应该将CPU的控制权交还给使它得以运行的程序P1,此后,P1继续运行。
现在,我们知道,一个程序结束后,将CPU的控制权交还给使它得以运行的程序,我们称这个过程为:程序返回。那么,如何返回呢?应该在程序的末尾添加返回的程序段。
我们回过头来,看一下最开始示例程序中的两条指令:
mov ax,4C00H
int 21H
这两条指令所实现的功能就是程序返回。
在目前阶段,我们不必去理解
int 21H
指令的含义,和为什么要在这条指令的前面加上指令mov ax, 4c00H
。我们只要知道,在程序的末尾使用这两条指令就可以实现程序返回。
到目前为止,我们已经遇到了几个和结束相关的内容:段结束、程序结束、程序返回。下表展示了它们的区别。
目的 | 相关指令 | 指令性质 | 指令执行者 |
---|---|---|---|
通知编译器一个段结束 | 段名 ends | 伪指令 | 编译时,由编译器执行 |
通知编译器程序结束 | end | 伪指令 | 编译时,由编译器执行 |
程序返回 | mov ax, 4c00H int 21H | 汇编指令 | 执行时,由CPU执行 |
7. 语法错误和逻辑错误
可见,在上面我们自己实现任务的程序在运行时会引发一些问题,因为程序没有返回。当然,这个错误在编译的时候是不能表现出来的,也就是说,该程序对于编译器来说是正确的程序。
一般说来,程序在编译时被编译器发现的错误是语法错误。
比如将程序写成如下这样就会发生语法错误:
aume cs:abc
abc segment
mov ax, 2
add ax, ax
add ax, ax
end
显然,程序中有编译器不能识别的aume,而且编译器在编译的过程中也无法知道 abc段到何处结束。
在源程序编译后,在运行时发生的错误是逻辑错误。语法错误容易发现,也容易解决。而逻辑错误通常不容易被发现。
不过,最后完成的任务程序中的错误却显而易见,没有加程序返回,我们将它改正过来:
assume cs:abc
abc segment
mov ax, 2
add ax, ax
add ax, ax
mov ax,4C00H
int 21H
abc ends
end
结语
今天的分享到这里就结束啦!如果觉得文章还不错的话,可以三连支持一下。
也可以点点关注,避免以后找不到我哦!
Crossoads主页还有很多有趣的文章,欢迎小伙伴们前去点评,您的支持就是作者前进的动力!