汇编语言(四)——编程语法入门
作者:互联网
目录
0.第一个汇编程序
汇编程序几乎提供了全部信息的语言。程序员可以看到正在发生的所有事情,甚至包括 CPU 中的寄存器和标志!但是,在拥有这种能力的同时,程序员必须负责处理数据表示的细节和指令的格式。程序员工作在一个具有大量详细信息的层次。现在以一个简单的汇编语言程序为例,来了解其工作过程。
下面这个程序是执行两个数相加,并将结果保存在寄存器中的代码:(没运行起来)
.data ;此为数据区
sum DWORD 0 ;定义名为sum的变量
.code ;此为代码区
main PROC
mov eax,5 ;将数字5送入而eax寄存器
add eax,6 ;eax寄存器加6
mox sum,eax
INVOKE ExitProcess,0 ;结束程序
main ENDP
程序员可能熟悉的类型相比它们没有那么具体,比如 int、double、float 等等。这些关键字只限制大小,并不检查变量中存放的内容。记住,程序员拥有完全控制权。那些被 .code 和 .data 伪指令标记的代码和数据区,被称为段。即,程序有代码段和数据段。
1.语言常量
这里使用 Microsoft 语法符号。方括号内的元素是可选的;大括号内的元素用 | 符号分隔,且必须要选择其中一个元素;斜体字标识的是有明确定义或说明的元素。
(1)整数常量
整数常量(integer literal)(又称为整型常量(integer constant))由一个可选前置符号、一个或多个数字,以及一个指明其基数的可选基数字符构成:
[{+|-}] digits [radix]
比如 26 就是一个有效的整数常量。它没有基数,所以假设其是十进制形式。如果想要表示十六进制数 26,就将其写为 26h。同样,数字 1101 可以被看做是十进制值,除非在其末尾添加“b”,使其成为 1101b (二进制)。下表列出了可能的基数值:
h | 十六进制 | r | 编码实数 |
q/o | 八进制 | t | 十进制(备用) |
d | 十进制 | y | 二进制(备用) |
b | 二进制 |
以字母开头的十六进制数必须加个前置 0,以防汇编器将其解释为标识符。整型常量表达式 (constant integer expression) 是一种算术表达式,它包含了整数常量和算术运算符。每个表达式的计算结果必须是一个整数,并可用 32 位 (从 0 到 FFFFFFFFh) 来存放。下表列出了算术运算符,并按照从高 (1) 到低 (4) 的顺序给出了它们的优先级。对整型常量表达式而言很重要的是,要意识到它们只在汇编时计算。这里将它们简称为 整数表达式:
运算符 | 名称 | 优先级 |
---|---|---|
() | 圆括号 | 1 |
+,- | 一元加、减 | 2 |
*, / | 乘、除 | 3 |
MOD | 取模 | 3 |
+, - | 加、减 | 4 |
(2)实数常量
实数常量(real number literal)(又称为浮点数常量(floating-point literal))用于表示十进制实数和编码(十六进制)实数。十进制实数包含一个可选符号,其后跟随一个整数,一个十进制小数点,一个可选的表示小数部分的整数和一个可选的指数:
实数常量(real number literal)(又称为浮点数常量(floating-point literal))用于表示十进制实数和编码(十六进制)实数。十进制实数包含一个可选符号,其后跟随一个整数,一个十进制小数点,一个可选的表示小数部分的整数,和一个可选的指数:
[sign]integer.[integer] [exponent]
符号和指数的格式如下:
sign {+,-}
exponent E[{+,-}]integer
(3)字符常量
字符常量 (character literal) 是指,用单引号或双引号包含的一个字符。汇编器在内存中保存的是该字符二进制 ASCII 码的数值。例如:
'A'
"d"
字符串常量 (string literal) 是用单引号或双引号包含的一个字符 ( 含空格符 ) 序列,例如:
'ABC'
'X'
"Good night, Gracie"
嵌套引号也是被允许的,和字符常量以整数形式存放一样,字符串常量在内存中的保存形式为整数字节数值序列。
2.保留字
保留字(reserved words)有特殊意义并且只能在其正确的上下文中使用。默认情况下,保留字是没有大小写之分的。比如,MOV 与 mov、Mov 是相同的。保留字有不同的类型:
- 指令助记符,如 MOV、ADD 和 MUL。
- 寄存器名称。
- 伪指令,告诉汇编器如何汇编程序。
- 属性,提供变量和操作数的大小与使用信息。例如 BYTE 和 WORD。
- 运算符,在常量表达式中使用。
- 预定义符号,比如 @data,它在汇编时返回常量的整数值。
下表是常用的保留字列表。
$ | PARITY? | DWORD | STDCALL |
? | PASCAL | FAR | SWORD |
@B | QWORD | FAR16 | SYSCALL |
@F | REAL4 | FORTRAN | TBYTE |
ADDR | REAL8 | FWORD | VARARG |
BASIC | REAL10 | NEAR | WORD |
BYTE | SBYTE | NEAR16 | ZERO? |
C | SDORD | OVERFLOW? | |
CARRY? | SIGN? |
3.标识符
标识符(identifier)是由程序员选择的名称,它用于标识变量、常数、子程序和代码标签。
标识符的形成有一些规则:
- 可以包含 1 到 247 个字符。
- 不区分大小写。
- 第一个字符必须为字母 (A---Z, a---z) A 下划线 (_)、@、? 或 $。其后的字符也可以是数字。
- 标识符不能与汇编器保留字相同。
一般情况下,应避免用符号 @ 和下划线作为第一个字符,因为它们既用于汇编器,也用于高级语言编译器。
4.伪指令
伪指令 (directive) 是嵌入源代码中的命令,由汇编器识别和执行。伪指令不在运行时执行,但是它们可以定义变量、宏和子程序;为内存段分配名称,执行许多其他与汇编器相关的日常任务。默认情况下,伪指令不区分大小写。例如,.data,.DATA 和 .Data 是相同的。
汇编器伪指令的一个重要功能是定义程序区段,也称为段 (segment)。程序中的段具有不同的作用。段可以用于定义变量,并用 .DATA 伪指令进行标识;.CODE 伪指令标识的程序区段包含了可执行的指令;.STACK 伪指令标识的程序区段定义了运行时堆栈,并设置了其大小,例如:.stack 100h;
5.指令
指令(instruction)是一种语句,它在程序汇编编译时变得可执行。汇编器将指令翻译为机器语言字节,并且在运行时由 CPU 加载和执行。一条指令有四个组成部分:
- 标号(可选)
- 指令助记符(必需)
- 操作数(通常是必需的)
- 注释(可选)
不同部分的位置安排如下所示:
[label: ] mnemonic [operands] [;comment]
(1)标号
标号(label)是一种标识符,是指令和数据的位置标记。标号位于指令的前端,表示指令的地址。同样,标号也位于变量的前端,表示变量的地址。标号有两种类型:数据标号和代码标号。数据标号标识变量的位置,它提供了一种方便的手段在代码中引用该变量。汇编器为每个标号分配一个数字地址。可以在一个标号后面定义多个数据项。程序代码区(指令所在区段)的标号必须用冒号(:)结束。代码标号用作跳转和循环指令的目标。代码标号可以与指令在同一行上,也可以自己独立一行。
(2) 指令助记符
指令助记符(instruction mnemonic)是标记一条指令的短单词。在英语中,助记符是帮助记忆的方法。相似地,汇编语言指令助记符,如 mov, add 和 sub,给出了指令执行操作类型的线索。下面是一些指令助记符的例子:
助记符 | 说明 | 助记符 | 说明 |
---|---|---|---|
MOV | 传送(分配)数值 | MUL | 两个数值相乘 |
ADD | 两个数值相加 | JMP | 跳转到一个新位置 |
SUB | 从一个数值中减去另一个数值 | CALL | 调用一个子程序 |
(3)操作数
操作数是指令输入输出的数值。汇编语言指令操作数的个数范围是 0〜3 个,每个操作数可以是寄存器、内存操作数、整数表达式和输入输岀端口。操作数有固有顺序。当指令有多个操作数时,通常第一个操作数被称为目的操作数,第二个操作数被称为源操作数(source operand)。一般情况下,目的操作数的内容由指令修改。
(4)注释
注释是程序编写者与阅读者交流程序设计信息的重要途径。程序清单的开始部分通常包含如下信息:
- 程序目标的说明
- 程序创建者或修改者的名单
- 程序创建和修改的日期
- 程序实现技术的说明
注释有两种指定方法:
- 单行注释,用分号(;)开始。汇编器将忽略在同一行上分号之后的所有字符。
- 块注释,用 COMMENT 伪指令和一个用户定义的符号开始。汇编器将忽略其后所有的文本行,直到相同的用户定义符号出现为止。
(5) NOP(空操作)指令
最安全(也是最无用)的指令是 NOP(空操作)。它在程序空间中占有一个字节,但是不做任何操作。它有时被编译器和汇编器用于将代码对齐到有效的地址边界。
6.汇编器以及汇编流程
用汇编语言编写的源程序不能直接在其目标计算机上执行,必须通过翻译或汇编将其转换为可执行代码。实际上,汇编器与编译器 (compiler) 很相似,编译器是一类程序,用于将 C++ 或 Java 程序翻译为可执行代码。汇编器生成包含机器语言的文件,称为目标文件 (object file)。这个文件还没有准备好执行,它还需传递给一个被称为链接器 (linker) 的程序,从而生成可执行文件 (executable file)。这个文件就准备好在操作系统命令提示符下执行。
- 步骤1:编程者用文本编辑器 (text editor) 创建一个 ASCII 文本文件,称之为源文件。
- 步骤2:汇编器读取源文件,并生成目标文件,即对程序的机器语言翻译。或者,它也会生成列表文件。只要出现任何错误,编程者就必须返回步骤 1,修改程序。
- 步骤3:链接器读取并检查目标文件,以便发现该程序是否包含了任何对链接库中过程的调用。链接器从链接库中复制任何被请求的过程,将它们与目标文件组合,以生成可执行文件。
- 步骤4:操作系统加载程序将可执行文件读入内存,并使 CPU 分支到该程序起始地址,然后程序开始执行。
列表文件 (listing file) 包括了程序源文件的副本,再加上行号、每条指令的数字地址、每条指令的机器代码字节(十六进制)以及符号表。符号表中包含了程序中所有标识符的名称、段和相关信息。
7.数据定义详解
汇编器识别一组基本的内部数据类型(intrinsic data type),按照数据大小(字节、字、双字等等)、是否有符号、是整数还是实数来描述其类型。序员用 SDWORD 告诉读程序的人,这个值是有符号的,但是,对于汇编器来说这不是强制性的。汇编器只评估操作数的大小。因此,举例来说,程序员只能将 32 位整数指定为 DWORD、SDWORD 或者 REAL4 类型。
下表给出了全部内部数据类型的列表,有些表项中的 IEEE 符号指的是 IEEE 计算机学会出版的标准实数格式。
类型 | 用法 |
---|---|
BYTE | 8 位无符号整数,B 代表字节 |
SBYTE | 8 位有符号整数,S 代表有符号 |
WORD | 16 位无符号整数 |
SWORD | 16 位有符号整数 |
DWORD | 32 位无符号整数,D 代表双(字) |
SDWORD | 32 位有符号整数,SD 代表有符号双(字) |
FWORD | 48 位整数(保护模式中的远指针) |
QWORD | 64 位整数,Q 代表四(字) |
TBYTE | 80 位(10 字节)整数,T 代表 10 字节 |
REAL4 | 32 位(4 字节)IEEE 短实数 |
REAL8 | 64 位(8 字节)IEEE 长实数 |
REAL10 | 80 位(10 字节)IEEE 扩展实数 |
数据定义语句(data definition statement)在内存中为变量留岀存储空间,并赋予一个可选的名字。数据定义语句根据内部数据类型(上表)定义变量。数据定义语法如下所示:
[name] directive initializer [,initializer]...
- 名字:分配给变量的可选名字必须遵守标识符规范。
- 伪指令:数据定义语句中的伪指令可以是 BYTE、WORD、DWORD、SBTYE、SWORD 或其他在上表中列出的类型。此外,它还可以是传统数据定义伪指令,如下表所示。
伪指令 | 用法 | 伪指令 | 用法 |
---|---|---|---|
DB | 8位整数 | DQ | 64 位整数或实数 |
DW | 16 位整数 | DT | 定义 80 位(10 字节)整数 |
DD | 32 位整数或实数 |
MODEL 伪指令,它告诉汇编程序用的是哪一种存储模式。32 位程序总是使用平面(flat)存储模式,它与处理器的保护模式相关联。关键字 stdcall 在调用程序时告诉汇编器,怎样管理运行时堆栈。然后是 .STACK 伪指令,它告诉汇编器应该为程序运行时堆栈保留多少内存字节。ENDP 伪指令标记一个过程的结束。
(1)定义 BYTE 和 SBYTE 数据
BYTE(定义字节)和 SBYTE(定义有符号字节)为一个或多个无符号或有符号数值分配存储空间。每个初始值在存储时,都必须是 8 位的。问号(?)初始值使得变量未初始化,这意味着在运行时分配数值到该变量。如果同一个数据定义中使用了多个初始值,那么它的标号只指出第一个初始值的偏移量。并不是所有的数据定义都要用标号。比如,在 list 后面继续添加字节数组,就可以在下一行定义它们:在单个数据定义中,其初始值可以使用不同的基数。字符和字符串常量也可以自由组合。定义一个字符串,要用单引号或双引号将其括起来。最常见的字符串类型是用一个空字节(值为0)作为结束标记,称为以空字节结束的字符串,很多编程语言中都使用这种类型的字符串:每个字符占一个字节的存储空间。对于字节数值必须用逗号分隔的规则而言,字符串是一个例外。DUP 操作符使用一个整数表达式作为计数器,为多个数据项分配存储空间。在为字符串或数组分配存储空间时,这个操作符非常有用,它可以使用初始化或非初始化数据。
(2)定义 WORD 和 SWORD 数据
WORD(定义字)和 SWORD(定义有符号字)伪指令为一个或多个 16 位整数分配存储空间。也可以使用传统的 DW 伪指令。
(3)定义 DWORD 和 SDWORD 数据
DWORD(定义双字)和 SDWORD(定义有符号双字)伪指令为一个或多个 32 位整数分配存储空间,传统的 DD 伪指令也可以用来定义双字数据。DWORD 还可以用于声明一种变量,这种变量包含的是另一个变量的 32 位偏移量。
(4)定义 QWORD 数据
QWORD(定义四字)伪指令为 64 位(8 字节)数值分配存储空间,传统的 DQ 伪指令也可以用来定义四字数据。
(5)定义压缩 BCD(TBYTE)数据
Intel 把一个压缩的二进制编码的十进制(BCD, Binary Coded Decimal)整数存放在一个 10 字节的包中。每个字节(除了最高字节之外)包含两个十进制数字。在低 9 个存储字节中,每半个字节都存放了一个十进制数字。最高字节中,最高位表示该数的符号位。如果最高字节为 80h,该数就是负数;如果最高字节为 00h,该数就是正数。整数的范围是 -999 999 999 999 999 999 到 +999 999 999 999 999 999。MASM 使用 TBYTE 伪指令来定义压缩 BCD 变量。常数初始值必须是十六进制的,因为,汇编器不会自动将十进制初始值转换为 BCD 码。如果想要把一个实数编码为压缩 BCD 码,可以先用 FLD 指令将该实数加载到浮点寄存器堆栈,再用 FBSTP 指令将其转换为压缩 BCD 码,该指令会把数值舍入到最接近的整数。
(6)定义浮点类型
REAL4 定义 4 字节单精度浮点变量。REAL8 定义 8 字节双精度数值,REAL10 定义 10 字节扩展精度数值。每个伪指令都需要一个或多个实常数初始值。下表描述了标准实类型的最少有效数字个数和近似范围:
数据类型 | 有效数字 | 近似范围 |
---|---|---|
短实数 | 6 | 1.18x 10-38 to 3.40 x 1038 |
长实数 | 15 | 2.23 x 10-308 to 1.79 x 10308 |
扩展精度实数 | 19 | 3.37 x 10-4932 to 1.18 x 104932 |
DD、DQ 和 DT 伪指令也可以定义实数。
(7)等号(=)伪指令
等号伪指令(equal-sign directive)把一个符号名称与一个整数表达式连接起来。通常,表达式是一个 32 位的整数值。当程序进行汇编时,在汇编器预处理阶段,所有出现的 name 都会被替换为 expression。假设下面的语句出现在一个源代码文件开始的位置。
最重要的符号之一被称为当前地址计数器(current location counter),表示为 $。用“=”定义的符号,在同一程序内可以被重新定义。
8.数组和字符串长度
显式声明数组的大小会导致编程错误,尤其是如果后续还会插入或删除数组元素。声明数组大小更好的方法是,让汇编器来计算这个值。$ 运算符(当前地址计数器)返回当前程序语句的偏移量。当要计算元素数量的数组中包含的不是字节时,就应该用数组总的大小(按字节计)除以单个元素的大小。
9.常用伪指令
(1)EQU伪指令
EQU 伪指令把一个符号名称与一个整数表达式或一个任意文本连接起来,它有 3 种格式:
name EQU expression
name EQU symbol
name EQU <text>
第一种格式中,expression 必须是一个有效整数表达式。第二种格式中,symbol 是一个已存在的符号名称,已经用 = 或 EQU 定义过了。第三种格式中,任何文本都可以岀现在<...>内。当汇编器在程序后面遇到 name 时,它就用整数值或文本来代替符号。在定义非整数值时,EQU 非常有用。
(2)TEXTEQU伪指令
TEXTEQU 伪指令,类似于 EQU,创建了文本宏(text macro)。它有 3 种格式:第一种为名称分配的是文本;第二种分配的是已有文本宏的内容;第三种分配的是整数常量表达式:
name TEXTEQU <text>
name TEXTEQU textmacro
name TEXTEQU %constExpr
标签:汇编器,定义,字节,汇编语言,编程,伪指令,整数,指令,入门 来源: https://blog.csdn.net/qq_35789421/article/details/113722447