汇编语言基础(一)
汇编笔记是根据C语言中文网完成的
汇编语言以隐晦难懂而著名,但是本教程从另一个角度来看它——它是一种几乎提供了全部信息的语言。程序员可以看到正在发生的所有事情,甚至包括 CPU 中的寄存器和标志!
本章侧重于 Microsoft MASM 汇编程序的基本组成部分。通过学习将会了解到如何定义常数和变量,数字和字符常量的标准格式,以及怎样汇编并运行你的第一个程序。
第一个汇编语言程序
汇编语言是一种几乎提供了全部信息的语言。程序员可以看到正在发生的所有事情,甚至包括 CPU 中的寄存器和标志!
但是,在拥有这种能力的同时,程序员必须负责处理数据表示的细节和指令的格式。程序员工作在一个具有大量详细信息的层次。现在以一个简单的汇编语言程序为例,来了解其工作过程。
程序执行两个数相加,并将结果保存在寄存器中。程序名称为 AddTwo:
1 | main PROC |
现在按照一次一行代码的方法来仔细查看这段程序:
- 第 1 行开始 main 程序(主程序),即程序的入口;
- 第 2 行将数字 5 送入 eax 寄存器;
- 第 3 行把 6 加到 EAX 的值上,得到新值 11;
- 第 5 行调用 Windows 服务(也被称为函数)ExitProcess 停止程序,并将控制权交还给操作系统;
- 第 6 行是主程序结束的标记。
程序中包含的注释,它总是用分号开头。程序的顶部省略了一些声明,稍后会予以说明,不过从本质上说,这是一个可以用的程序。
添加一个变量
现在让这个程序变得有趣些,将加法运算的结果保存在变量 sum 中。要实现这一点,需要增加一些标记,或声明,用来标识程序的代码和数据区:
1 | .data ;此为数据区 |
变量 sum 在第 2 行进行了声明,其大小为 32 位,使用了==关键字 DWORD==。汇编语言中有很多这样的大小关键字,其作用或多或少与数据类型一样。
但是与程序员可能熟悉的类型相比它们没有那么具体,比如 int、double、float 等等。**这些关键字只限制大小,并不检查变量中存放的内容。**记住,程序员拥有完全控制权。
顺便说一下,那些被 .code 和 .data 伪指令标记的代码和数据区,被称为段。即,程序有代码段和数据段。
汇编语言常量
常量(constant)是程序中使用的一个确定数值,在汇编阶段就可以确定,直接编码于指令代码中,不是保存在存储器中可变的变量,因为是编码在指令中的量,和指令一起存储了,所以不用单独开辟主存空间,所以也就没法动态改变它了,这也正是高级语言常量无法修改的原因。
整数常量
整数常量(integer literal)(又称为整型常量(integer constant))由一个可选前置符号、一个或多个数字,以及一个指明其基数的可选基数字符构成。
比如 26 就是一个有效的整数常量。它没有基数,所以假设其是十进制形式。如果想要表示十六进制数 26,就将其写为 26h。同样,数字 1101 可以被看做是十进制值,除非在其末尾添加“b”,使其成为 1101b (二进制)。下表列出了可能的基数值:
h | 十六进制 | r | 编码实数 |
---|---|---|---|
q/o | 八进制 | t | 十进制(备用) |
d | 十进制 | y | 二进制(备用) |
b | 二进制 |
下面这些整数常量声明了各种基数。每行都有注释:
1 | 26 ;十进制 |
以字母开头的十六进制数必须加个前置 0,以防汇编器将其解释为标识符。
整型常量表达式
整型常量表达式 (constant integer expression) 是一种算术表达式,它包含了整数常量和算术运算符。每个表达式的计算结果必须是一个整数,并可用 32 位 (从 0 到 FFFFFFFFh) 来存放。
下表列出了算术运算符,并按照从高 (1) 到低 (4) 的顺序给出了它们的优先级。对整型常量表达式而言很重要的是,要意识到它们只在汇编时计算。这里将它们简称为 整数表达式。
运算符 | 名称 | 优先级 |
---|---|---|
() | 圆括号 | 1 |
+,- | 一元加、减 | 2 |
*, / | 乘、除 | 3 |
MOD | 取模 | 3 |
+, - | 加、减 | 4 |
在表达式中使用圆括号来表明操作顺序,那么就不用去死记运算符优先级。
实数常量
实数常量(real number literal)(又称为浮点数常量(floating-point literal))用于表示十进制实数和编码(十六进制)实数。十进制实数包含一个可选符号,其后跟随一个整数,一个十进制小数点,一个可选的表示小数部分的整数,和一个可选的指数。
下面是一些有效的十进制实数:
1 | 2. |
至少需要一个数字和一个十进制小数点。
字符常量
字符常量 (character literal) 是指,用单引号或双引号包含的一个字符。汇编器在内存中保存的是该字符二进制 ASCII 码的数值。例如:
1 | 'A' |
表明字符常量在内部保存为整数,使用的是 ASCII 编码序列。因此,当编写字符常量“A”时,它在内存中存放的形式为数字 65 ( 或 41H)。
字符串常量
字符串常量 (string literal) 是用单引号或双引号包含的一个字符 ( 含空格符 ) 序列:
1 | 'ABC' |
嵌套引号也是被允许的,使用方法如下例所示:
1 | "This isn't a test" |
和字符常量以整数形式存放一样,字符串常量在内存中的保存形式为整数字节数值序列。例如,字符串常量“ABCD”就包含四个字节 41H、42H、43H、44H。
汇编语言保留字
**保留字(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? |
汇编语言标识符及其命名规则
标识符(identifier)是由程序员选择的名称,它用于标识变量、常数、子程序和代码标签。
标识符的形成有一些规则:
- 可以包含 1 到 247 个字符。
- 不区分大小写。
- 第一个字符必须为字母 (A—Z, a—z) A 下划线 (_)、@、? 或 $。其后的字符也可以是数字。
- 标识符不能与汇编器保留字相同。
一般情况下,应避免用符号 @ 和下划线作为第一个字符,因为它们既用于汇编器,也用于高级语言编译器。
汇编语言伪指令
伪指令 (directive) 是嵌入源代码中的命令,由汇编器识别和执行。伪指令不在运行时执行,但是它们可以定义变量、宏和子程序;为内存段分配名称,执行许多其他与汇编器相关的日常任务。
默认情况下,伪指令不区分大小写。例如,.data,.DATA 和 .Data 是相同的。
下面的例子有助于说明伪指令和指令的区别。DWORD 伪指令告诉汇编器在程序中为一个双字变量保留空间。另一方面,MOV 指令在运行时执行,将 myVar 的内容复制到 EAX 寄存器中:
1 | myVar DWORD 26 |
尽管 Intel 处理器所有的汇编器使用相同的指令集,但是通常它们有着不同的伪指令。比如,Microsoft 汇编器的 REPT 伪指令对其他一些汇编器就是无法识别的。
定义段
汇编器伪指令的一个重要功能是定义程序区段,也称为段 (segment)。程序中的段具有不同的作用。如下面的例子,一个段可以用于定义变量,并用==.DATA== 伪指令进行标识:
1 | .data |
.CODE 伪指令标识的程序区段包含了可执行的指令:
1 | .code |
.STACK 伪指令标识的程序区段定义了运行时堆栈,并设置了其大小:
1 | .stack 100h |
汇编语言指令详解
指令(instruction)是一种语句,它在程序汇编编译时变得可执行。汇编器将指令翻译为机器语言字节,并且在运行时由 CPU 加载和执行。
一条指令有四个组成部分:
- 标号(可选)
- 指令助记符(必需)
- 操作数(通常是必需的)
- 注释(可选)
1.标号
标号(label)是一种标识符,是指令和数据的位置标记。标号位于指令的前端,表示指令的地址。同样,标号也位于变量的前端,表示变量的地址。标号有两种类型:数据标号和代码标号。
==数据标号==标识变量的位置,它提供了一种方便的手段在代码中引用该变量。比如,下面定义了一个名为 count 的变量:
1 | count DWORD 100 |
汇编器为每个标号分配一个数字地址。可以在一个标号后面定义多个数据项。在下面的例子中,array 定义了第一个数字(1024)的位置,其他数字在内存中的位置紧随其后:
1 | array DWORD 1024, 2048 |
程序代码区(CODES SEGMENT,指令所在区段)的标号必须用冒号(:)结束。代码标号用作跳转和循环指令的目标。例如,下面的 JMP 指令创建一个循环,将程序控制传递给标号 target 标识的位置:
1 | target: |
**代码标号**可以与指令在同一行上,也可以自己独立一行:
1 | L1: mov ax, bx |
标号命名规则要求,只要每个标号在其封闭子程序页中是唯一的,那么就可以多次使用相同的标号。
2.指令助记符
指令助记符(instruction mnemonic)是标记一条指令的短单词。在英语中,助记符是帮助记忆的方法。相似地,汇编语言指令助记符,如 mov, add 和 sub,给出了指令执行操作类型的线索。下面是一些指令助记符的例子:
助记符 | 说明 | 助记符 | 说明 |
---|---|---|---|
MOV | 传送(分配)数值 | MUL | 两个数值相乘 |
ADD | 两个数值相加 | JMP | 跳转到一个新位置 |
SUB | 从一个数值中减去另一个数值 | CALL | 调用一个子程序 |
3.操作数
操作数是指令输入输出的数值。汇编语言指令操作数的个数范围是 0〜3 个,每个操作数可以是寄存器、内存操作数、整数表达式和输入输岀端口。
生成内存操作数有不同的方法,比如,使用变量名、带方括号的寄存器等。变量名暗示了变量地址,并指示计算机使用给定地址的内存内容。下表列出了一些操作数示例:
示例 | 操作数类型 | 示例 | 操作数类型 |
---|---|---|---|
96 | 整数常量 | eax | 寄存器 |
2+4 | 整数表达式 | count | 内存 |
现在来考虑一些包含不同个数操作数的汇编语言指令示例。比如,STC 指令没有操作数:
1 | stc ;进位标志位置 1 |
INC 指令有一个操作数:
1 | inc eax ;EAX 加 1 |
MOV 指令有两个操作数:
1 | mov count, ebx ;将 EBX 传送给变量 count |
操作数有固有顺序。当指令有多个操作数时,通常第一个操作数被称为目的操作数,第二个操作数被称为源操作数(source operand)。
一般情况下,目的操作数的内容由指令修改。比如,在 mov 指令中,数据就是从源操作数复制到目的操作数。
IMUL 指令有三个操作数,第一个是目的操作数,第二个和第三个是进行乘法的源操作数:
1 | imul eax,ebx,5 ;EBX 与 5 相乘,结果存放在 EAX 寄存器中。 |
4.注释
注释有两种指定方法:
- 单行注释,用分号(;)开始。汇编器将忽略在同一行上分号之后的所有字符。
- 块注释,用 COMMENT 伪指令和一个用户定义的符号开始。汇编器将忽略其后所有的文本行,直到相同的用户定义符号出现为止。
块注释例子:
1 | COMMENT ! |
其他符号也可以使用,只要该符号不出现在注释行中:
1 | COMMENT & |
汇编笔记是根据C语言中文网完成的