67、深入解析x86指令集:从基础到高级应用

深入解析x86指令集:从基础到高级应用

1. 引言

x86指令集是计算机领域中广泛使用的指令集之一,它为处理器提供了执行各种任务的基础。这里将介绍最常用的32位x86指令,不涉及系统模式指令以及通常仅在操作系统内核代码或保护模式设备驱动程序中使用的指令。

1.1 标志位(EFlags)

每个指令描述中都包含一系列框,用于描述该指令将如何影响CPU状态标志。每个标志由一个字母标识,具体如下:
| 标志 | 含义 |
| — | — |
| O | 溢出标志(Overflow) |
| S | 符号标志(Sign) |
| P | 奇偶标志(Parity) |
| D | 方向标志(Direction) |
| Z | 零标志(Zero) |
| C | 进位标志(Carry) |
| I | 中断标志(Interrupt) |
| A | 辅助进位标志(Auxiliary Carry) |

框内使用以下符号表示指令对标志的影响:
- 1 :设置标志。
- 0 :清除标志。
- ? :可能将标志更改为未确定的值。
- (空白):标志不变。
- * :根据与标志相关的特定规则更改标志。

例如,在某个指令描述中,CPU标志的图示如下:

?  ?  ?  *  ?
O  D  I  S  C
A  P  Z  *

从该图示可以看出,溢出、符号、零和奇偶标志将被更改为未知值;辅助进位和进位标志将根据相关规则进行修改;方向和中断标志将保持不变。

1.2 指令描述和格式

在引用源操作数和目标操作数时,遵循所有x86指令中操作数的自然顺序,即第一个操作数是目标操作数,第二个是源操作数。例如,在 MOV 指令中,目标操作数将被赋予源操作数数据的副本:

MOV destination, source

单个指令可能有多种格式。以下是指令格式中使用的符号列表:
| 符号 | 描述 |
| — | — |
| reg | 8位、16位或32位通用寄存器,如AH、AL、BH等。 |
| reg8, reg16, reg32 | 按位数标识的通用寄存器。 |
| segreg | 16位段寄存器(CS、DS、ES、SS、FS、GS)。 |
| accum | AL、AX或EAX。 |
| mem | 使用任何标准内存寻址模式的内存操作数。 |
| mem8, mem16, mem32 | 按位数标识的内存操作数。 |
| shortlabel | 代码段中距离当前位置在 -128 到 +127 字节范围内的位置。 |
| nearlabel | 当前代码段中由标签标识的位置。 |
| farlabel | 外部代码段中由标签标识的位置。 |
| imm | 立即操作数。 |
| imm8, imm16, imm32 | 按位数标识的立即操作数。 |
| instruction | 80x86汇编语言指令。 |

2. 非浮点指令集详情

2.1 算术和调整指令

  • AAA(ASCII Adjust After Addition) :在两个ASCII数字相加后调整AL中的结果。如果AL > 9,则将结果的高位数字放入AH,并设置进位和辅助进位标志。
  • 指令格式: AAA
  • AAD(ASCII Adjust Before Division) :在执行 DIV 指令之前,将AH和AL中的未压缩BCD数字转换为单个二进制值。
  • 指令格式: AAD
  • AAM(ASCII Adjust After Multiply) :在两个未压缩BCD数字相乘后调整AX中的结果。
  • 指令格式: AAM
  • AAS(ASCII Adjust After Subtraction) :在减法操作后调整AX中的结果。如果AL > 9,AAS将递减AH并设置进位和辅助进位标志。
  • 指令格式: AAS

2.2 加法和减法指令

  • ADC(Add Carry) :将源操作数和进位标志的值加到目标操作数上。操作数必须大小相同。
  • 指令格式:
    • ADC reg,reg
    • ADC reg,imm
    • ADC mem,reg
    • ADC mem,imm
    • ADC reg,mem
    • ADC accum,imm
  • ADD(Add) :将源操作数加到目标操作数上,并将和存储在目标操作数中。操作数必须大小相同。
  • 指令格式:
    • ADD reg,reg
    • ADD reg,imm
    • ADD mem,reg
    • ADD mem,imm
    • ADD reg,mem
    • ADD accum,imm
  • SBB(Subtract with Borrow) :从目标操作数中减去源操作数,然后再减去进位标志的值。
  • 指令格式:
    • SBB reg,reg
    • SBB reg,imm
    • SBB mem,reg
    • SBB mem,imm
    • SBB reg,mem

