汇编语言(六)——操作数

0.前言

x86 的指令格式为:

[label:] mnemonic [operands][ ;comment ]

指令包含的操作数个数可以是:0 个,1 个,2 个或 3 个。这里,为了清晰起见,省略掉标号和注释:

mnemonic

mnemonic [destination]

mnemonic [destination] , [source]

mnemonic [destination] , [source-1] , [source-2]

操作数有 3 种基本类型:

  • 立即数——用数字文本表达式

  • 寄存器操作数——使用 CPU 内已命名的寄存器

  • 内存操作数——引用内存位置

下表说明了标准操作数类型,它使用了简单的操作数符号(32 位模式下),这些符号来自 Intel 手册并进行了改编。本教程将用这些符号来描述每条指令的语法。

操作数 说明
reg8 8 位通用寄存器:AH、AL、BH、BL、CH、CL、DH、DL
reg16 16 位通用寄存器:AX、BX、CX、DX、SI、DI、SP、BP
reg32 32 位通用寄存器:EAX、EEX、ECX、EDX、ESI、EDI、ESP、EBP
reg 通用寄存器
sreg 16 位段寄存器:CS、DS、SS、ES、FS、GS
imm 8 位、16 位或 32 位立即数
imm8 8 位立即数,字节型数值
imm16 16 位立即数,字类型数值
imm32 32 位立即数,双字型数值
reg/mem8 8 位操作数,可以是 8 位通用寄存器或内存字节
reg/mem16 16 位立即数,可以是 16 位通用寄存器或内存字
reg/mem32 32 位立即数,可以是 32 位通用寄存器或内存双字
mem 8位、16 位或 32 位内存操作数

变量名引用的是数据段内的偏移量。可以编写指令,通过内存操作数的地址来解析(查找)这些操作数。另一种表示法。一些程序员更喜欢使用下面这种直接操作数的表达方式。因为,括号意味着解析操作。

mov al, [var1]

MASM 允许这种表示法,因此只要愿意就可以在程序中使用。由于多数程序(包括 Microsoft 的程序)印刷时都没有用括号,所以,本书只在出现算术表达式时才使用这种带括号的表示法:

mov al,[var1 + 5]

1.MOV指令

MOV 指令将源操作数复制到目的操作数。作为数据传送(data transfer)指令,它几乎用在所有程序中。在它的基本格式中,第一个操作数是目的操作数,第二个操作数是源操作数:

MOV destination,source

其中,目的操作数的内容会发生改变,而源操作数不会改变。

在几乎所有的汇编语言指令中,左边的操作数是目标操作数,而右边的操作数是源操作数。只要按照如下原则,MOV 指令使用操作数是非常灵活的。

  • 两个操作数必须是同样的大小。
  • 两个操作数不能同时为内存操作数。
  • 指令指针寄存器(IP、EIP 或 RIP)不能作为目标操作数。

单条 MOV 指令不能用于直接将数据从一个内存位置传送到另一个内存位置。相反,在将源操作数的值赋给内存操作数之前,必须先将该数值传送给一个寄存器。在将整型常数复制到一个变量或寄存器时,必须考虑该常量需要的最少字节数。尽管 MOV 指令不能直接将较小的操作数复制到较大的操作数中,但是程序员可以想办法解决这个问题。假设要将 count(无符号,16 位)传送到 ECX(32 位),可以先将 ECX 设置为 0,然后将 count 传送到 CX。

MOVZX 指令(进行全零扩展并传送)将源操作数复制到目的操作数,并把目的操作数 0 扩展到 16 位或 32 位。这条指令只用于无符号整数,有三种不同的形式:

MOVZX reg32,reg/mem8
MOVZX reg32,reg/mem16
MOVZX reg16,reg/mem8

在三种形式中,第一个操作数(寄存器)是目的操作数,第二个操作数是源操作数。注意,源操作数不能是常数。

MOVSX 指令(进行符号扩展并传送)将源操作数内容复制到目的操作数,并把目的操作数符号扩展到 16 位或 32 位。这条指令只用于有符号整数,有三种不同的形式:

MOVSX reg32, reg/mem8
MOVSX reg32, reg/mem16
MOVSX reg16, reg/mem8

操作数进行符号扩展时,在目的操作数的全部扩展位上重复(复制)长度较小操作数的最高位。

1.LAHF指令

LAHF(加载状态标志位到 AH)指令将 EFLAGS 寄存器的低字节复制到 AH。被复制的标志位包括:符号标志位、零标志位、辅助进位标志位、奇偶标志位和进位标志位。使用这条指令,可以方便地把标志位副本保管在变量中。

2.SAHF指令

SAHF(保存 AH 内容到状态标志位)指令将 AH 内容复制到 EFLAGS(或 RFLAGS)寄存器低字节。

3.XCHG指令

XCHG(交换数据)指令交换两个操作数内容。该指令有三种形式:

XCHG reg, reg
XCHG reg, mem
XCHG mem, reg

4.直接偏移量操作数

变量名加上一个位移就形成了一个直接 - 偏移量操作数。这样可以访问那些没有显式标记的内存位置。在 16 位的字数组中,每个数组元素的偏移量比前一个多 2 个字节。同样,如果是双字数组,则第一个元素偏移量加 4 才能指向第二个元素。

5.INC 和 DEC 指令

INC(增加)和DEC(减少)指令分别表示寄存器或内存操作数加 1 和减 1。语法如下所示:

INC reg/mem
DEC reg/mem

根据目标操作数的值,溢岀标志位、符号标志位、零标志位、辅助进位标志位、进位标志位和奇偶标志位会发生变化。INC 和 DEC 指令不会影响进位标志位(这还真让人吃惊)。

6.运算指令

