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

  • Post author:
  • Post category:其他


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中被设置的标记位,例如:


  • jcc



    Jump if Condition Is Met

    ,即

    条件跳转指令



    jcc

    是一类指令,包括

    jg

    (大于就跳转),

    je

    (等于就跳转),

    jl

    (小于就跳转)等等。总之,

    jcc

    指令的格式就是

    j+后缀

    表示特殊的含义。更多

    jcc

    指令可以参考https://www.felixcloutier.com/x86/jcc。

  • cmovcc



    Conditional Move

    ,即条件mov指令。类似的,对应的指令是

    cmov+后缀

    格式,包括

    cmovg, cmove, cmovl

    等指令。

  • setcc



    Set Byte on Condition

    ,即条件set指令,仅设置一个字节。类似的,对应的指令是

    set+后缀

    格式,包括

    setg, sete, setl

    等。

上述三类条件指令支持的后缀、每种后缀的含义以及使用的标记位如下表所示:

在这里插入图片描述

在这里插入图片描述



版权声明:本文为qq_29328443原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。