2.3 逻辑指令

  • AND(Logical AND) :将目标操作数的每一位与源操作数的对应位进行逻辑与操作。
  • 指令格式:
    • AND reg,reg
    • AND reg,imm
    • AND mem,reg
    • AND mem,imm
    • AND reg,mem
    • AND accum,imm
  • OR(Inclusive OR) :对目标操作数和源操作数的每一位进行逻辑或操作。
  • 指令格式:
    • OR reg,reg
    • OR reg,imm
    • OR mem,reg
    • OR mem,imm
    • OR reg,mem
    • OR accum,imm
  • XOR(Exclusive OR) :将源操作数的每一位与目标操作数的对应位进行异或操作。目标位仅在源位和目标位不同时为1。
  • 指令格式:
    • XOR reg,reg
    • XOR reg,imm
    • XOR mem,reg
    • XOR mem,imm
    • XOR reg,mem
    • XOR accum,imm

2.4 跳转和调用指令

  • CALL(Call a Procedure) :将下一条指令的位置压入栈中,并转移到目标位置。如果过程是近过程(在同一代码段内),则仅压入下一条指令的偏移量;否则,同时压入段和偏移量。
  • 指令格式:
    • CALL nearlabel
    • CALL mem16
    • CALL farlabel
    • CALL mem32
    • CALL reg
  • Jcondition(Conditional Jump) :如果指定的标志条件为真,则跳转到标签处。在使用早于x86的处理器时,标签必须在当前位置的 -128 到 +127 字节范围内。在x86处理器上,标签的偏移量可以是正或负的32位值。
  • 条件跳转助记符及含义如下:
    | 助记符 | 含义 | 助记符 | 含义 |
    | — | — | — | — |
    | JA | 高于则跳转 | JE | 相等则跳转 |
    | JNA | 不高于则跳转 | JNE | 不相等则跳转 |
    | JAE | 高于或等于则跳转 | JZ | 为零则跳转 |
    | JNAE | 不高于或等于则跳转 | JNZ | 不为零则跳转 |
    | JB | 低于则跳转 | JS | 为负则跳转 |
    | JNB | 不低于则跳转 | JNS | 不为负则跳转 |
    | JBE | 低于或等于则跳转 | JC | 有进位则跳转 |
    | JNBE | 不低于或等于则跳转 | JNC | 无进位则跳转 |
    | JG | 大于则跳转 | JO | 溢出则跳转 |
    | JNG | 不大于则跳转 | JNO | 无溢出则跳转 |
    | JGE | 大于或等于则跳转 | JP | 奇偶性为偶则跳转 |
    | JNGE | 不大于或等于则跳转 | JPE | 奇偶性相等则跳转 |
    | JL | 小于则跳转 | JNP | 奇偶性为奇则跳转 |
    | JNL | 不小于则跳转 | JPO | 奇偶性为奇则跳转 |
    | JLE | 小于或等于则跳转 | JNLE | 不小于或等于则跳转 |
    | JCXZ, JECXZ | CX为零则跳转 | | |

2.5 数据传输指令

  • MOV(Move) :将源操作数的一个字节或字复制到目标操作数。
  • 指令格式:
    • MOV reg,reg
    • MOV reg,imm
    • MOV mem,reg
    • MOV mem,imm
    • MOV reg,mem
    • MOV mem16,segreg
    • MOV reg16,segreg
    • MOV segreg,mem16
    • MOV segreg,reg16
  • MOVS(Move String) :将由DS:(E)SI寻址的内存中的字节或字复制到由ES:(E)DI寻址的内存中。
  • 指令格式:
    • MOVSB
    • MOVSW
    • MOVSD
    • MOVS dest, source
    • MOVS ES:dest, segreg:source

2.6 其他指令

  • BOUND(Check Array Bounds) :验证有符号索引值是否在数组的边界内。在80286处理器上,目标操作数可以是任何包含要检查索引的16位寄存器;源操作数必须是一个32位内存操作数,其中高字和低字分别包含索引值的上界和下界。在x86处理器上,目标可以是32位寄存器,源可以是64位内存操作数。
  • 指令格式:
    • BOUND reg16,mem32
    • BOUND r32,mem64
  • BSF, BSR(Bit Scan) :扫描操作数以找到第一个设置的位。如果找到该位,则清除零标志,并将目标操作数赋值为遇到的第一个设置位的位号(索引)。如果未找到设置位,则ZF = 1。BSF从位0开始向最高位扫描,BSR从最高位开始向位0扫描。
  • 指令格式(适用于BSF和BSR):
    • BSF reg16,r/m16
    • BSF reg32,r/m32