ADD 指令将长度相同的源操作数和目的操作数进行相加操作。语法如下:

ADD dest,source

SUB 指令从目的操作数中减去源操作数。指令语法如下:

SUB dest, source

NEG(非)指令通过把操作数转换为其二进制补码,将操作数的符号取反。下述操作数可以用于该指令:

NEG reg
NEG mem

提示:将目标操作数按位取反再加 1,就可以得到这个数的二进制补码。

标志位:进位标志位、零标志位、符号标志位、溢出标志位、辅助进位标志位和奇偶标 志位根据存入目标操作数的数值进行变化。检查算术运算结果使用的是 CPU 状态标志位的值,同时,这些值还可以触发条件分支指令,即基本的程序逻辑工具。下面是对状态标志位的简要概述:

  • 进位标志位意味着无符号整数溢出。比如,如果指令目的操作数为 8 位,而指令产生的结果大于二进制的 1111 1111,那么进位标志位置 1。
  • 溢出标志位意味着有符号整数溢出。比如,指令目的操作数为 16 位,但其产生的负数结果小于十进制的 -32 768,那么溢出标志位置 1。
  • 零标志位意味着操作结果为 0。比如,如果两个值相等的操作数相减,则零标志位置 1。
  • 符号标志位意味着操作产生的结果为负数。如果目的操作数的最高有效位(MSE)置 1,则符号标志位置 1。
  • 奇偶标志位是指,在一条算术或布尔运算指令执行后,立即判断目的操作数最低有效字节中 1 的个数是否为偶数。
  • 辅助进位标志位置 1,意味着目的操作数最低有效字节中位 3 有进位。

7.OFFSET运算符

OFFSET 运算符返回数据标号的偏移量。这个偏移量按字节计算,表示的是该数据标号距离数据段起始地址的距离。

8.ALIGN伪指令

ALIGN 伪指令将一个变量对齐到字节边界、字边界、双字边界或段落边界。语法如下:

ALIGN bound

9.PTR运算符

PTR 运算符可以用来重写一个已经被声明过的操作数的大小类型。只要试图用不同于汇编器设定的大小属性来访问操作数,那么这个运算符就是必需的。

10.TYPE运算符

TYPE 运算符返回变量单个元素的大小,这个大小是以字节为单位计算的。

11.LENGTHOF运算符

LENGTHOF 运算符计算数组中元素的个数,元素个数是由数组标号同一行出现的数值来定义的。如果数组定义中出现了嵌套的 DUP 运算符,那么 LENGTHOF 返回的是两个数值的乘积。如果数组定义占据了多个程序行,那么 LENGTHOF 只针对第一行定义的数据。

12.LABEL伪指令

LABEL 伪指令可以插入一个标号,并定义它的大小属性,但是不为这个标号分配存储空间。LABEL 中可以使用所有的标准大小属性,如 BYTE、WORD、DWORD、QWORD 或 TBYTE。LABEL 常见的用法是,为数据段中定义的下一个变量提供不同的名称和大小属性。

13.间接寻址

直接寻址很少用于数组处理,因为,用常数偏移量来寻址多个数组元素时,直接寻址不实用。反之,会用寄存器作为指针(称为间接寻址)并控制该寄存器的值。如果一个操作数使用的是间接寻址,就称之为间接操作数。任何一个 32 位通用寄存器(EAX、EBX、ECX、EDX、ESI、EDI、EBP 和 ESP)加上括号就能构成一个间接操作数。寄存器中存放的是数据的地址。如果目的操作数也是间接操作数,那么新值将存入由寄存器提供地址的内存位置。变址操作数是指,在寄存器上加上常数产生一个有效地址。

如果一个变量包含另一个变量的地址,则该变量称为指针。指针是控制数组和数据结构的重要工具,因为,它包含的地址在运行时是可以修改的。比如,可以使用系统调用来分配(保留)一个内存块,再把这个块的地址保存在一个变量中。

14.JMP和LOOP(转移)指令

汇编语言程序使用条件指令来实现如 IF 语句的高级语句与循环。每条条件指令都包含了一个可能的转向不同内存地址的转移(跳转)。控制转移,或分支,是一种改变语句执行顺序的方法,它有两种基本类型:

  • 无条件转移:无论什么情况都会转移到新地址。新地址加载到指令指针寄存器,使得程序在新地址进行执行。JMP 指令实现这种转移。
  • 条件转移:满足某种条件,则程序出现分支。各种条件转移指令还可以组合起来,形成条件逻辑结构。CPU 基于 ECX 和标志寄存器的内容来解释真 / 假条件。

JMP 指令无条件跳转到目标地址,该地址用代码标号来标识,并被汇编器转换为偏移 量。语法如下所示:

JMP destination

当 CPU 执行一个无条件转移时,目标地址的偏移量被送入指令指针寄存器,从而导致迈从新地址开始继续执行。

LOOP 指令,正式称为按照 ECX 计数器循环,将程序块重复特定次数。ECX 自动成为计数器,每循环一次计数值减 1。语法如下所示:

LOOP destination

循环目标必须距离当前地址计数器 -128 到 +127 字节范围内。LOOP 指令的执行有两个步骤:

  • 第一步,ECX 减 1。
  • 第二步,将 ECX 与 0 比较。

如果 ECX 不等于 0,则跳转到由目标给岀的标号。否则,如果 ECX 等于 0,则不发生跳转,并将控制传递到循环后面的指令。实地址模式中,CX 是 LOOP 指令的默认循环计数器。同时,LOOPD 指令使用 ECX 为循环计数器,LOOPW 指令使用 CX 为循环计数器。

猜你喜欢

转载自blog.csdn.net/qq_35789421/article/details/113736373