x86_64汇编之二:x86_64的基本架构(寄存器、寻址模式、指令集概览)

一、x86_64中的寄存器

x86_64架构中的寄存器可分为以下几类:

  • 通用寄存器 (General-Purpose registers)
  • 状态和控制寄存器(RFLAGS register)
  • 指令寄存器 (RIP)
  • XMM寄存器
  • 浮点控制和状态寄存器 (MXCSR)
  • 等等

如下图所示:
在这里插入图片描述

通用寄存器

通用寄存器主要用于完成一些通用的功能,包括算数运算、逻辑运算、比较运算、数据转移、地址计算,还可以临时存放常量、中间结果、指针等内容。

x86_64中的通用寄存器如下:
在这里插入图片描述
x86_64中的一个通用寄存器有4种用法。以RAX为例:

  • 可以被当做RAX使用——64位
  • 可以被当做EAX使用——32位
  • 可以被当做AX使用——16位
  • 可以被当做AL使用——8位

对于通用寄存器的用途,有一些隐含的规定,有时候某些指令必须使用特定的寄存器来进行操作,例如:

  • 有符号乘法指令imul默认会将乘积存放在RDX:RAX,EDX:EAX,DX:AX,或AX中。
  • 有符号除法指令idiv默认要求被除数放在RDX:RAX,EDX:EAX,DX:AX,或AX中。
  • x86的字符串操作要求源操作数和目的操作数的地址分别存放在RSI和RDI中
  • RBP是栈基址指针,RSP是栈顶指针。通常,x86_64中的栈的操作单位是8字节,有些运行环境也可能是16字节。
  • 等等

RFLAGS寄存器

x86_64中,RFLAGS是一个64位寄存器,其每一个比特位有不同的含义。

其中,最常用的标记位有:

  • 进位标识 CF:无符号数的算数运算产生溢出会导致CF被设置;寄存器移位指令也可能会导致CF被设置;等等
  • 溢出标识 OF:两个有符号数的算数运算可能导致OF被设置
  • 奇偶校验位 PF
  • 符号位 SF:用于标识算数指令和逻辑运算指令运算结果的符号
  • 零标识位 ZF:用于标识算数指令和逻辑运算指令运算结果是不是0

RFLAGS寄存器中每一位的含义可以参考:https://en.wikipedia.org/wiki/FLAGS_register

RFLAGS中每一位何时被设置可以参考:http://www.c-jump.com/CIS77/ASM/Instructions/I77_0070_eflags_bits.htm

每个指令对RFLAGS中标记位的影响可以参考该指令相关的文档

指令寄存器

指令寄存器(RIP)包含下一条将要被执行的指令的逻辑地址。

通常情况下,每取出一条指令后,RIP会自增指向下一条指令。在x86_64中RIP的自增也即偏移8字节。

但是RIP并不总是自增,也有例外,例如call 指令和ret指令。call指令会将当前RIP的内容压入栈中,将程序的执行权交给目标函数;ret指令则执行出栈操作,将之前压入栈的8个字节的RIP地址弹出,重新放入RIP。

浮点数寄存器: XMM/YMM/ZMM

XMM0-XMM15是一系列128位寄存器,它主要用于:

  • 32位和64位浮点数的操作
  • SIMD指令:SIMD即Single Instruction Multiple Data。一条SIMD指令可以同时接受多个数据流,提升处理速度。
  • SSE指令(Streaming SIMD Extension):一般用不到,不详细讨论。

此外,后续x86_64机器引入了AVX扩展,随之而来的是256位的YMM寄存器和512位的ZMM寄存器,也是类似的功能。随着ZMM的出现,XMM和YMM寄存器的个数被扩展到了32个。

需要说明的是,本文第一幅图对XMM/YMM/ZMM关系的描述其实不太准确,下图才是准确的:XMM是YMM的低半部分,YMM是ZMM的低半部分。

浮点数寄存器的演进过程

二、x86_64的寻址方式

参考:
https://blog.yossarian.net/2020/06/13/How-x86_64-addresses-memory
https://cs.nyu.edu/courses/fall10/V22.0201-002/addressing_modes.pdf

有效地址的计算

在x86_64架构中,有效地址 (effective address) 的计算公式如下:

EffectiveAddress = BaseReg + IndexReg * ScaleFactor + Disp

其中:

  • BaseReg是基址寄存器,可以是任意一个通用寄存器
  • IndexReg是索引寄存器,可以是除了RSP之外的任意一个通用寄存器
  • ScaleFactor的取值可以是1,2,4,8
  • Disp是偏移量,可以是长度为8bit,16bit,32bit的有符号整数

关于上述公式中各个部分的取值,有一些隐含的规定:

  • 如果没指明偏移量Disp,则默认是0
  • 最终的有效地址长度始终是64bit

下标展示各种寻址方式(第一列)及其对应的汇编指令,可以看到,各种寻址方式其实都是由上述公式得来的。
在这里插入图片描述

上表中的各种寻址方式对应到C/C++代码中的何种操作呢?

  • Disp :访问全局变量或静态变量通常使用这种方式
  • BaseReg:在使用指针获取对应变量的值时,通常使用这种寻址方式。rbx存放被指针指向的变量的地址。
  • BaseReg + Disp:访问某种数据结构(例如结构体、类对象)中的某个成员通常使用该模式
  • IndexReg * SF + Disp:访问数组通常使用该模式。此时SF代表数组元素的大小,例如如果是int类型,那么SF就是4
  • 访问更复杂的数据结构时,可能需要BaseReg, IndexReg, ScaleFactor, Disp 4者的相互搭配,对应余下的情况。

更详细的内容可以参考这篇文章:https://blog.yossarian.net/2020/06/13/How-x86_64-addresses-memory

三、x86_64指令集概览

在这里插入图片描述
在这里插入图片描述
上表列出了x86_64指令集中的主要指令。其中大多数算数和逻辑运算指令都会修改RFLAGS寄存器中的若干个标记位。

某些基于条件的指令会用到RFLAGS中被设置的标记位,例如:

  • jccJump if Condition Is Met,即条件跳转指令jcc是一类指令,包括jg(大于就跳转),je(等于就跳转),jl(小于就跳转)等等。总之,jcc指令的格式就是j+后缀表示特殊的含义。更多jcc指令可以参考https://www.felixcloutier.com/x86/jcc。
  • cmovccConditional Move,即条件mov指令。类似的,对应的指令是cmov+后缀格式,包括cmovg, cmove, cmovl等指令。
  • setccSet Byte on Condition,即条件set指令,仅设置一个字节。类似的,对应的指令是set+后缀格式,包括setg, sete, setl等。

上述三类条件指令支持的后缀、每种后缀的含义以及使用的标记位如下表所示:
在这里插入图片描述
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_29328443/article/details/107188689