下面是一个简单的mermaid流程图,展示了条件跳转指令的基本逻辑:

graph TD;
    A[开始] --> B{标志条件是否为真};
    B -- 是 --> C[跳转到标签];
    B -- 否 --> D[继续执行下一条指令];

通过以上介绍,我们对x86非浮点指令集有了较为全面的了解。这些指令为编写高效、复杂的程序提供了基础,无论是系统软件还是应用程序,都离不开这些指令的支持。在实际编程中,合理运用这些指令可以充分发挥处理器的性能。

3. 浮点指令集详情

3.1 浮点指令概述

浮点指令主要用于处理浮点数运算,在科学计算、图形处理等领域有着广泛的应用。下面是x86浮点指令的详细列表及简要说明:
| 指令 | 描述 |
| — | — |
| F2XM1 | 计算2^x - 1,无操作数。 |
| FABS | 求绝对值,清除ST(0)的符号位,无操作数。 |
| FADD | 浮点加法,将目标操作数和源操作数相加,结果存储在目标操作数中。 |
| FADDP | 浮点加法并弹出栈,执行与FADD相同的操作,然后弹出栈。 |
| FIADD | 将整数转换为浮点数并相加,将目标操作数和源操作数相加,结果存储在目标操作数中。 |
| FBLD | 加载二进制编码的十进制数,将BCD源操作数转换为双扩展精度浮点格式并压入栈中。 |
| FBSTP | 存储BCD整数并弹出栈,将ST(0)寄存器中的值转换为18位压缩BCD整数,存储在目标操作数中,并弹出寄存器栈。 |
| FCHS | 改变符号,对ST(0)取反,无操作数。 |
| FCLEX | 清除异常标志,清除浮点异常标志(PE、UE、OE、ZE、DE和IE)、异常汇总状态标志(ES)、栈故障标志(SF)和FPU状态字中的忙标志(B),无操作数。 |
| FCMOVcc | 浮点条件移动,测试EFLAGS中的状态标志,如果给定的测试条件为真,则将源操作数(第二个操作数)移动到目标操作数(第一个操作数)。 |
| FCOM | 比较浮点值,将ST(0)与源操作数进行比较,并根据结果设置FPU状态字中的条件代码标志C0、C2和C3。 |
| FCOMP | 执行与FCOM相同的操作,然后弹出栈。 |
| FCOMPP | 执行与FCOM相同的操作,然后弹出栈两次。 |
| FUCOM | 与FCOM相同,但检查无序值。 |
| FUCOMP | 与FCOMP相同,但检查无序值。 |
| FUCOMPP | 与FCOMPP相同,但检查无序值。 |
| FCOMI | 比较浮点值并设置EFLAGS,对寄存器ST(0)和ST(i)进行无序比较,并根据结果设置EFLAGS寄存器中的状态标志(ZF、PF、CF)。 |
| FCOMIP | 执行与FCOMI相同的操作,然后弹出栈。 |
| FUCOMI | 与FCOMI相同,但检查无序值。 |
| FUCOMIP | 与FCOMIP相同,但检查无序值。 |
| FCOS | 计算余弦值,计算ST(0)的余弦值并存储在ST(0)中,输入必须为弧度,无操作数。 |
| FDECSTP | 递减栈顶指针,从FPU状态字的TOP字段中减去1,有效地旋转栈,无操作数。 |
| FDIV | 浮点除法并弹出栈,将目标操作数除以源操作数,结果存储在目标位置。 |
| FDIVP | 浮点除法并弹出栈,与FDIV相同,然后从栈中弹出。 |
| FIDIV | 将整数转换为浮点数并除法,转换后执行与FDIV相同的操作。 |
| FDIVR | 反向除法,将源操作数除以目标操作数,结果存储在目标位置。 |
| FDIVRP | 反向除法并弹出栈,执行与FDIVR相同的操作,然后从栈中弹出。 |
| FIDIVR | 将整数转换为浮点数并执行反向除法,转换后执行与FDIVR相同的操作。 |
| FFREE | 释放浮点寄存器,使用标签字将寄存器设置为空。 |
| FICOM | 比较整数,将ST(0)中的值与整数源操作数进行比较,并根据结果设置条件代码标志C0、C2和C3,整数源操作数在比较前转换为浮点数。 |
| FICOMP | 执行与FICOM相同的操作,然后从栈中弹出。 |
| FILD | 将整数转换为浮点数并加载到寄存器栈中。 |
| FINCSTP | 递增栈顶指针,向FPU状态字的TOP字段加1,无操作数。 |
| FINIT | 初始化浮点单元,将控制、状态、标签、指令指针和数据指针寄存器设置为默认状态,控制字设置为037FH(四舍五入到最近值,所有异常屏蔽,64位精度),状态字清除(无异常标志设置,TOP = 0),寄存器栈中的数据寄存器不变,但标记为空,无操作数。 |
| FNINIT | 执行与FINIT相同的操作,不检查待处理的未屏蔽浮点异常。 |
| FIST | 将整数存储在内存操作数中,将ST(0)存储在有符号整数内存操作数中,根据FPU控制字中的RC字段进行舍入。 |
| FISTP | 执行与FIST相同的操作,然后弹出寄存器栈。 |
| FISTTP | 截断存储整数,执行与FIST相同的操作,但自动截断整数并弹出栈。 |
| FLD | 将浮点值加载到寄存器栈中。 |
| FLD1 | 加载 +1.0 到寄存器栈中,无操作数。 |
| FLDL2T | 加载log2(10)到寄存器栈中,无操作数。 |
| FLDL2E | 加载log2(e)到寄存器栈中,无操作数。 |
| FLDPI | 加载π到寄存器栈中,无操作数。 |
| FLDLG2 | 加载log10(2)到寄存器栈中,无操作数。 |
| FLDLN2 | 加载loge(2)到寄存器栈中,无操作数。 |
| FLDZ | 加载 +0.0 到寄存器栈中,无操作数。 |
| FLDCW | 从16位内存值加载FPU控制字。 |
| FLDENV | 从内存加载FPU环境到FPU中。 |
| FMUL | 浮点乘法,将目标操作数和源操作数相乘,结果存储在目标位置。 |
| FMULP | 浮点乘法并弹出栈,执行与FMUL相同的操作,然后弹出栈。 |
| FIMUL | 将整数转换并相乘,将源操作数转换为浮点数,与ST(0)相乘,结果存储在ST(0)中。 |
| FNOP | 无操作,无操作数。 |
| FPATAN | 部分反正切,用arctan(ST(1)/ST(0))替换ST(1)并弹出寄存器栈,无操作数。 |
| FPREM | 部分余数,用ST(0)除以ST(1)的余数替换ST(0),无操作数。 |
| FPREM1 | 与FPREM类似,用ST(0)除以ST(1)的IEEE余数替换ST(0)。 |
| FPTAN | 部分正切,用ST(0)的正切替换ST(0),并将1.0压入FPU栈,输入必须为弧度,无操作数。 |
| FRNDINT | 四舍五入到整数,将ST(0)四舍五入到最接近的整数值,无操作数。 |
| FRSTOR | 恢复x87 FPU状态,从源操作数指定的内存区域加载FPU状态(操作环境和寄存器栈)。 |
| FSAVE | 存储x87 FPU状态,将当前FPU状态(操作环境和寄存器栈)存储在目标操作数指定的内存中,然后重新初始化FPU。 |
| FNSAVE | 执行与FSAVE相同的操作,不检查待处理的未屏蔽浮点异常。 |
| FSCALE | 缩放,将ST(1)的值截断为整数值,并将该值加到目标操作数ST(0)的指数上,无操作数。 |
| FSIN | 求正弦值,用ST(0)的正弦值替换ST(0),输入必须为弧度,无操作数。 |
| FSINCOS | 求正弦和余弦值,计算ST(0)的正弦和余弦值,输入必须为弧度,用正弦值替换ST(0),并将余弦值压入寄存器栈,无操作数。 |
| FSQRT | 求平方根,用ST(0)的平方根替换ST(0),无操作数。 |
| FST | 存储浮点值。 |
| FSTP | 执行与FST相同的操作,然后弹出栈。 |
| FSTCW | 存储FPU控制字。 |
| FNSTCW | 执行与FSTCW相同的操作,不检查待处理的未屏蔽浮点异常。 |
| FSTENV | 存储FPU环境,将FPU环境存储在m14byte或m28byte结构中,具体取决于处理器是处于实模式还是保护模式。 |
| FNSTENV | 执行与FSTENV相同的操作,不检查待处理的未屏蔽浮点异常。 |
| FSTSW | 存储FPU状态字。 |
| FNSTSW | 执行与FSTSW相同的操作,不检查待处理的未屏蔽浮点异常。 |
| FSUB | 浮点减法,从目标操作数中减去源操作数,结果存储在目标位置。 |
| FSUBP | 浮点减法并弹出栈,执行与FSUB相同的操作,然后弹出栈。 |
| FISUB | 将整数转换为浮点数并减法,将源操作数转换为浮点数,从ST(0)中减去,结果存储在ST(0)中。 |
| FSUBR | 反向浮点减法,从源操作数中减去目标操作数,结果存储在目标位置。 |
| FSUBRP | 反向浮点减法并弹出栈,执行与FSUBR相同的操作,然后弹出栈。 |
| FISUBR | 将整数转换并反向浮点减法,转换为浮点数后,执行与FSUBR相同的操作。 |
| FTST | 测试,将ST(0)与0.0进行比较,并设置FPU状态字中的条件代码标志,无操作数。 |
| FWAIT | 等待,等待所有待处理的浮点异常处理程序完成,无操作数。 |
| FXAM | 检查,检查ST(0)并设置FPU状态字中的条件代码标志,无操作数。 |
| FXCH | 交换寄存器内容。 |
| FXRSTOR | 恢复x87 FPU、MMX技术、SSE和SSE2状态,从源操作数指定的内存映像中重新加载FPU、MMX技术、XMM和MXCSR寄存器。 |
| FXSAVE | 保存x87 FPU、MMX技术、SSE和SSE2状态,将FPU、MMX技术、XMM和MXCSR寄存器的当前状态保存到目标操作数指定的内存映像中。 |
| FXTRACT | 提取指数和尾数,将ST(0)中的源分离为指数和尾数,将指数存储在ST(0)中,并将尾数压入寄存器栈,无操作数。 |
| FYL2X | 计算y * log2(x),寄存器ST(1)保存y的值,ST(0)保存x的值,弹出栈,结果留在ST(0)中,无操作数。 |
| FYL2XP1 | 计算y * log2(x + 1),寄存器ST(1)保存y的值,ST(0)保存x的值,弹出栈,结果留在ST(0)中,无操作数。 |

