本章包含开始使用平面汇编程序所需的所有最重要的信息。如果您是经验丰富的汇编语言程序员,则在使用此编译器之前,至少应阅读本章。
Flat assembler是用于x86体系结构处理器的快速汇编语言编译器,该编译器进行了多次遍历以优化生成的机器代码的大小。它是可自编译的,并且提供了适用于不同操作系统的版本。所有版本均设计为可从系统命令行使用,并且它们的行为不应有差异。
1.1.1系统要求
所有版本都需要x86架构的32位处理器(至少80386),尽管它们也可以为x86架构的16位处理器生成程序。DOS版本需要与MS DOS 2.0兼容的操作系统,以及真正的实模式环境或DPMI。Windows版本需要与3.1版本兼容的Win32控制台。
1.1.2从命令行执行编译器
要从命令行执行平面汇编程序,您需要提供两个参数-第一个应该是源文件的名称,第二个应该是目标文件的名称。如果未提供第二个参数,则将自动猜测输出文件的名称。在显示有关程序名称和版本的简短信息之后,编译器将从源文件中读取数据并进行编译。编译成功后,编译器会将生成的代码写入目标文件并显示编译过程的摘要;否则,它将显示有关发生的错误的信息。
在命令行中,您还可以包括-m
选项,后跟数字,该数字指定最大应使用平面汇编程序多少KB的内存。在DOS版本中,此选项仅限制扩展内存的使用。该-p
选项后跟数字可用于指定汇编程序执行的通过次数限制。如果无法在指定的通过时间内生成代码,则程序集将终止并显示一条错误消息。此设置的最大值为65536,而在命令行中不包含此类选项时使用的默认限制为100。
源文件应该是文本文件,并且可以在任何文本编辑器中创建。DOS和Unix标准均接受换行符,制表符视为空格。
没有命令行选项会影响编译器的输出,平面汇编程序仅需要源代码来包含其真正需要的信息。例如,要指定输出格式,可以使用format
源开头的指令来指定输出格式 。
1.1.3编译器消息
如上所述,在成功编译之后,编译器将显示编译摘要。它包含以下信息:完成了几次传递,花费了多少时间以及将多少字节写入了目标文件。以下是编译摘要的示例
如果在编译过程中出错,程序将显示一条错误消息。例如,当编译器找不到输入文件时,它将显示以下消息:
flat assembler version 1.73 (16384 kilobytes memory) 38 passes, 5.3 seconds, 77824 bytes.
如果错误与源代码的特定部分有关,则还将显示导致错误的源代码行。另外,此行在源代码中的位置也可以帮助您发现此错误,例如:
flat assembler version 1.73 (16384 kilobytes memory) error: source file not found.
这意味着在example.asm
文件编译器的第三行中遇到了无法识别的指令。当导致错误的行包含宏指令时,也会在宏指令定义中显示生成错误指令的行:
flat assembler version 1.73 (16384 kilobytes memory) example.asm [3]: mob ax,1 error: illegal instruction.
这意味着example.asm
文件第六行中的宏指令使用其定义的第一行生成了无法识别的指令。
1.1.4输出格式
默认情况下,当format
源文件中没有指令时,平面汇编程序只是将生成的指令代码放入输出中,从而以这种方式创建平面二进制文件。默认情况下,它会生成16位代码,但是您始终可以使用use16
或use32
指令将其转换为16位或32位模式。选择后,某些输出格式会切换为32位模式-有关可以选择的格式的更多信息,请参见2.4。
所有输出代码始终按其输入到源文件中的顺序。
下面提供的信息主要供以前使用过某些其他汇编编译器的汇编程序员使用。如果您是初学者,则应查找汇编编程教程。
尽管您可以使用预处理器功能(宏指令和符号常量)自定义它,但默认情况下,平面汇编程序使用Intel语法进行汇编指令。它还有自己的一组指令-编译器指令。
源内定义的所有符号均区分大小写。
1.2.1指令语法
汇编语言中的指令由换行符分隔,并且期望一条指令填充一行文本。如果一行中包含分号(除了带引号的字符串中的分号除外),则此行的其余部分为注释,编译器将忽略该注释。如果一行以\
字符结尾(最终可能在分号和注释之后),则在此位置附加下一行。
源中的每一行都是项目序列,可以是三种类型之一。一种类型是符号字符,它们是特殊字符,即使与其他字符没有间距,它们也是单独的项目。任何+-/*=<>()[]{}:,|&~#`
是符号字符。其他字符的序列(由空格或符号字符与其他项目分隔)是一个符号。如果symbol的第一个字符是单引号或双引号,它会将其后的任何字符序列(甚至是特殊字符)都集成到带引号的字符串中,该字符串应以其开头的相同字符结尾(单引号或双引号)引号)-但是,如果连续有两个这样的字符(它们之间没有任何其他字符),则将它们集成到带引号的字符串中,就像其中一个一样,然后继续带引号的字符串。除符号字符和带引号的字符串以外的其他符号也可以用作名称,因此也称为名称符号。
每个指令都由助记符和各种数量的操作数组成,并用逗号分隔。操作数可以是寄存器,立即值或在内存中寻址的数据,大小运算符也可以位于操作数之后,以定义或覆盖其大小(表1.1)。您可以在表1.2中找到可用寄存器的名称,它们的大小不能被覆盖。立即数可以通过任何数值表达式指定。
当操作数是内存中的数据时,该数据的地址(也可以是任何数字表达式,但可能包含寄存器)应括在方括号中或在ptr
运算符之前。例如,指令 mov eax,3
会将立即数3放入EAX寄存器中,指令 mov eax,[7]
会将来自地址7的32位值放入EAX中,指令mov byte [7],3
会将立即数3放入地址7中的字节中,也可以写成mov byte ptr 7,3
。要指定应使用哪个段寄存器进行寻址,应在地址值之前(方括号内或ptr
运算符之后)放置段寄存器名称,后跟冒号。
表1.1大小运算符
操作者 | 位 | 字节 |
---|---|---|
byte | 8 | 1 |
word | 16 | 2 |
dword | 32 | 4 |
fword | 48 | 6 |
pword | 48 | 6 |
qword | 64 | 8 |
tbyte | 80 | 10 |
tword | 80 | 10 |
dqword | 128 | 16 |
xword | 128 | 16 |
qqword | 256 | 32 |
yword | 256 | 32 |
dqqword | 512 | 64 |
zword | 512 | 64 |
表1.2寄存器
类型 | 位 | |||||||||
---|---|---|---|---|---|---|---|---|---|---|
一般 | 8 |
| ||||||||
16 |
| |||||||||
32 |
| |||||||||
分割 | 16 |
| ||||||||
控制 | 32 |
| ||||||||
调试 | 32 |
| ||||||||
FPU | 80 |
| ||||||||
MMX | 64 |
| ||||||||
SSE | 128 |
| ||||||||
AVX | 256 |
| ||||||||
AVX-512 | 512 |
| ||||||||
Opmask | 64 |
| ||||||||
边界 | 128 |
|
1.2.2数据定义
要定义数据或为其保留空间,请使用表1.3中列出的指令之一。数据定义指令后应跟一个或多个数字表达式,并用逗号分隔。这些表达式根据使用的指令定义大小为数据单元格的值。例如,db 1,2,3
将分别定义值1、2和3的三个字节。
的db
和du
指令还接受任何长度,时将被转换成字节的链的引用字符串值db
被使用并且与归零高字节字的链时du
被使用。例如db 'abc'
将定义值61、62和63的三个字节。
该dp
指令及其同义词df
接受由两个用冒号分隔的数值表达式组成的值,第一个值将成为远指针值的高位字,第二个值将成为远指针值的低位双字。还dd
接受由冒号分隔的两个单词值组成的指针,并dt
接受以冒号分隔的单词和四字单词值,首先存储四字。dt
以单个表达式为参数的伪指令仅接受浮点值,并以FPU双精度扩展格式创建数据。
以上任何指令均允许使用特殊dup
运算符来制作给定值的多个副本。重复项的计数应在此运算符之前,而重复项的值应在之后-甚至可以是用逗号分隔的值链,但是此类值集必须用括号括起来,例如db 5 dup (1,2)
,它定义了给定两个值的五个副本字节序列。
本file
是一个特殊的指令,它的语法是不同的。此伪指令包括文件中的字节链,并应在其后加上引号的文件名,然后是可选的数字表达式,指定文件中的偏移量,并以冒号开头,然后(也可选地)-逗号和数字表达式,指定要包含的字节数(如果未指定计数,包括文件末尾的所有数据)。例如,file 'data.bin'
将整个文件包含为二进制数据,并且file 'data.bin':10h,4
将仅包含从偏移量10h开始的四个字节。
数据保留指令应仅跟随一个数字表达式,并且此值定义应保留多少个指定大小的像元。所有数据定义指令也接受该?
值,这意味着不应将此单元格初始化为任何值,并且其效果与使用数据保留指令的效果相同。未初始化的数据可能不会包含在输出文件中,因此应始终将其值视为未知。
表1.3数据指令
大小(字节) | 定义数据 | 预约资料 | ||||
---|---|---|---|---|---|---|
1 |
| rb | ||||
2 |
| rw | ||||
4 | dd | rd | ||||
6 |
|
| ||||
8 | dq | rq | ||||
10 | dt | rt |
1.2.3常数和标签
在数字表达式中,您也可以使用常量或标签代替数字。要定义常量或标签,应使用特定的指令。每个标签只能定义一次,并且可以从任何源位置访问它(甚至在定义之前)。常数可以多次重定义,但是在这种情况下,常数只有在定义后才能访问,并且始终等于使用它之前的最后一个定义中的值。当常量在源中仅定义一次时,就可以像在标签中一样从任何位置访问它。
常量的定义由常量的名称, =
字符和数字表达式组成,计算后将成为常量的值。该值始终在定义常数时计算。例如,您可以count
使用指令定义常量count = 17
,然后在汇编指令中使用它,例如 mov cx,count
-会mov cx,17
在编译过程中变为常量。
有多种定义标签的方法。最简单的是在冒号后面跟随标签的名称,该指令甚至可以跟在同一行中的其他指令之后。它定义了标签,其值等于其定义点的偏移量。此方法通常用于标记代码中的位置。另一种方法是在某些数据指令后跟随标签名(不带冒号)。它定义的标签值等于已定义数据开头的偏移量,并记住作为表1.3中为该data指令指定的单元格大小的数据的标签。
可以将标签视为等于已标记代码或数据的偏移量的值的常数。例如,当您使用带标签的指令定义数据时 char db 224
,要将此数据的偏移量放入BX寄存器中,则应使用 mov bx,char
指令,而将由char
标签寻址的字节值放入DL寄存器中,则应使用mov dl,[char]
(或mov dl,ptr char
)。但是,当您尝试汇编时mov ax,[char]
,它将导致错误,因为fasm会比较操作数的大小,该大小应该相等。您可以使用大小覆盖:来强制汇编该指令mov ax,word [char]
,但是请记住,该指令将从char
地址处读取两个字节,而该字节被定义为一个字节。
定义标签的最后一种也是最灵活的方法是使用label
指令。该指令后应跟标签名称,然后是可选的大小运算符(可以在冒号之前),然后是(可选)at
运算符和定义该标签定义地址的数字表达式。例如,label wchar word at char
将在地址处为16位数据定义一个新标签char
。现在,该指令mov ax,[wchar]
将在编译后与相同 mov ax,word [char]
。如果未指定地址,则label
伪指令将标签定义为当前偏移量。因此mov [wchar],57568
将复制两个字节,而mov [char],224
将一个字节复制到同一地址。
名称以点开头的标签被视为本地标签,其名称附加到最后一个全局标签的名称(名称以点以外的任何名称开头)以形成该标签的全名。因此,您可以在定义下一个全局标签之前的任何地方使用该标签的简称(以点开头),而在其他地方则必须使用全名。以两个点开头的标签是个例外-它们就像全局标签一样,但是它们不会成为局部标签的新前缀。
该@@
名称表示匿名标签,您可以在源代码中定义很多标签。符号@b
(或等效符号@r
)引用最近的匿名标签,符号@f
引用最近的匿名标签。这些特殊符号不区分大小写。
1.2.4数值表达式
在以上示例中,所有数值表达式均为简单数字,常数或标签。但是,通过在编译时使用算术或逻辑运算符进行计算,它们可能会更加复杂。表1.4中列出了所有这些运算符及其优先级值。具有较高优先级值的运算将首先被计算,您当然可以通过将表达式的某些部分放在括号中来更改此行为。的+
, -
, *
和/
是标准的算术运算, mod
计算从余数。的and
, or
, xor
, shl
, shr
,bsf
,bsr
和not
执行与这些名称的汇编指令相同的位逻辑运算。的rva
和plt
是执行不同种类的地址之间的转换的特殊一元运算符,它们只能用少量的输出格式被使用以及它们的含义可能会发生变化(参见2.4)。
算术和位逻辑计算通常像在无穷精度2 adic数字上进行处理一样,并且由于其局限性而无法执行所需的计算或者结果太大,汇编器会发出溢出错误信号编号以适合目标单位大小的有符号或无符号范围。
表达式中的数字默认情况下被视为十进制,二进制数字应b
在末尾附加字母,八进制数字应以o
字母结尾,十六进制数字应以0x
字符(如C语言)开头或以$
字符(如Pascal语言),否则应以h
字母结尾。在表达式中遇到的带引号的字符串也将转换为数字-第一个字符将成为数字的最低有效字节。
用作地址值的数字表达式还可以包含用于寻址的任何通用寄存器,可以将它们相加并乘以适当的值,这是x86体系结构指令所允许的。默认情况下,地址定义内的数值计算在假定目标大小与当前代码位数相同的情况下进行操作,即使生成的指令编码将使用不同的大小也是如此。
数值表达式中也可以使用一些特殊符号。首先是$
,它始终等于当前偏移量的值,而$$
等于当前寻址空间的基址。另一个是%
,它是使用某些特殊指令(请参见2.2)重复的部分代码中当前重复的次数,而在其他任何地方为零。还有一个%t
符号,它始终等于当前时间戳。
任何数值表达式也可以由科学计数形式的单个浮点值组成(平面汇编程序在编译时不允许任何浮点运算),它们可以f
以要识别的字母结尾,否则应至少包含.
或之一E
字符。因此1.0
, 1E0
和1f
定义相同的浮点值,而简单的1
定义整数值。
表1.4按优先级排列的算术和位逻辑运算符
优先 | 运营商 |
---|---|
0 | + - |
1 | * / |
2 | mod |
3 | and or xor |
4 | shl shr |
五 | not |
6 | bsf bsr |
7 | rva plt |
1.2.5跳跃和呼唤
任何跳转或呼叫指令的操作数不仅可以通过大小操作者,而且也由操作者指定的类型的跳跃的一个前面: short
,near
的far
。例如,当汇编器处于16位模式时,指令jmp dword [0]
将变为远跳转,而当汇编器处于32位模式时,则将变为近跳转。要强制将本指令区别对待,请使用jmp near dword [0]
或jmp far dword [0]
形式。
如果近跳转的操作数是立即数,则汇编程序将在可能的情况下生成此跳转指令的最短变体(但除非存在以下情况,否则不会在16位模式下创建32位指令或在32位模式下创建16位指令。大小运算符说明它)。通过指定跳转类型,您可以强制其始终生成长变体(例如jmp near 0
)或始终生成短变体,并在不可能的情况下以错误终止(例如jmp short 0
)。
1.2.6尺寸设置
当指令使用某些存储器寻址时,默认情况下,如果仅地址值适合该范围,则使用短位移生成最小形式的指令。可以 在方括号内的地址之前(或 操作符之后)使用word
或dword
运算符来覆盖此设置ptr
,这将迫使进行适当大小的长位移。如果地址与任何寄存器都不相关,则这些运算符还允许选择适当的绝对寻址模式。
指令adc
,add
,and
,cmp
, or
,sbb
,sub
和xor
与第一操作数是16位或32位是由在当所述第二操作数是即时值的范围为嵌合符号的8位的值缩短8位形式生成的默认。也可以通过将word
or dword
运算符放在立即值之前来覆盖它。类似的规则适用于imul
最后一个操作数为立即数的指令。
push
如果汇编器处于16位模式,则默认情况下将 立即数作为不带大小运算符的指令的操作数视为字值;如果汇编器处于32位模式,则将立即数视为双字值(此指令的较短8位形式)如果可能,请使用,word
或者使用dword
大小运算符强制push
以更长的格式生成指定大小的 指令。pushw
和pushd
助记符迫使汇编而不迫使它使用指令的较长形式,以产生16位或32位的代码。