3.2 浮点指令操作示例

以FADD指令为例,其不同格式的操作说明如下:
- FADD :将ST(0)加到ST(1),并弹出栈。
- FADD m32fp :将m32fp加到ST(0)。
- FADD m64fp :将m64fp加到ST(0)。
- FADD ST(0),ST(i) :将ST(i)加到ST(0)。
- FADD ST(i),ST(0) :将ST(0)加到ST(i)。

下面是一个简单的mermaid流程图,展示了浮点加法指令FADD的基本执行流程:

graph TD;
    A[开始] --> B{选择操作数格式};
    B -- m32fp --> C[将m32fp加到ST(0)];
    B -- m64fp --> D[将m64fp加到ST(0)];
    B -- ST(0),ST(i) --> E[将ST(i)加到ST(0)];
    B -- ST(i),ST(0) --> F[将ST(0)加到ST(i)];
    B -- 无 --> G[将ST(0)加到ST(1)并弹出栈];
    C --> H[结束];
    D --> H;
    E --> H;
    F --> H;
    G --> H;

4. 总结

x86指令集涵盖了丰富的非浮点和浮点指令,为计算机编程提供了强大的支持。非浮点指令集适用于各种通用计算任务,如算术运算、逻辑运算、数据传输和程序控制等。而浮点指令集则专门用于处理浮点数运算,在科学计算、图形处理等领域发挥着重要作用。

在实际编程中,我们需要根据具体的任务需求选择合适的指令。合理运用这些指令可以提高程序的性能和效率,充分发挥x86处理器的优势。同时,对于指令对标志位的影响也需要有清晰的认识,以便在程序中进行准确的条件判断和控制。

通过深入学习和理解x86指令集,我们能够编写更加高效、优化的程序,无论是系统软件的开发还是应用程序的编写,都将受益于对这些指令的掌握。希望本文能够帮助读者更好地理解和运用x86指令集,在编程领域取得更好的成果。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值