汇编基础(3) --X86-64

简介

x86_64汇编语言是一种低级程序设计语言,用于控制x86_64架构的处理器执行特定的指令序列。它提供了对底层硬件的直接访问和控制,因此常用于编写性能关键的系统软件、驱动程序和嵌入式设备。对于熟悉高级编程语言(如C或C++)的开发者来说,了解和学习x86_64汇编语言有助于深入了解计算机的工作原理和优化代码的性能。

x86_64汇编语言使用助记符(mnemonics)来表示不同的机器指令。这些指令可以操作寄存器(Registers)、内存(Memory)和标志位(Flags)等系统资源。寄存器是一种高速存储器,用于暂存数据和执行算术和逻辑操作。x86_64架构包含一组通用寄存器,如RAX、RBX、RCX、RDX等,它们可以存储整数值和指针。此外,还有一些特殊目的寄存器,如程序计数器(Program Counter)和堆栈指针(Stack Pointer)。

x86_64汇编语言还支持各种内存访问模式,包括直接寻址、间接寻址和基于索引的寻址。通过这些寻址模式,可以读取和写入内存中的数据,这为处理复杂的数据结构和算法提供了便利性。

与高级编程语言相比,汇编语言更加底层和繁琐。它需要开发者手动管理寄存器和内存,并且需要详细了解处理器的指令集。此外,调试和维护汇编代码通常更加困难。因此,在大多数情况下,编写应用程序的大部分代码使用高级编程语言来实现,只有对性能要求极高或对底层硬件的直接控制有特殊需求时,才会使用汇编语言编写一小部分关键代码。

函数调用

  1. 寄存器使用:

    • 前6个整型参数(从左到右)通过寄存器传递,分别是:RDI、RSI、RDX、RCX、R8、R9。
    • 如果需要传递更多的整型参数,超出这6个寄存器的参数将通过栈传递。
    • 浮点数和矢量参数则使用XMM0至XMM7这8个XMM寄存器传递。
  2. 函数调用栈帧:

    • 函数调用时,返回地址会被压入栈中。
    • 当前函数将使用栈上的一部分空间来存储局部变量和临时数据。
    • RBP(基址指针)寄存器通常被用作指向当前函数的栈帧的基地址。
    • 函数参数也可能保存在栈上。
    • 返回值会通过RAX寄存器传递。
  3. 栈对齐:

    • 栈要求以16字节对齐,即栈指针的地址必须是16的倍数。
    • 在函数调用过程中,栈指针(RSP)会被调整以保持对齐。
  4. 寄存器保护:

    • 如果一个函数调用另一个函数,被调用函数必须保存并恢复使用的寄存器。
    • 一般情况下,RBP、RBX和R12至R15这些寄存器需要被调用者保存。
  5. 栈帧清理:

    • 调用者负责清理栈上的参数和临时数据。
    • 在函数调用结束后,通过调整栈指针(RSP)来释放栈帧。

通用寄存器 (general register)

通用寄存器(general-purpose registers, GPRs) 可能是读书是最早接触的寄存器。每一个用户空间的程序,或者内核程序都用到的,基本的寄存器。
因为X86-64是从32位的X86,甚至16位、8位演变而来的,为了软件可以向前兼容,所以,这些寄存器都有不同的版本。话不多说,看下表:

64-bit 32-bit 16-bit 8-bit (low)
RAX EAX AX AL
RBX EBX BX BL
RCX ECX CX CL
RDX EDX DX DL
RSI ESI SI SIL
RDI EDI DI DIL
RBP EBP BP BPL
RSP ESP SP SPL
R8 R8D R8W R8B
R9 R9D R9W R9B
R10 R10D R10W R10B
R11 R11D R11W R11B
R12 R12D R12W R12B
R13 R13D R13W R13B
R14 R14D R14W R14B
R15 R15D R15W R15B

其中,16位的寄存器中的AX, BX, CX, DX 可以高低位分别访问,所以又增加4个

16-bit 8-bit (high)
AX AH
BX BH
CX CH
DX DH

通用寄存器总共: 68.

到目前为止一共: 68.

特殊寄存器 (Special registers)

  • 指针寄存器(instruction pointer, RIP.)
    X86-64的RIP可以切分成32位的EIP 和 16位IP,但他们不可以同时使用,所以这里不重复计数。
  • 状态寄存器(status register, RFLAGS)
    就像上面的RIP一样,RFLAGS也有32位和16位版本,分别是EFLAGS 和 FLAGS,但不同的是PUSHF 和 PUSHFQ可以在长模式下同时使用,以及LAHF 和 SAHF可以同时在一个CPU上使用,这里计数。

本部分寄存器总共: 4.

到目前为止一共: 72.

段寄存器 (Segment registers)

X86-64一共有6个段寄存器: CS, SS, DS, ES, FS, and GS

  • 除了长模式以外的所有CPU运行模式里,都有一个段选择器 selector, 表示当前使用GDT 还是 LDT。 同时,还需要一个段描述符descriptor, 提供了段的基址和范围。
  • 长模式中,除了 FS 和 GS 之外的所有内容都被视为在一个具有零基地址和64位范围的平面地址空间中。 FS 和 GS 作为特殊情况保留,但不再使用段描述符表,取而代之的是,访问保存在 FSBASE 和 GSBASE 中的MSR寄存器中的基地址。

本部分寄存器总共: 6.

到目前为止一共: 78.

单指令多数据SIMD 和浮点运算指令FP

X86家族经历了几代 SIMD 和浮点指令,每一代都引入、扩展或重新定义各种各样的指令:

  • x87
  • MMX
  • SSE (SSE2, SSE3, SSE4, SSE4, …)
  • AVX (AVX2, AVX512)
  • AMX

x87

X87最初是一个独立的协处理器,有自己的指令集和寄存器,从80486开始,x87指令就经常被植入x86内核本身。
由于其协处理器的历史,x87定义了正常的寄存器(类似于GPR)和控制FPU状态所需的各种特殊寄存器。

  • ST0到ST7:8个80位浮点寄存器
  • FPSW, FPCW, FPTW 7:控制、状态和标签字寄存器
  • “数据操作数指针Data operand pointer”。我不知道这个是做什么的,但英特尔SDM规定了它
  • 指令指针Instruction pointer:x87状态机显然持有它自己的当前x87指令的拷贝。
  • 最后一条指令的操作码Last instruction opcode:与x87操作码不同,并且有它自己的寄存器。

本部分寄存器总共: 14.

到目前为止一共: 92.

MMX

MMX是Intel在X86芯片上添加SIMD指令的第一次尝试,发布于1997年。MMX寄存器实际上是x87 STn寄存器的子集。每个64位MMn占用其相应STn的尾数部分。因此,x86(和x86-64)CPU不能同时执行MMX和x87指令。
MMX定义了MM0到MM7,8个寄存器,另外还有一个新的状态寄存器(MXCSR),以及用于操作它的加载/存储指令对(LDMXCSR和STMXCSR)。

本部分寄存器总共: 9.

到目前为止一共: 101.

SSE and AVX

为了简单起见,我打算把SSE和AVX包成一个部分:它们使用与GPR和x87/MMX相同的子寄存器模式,所以放在一个表中。

AVX-512 (512-bit) AVX-2 (256-bit) SSE (128-bit)
ZMM0 YMM0 XMM0
ZMM1 YMM1 XMM1
ZMM2 YMM2 XMM2
ZMM3 YMM3 XMM3
ZMM4 YMM4 XMM4
ZMM5 YMM5 XMM5
ZMM6 YMM6 XMM6
ZMM7 YMM7 XMM7
ZMM8 YMM8 XMM8
ZMM9 YMM9 XMM9
ZMM10 YMM10 XMM10
ZMM11 YMM11 XMM11
ZMM12 YMM12 XMM12
ZMM13 YMM13 XMM13
ZMM14 YMM14 XMM14
ZMM15 YMM15 XMM15
ZMM16 YMM16 XMM16
ZMM17 YMM17 XMM17
ZMM18 YMM18 XMM18
ZMM19 YMM19 XMM19
ZMM20 YMM20 XMM20
ZMM21 YMM21 XMM21
ZMM22 YMM22 XMM22
ZMM23 YMM23 XMM23
ZMM24 YMM24 XMM24
ZMM25 YMM25 XMM25
ZMM26 YMM26 XMM26
ZMM27 YMM27 XMM27
ZMM28 YMM28 XMM28
ZMM29 YMM29 XMM29
ZMM30 YMM30 XMM30
ZMM31 YMM31 XMM31

换句话说:每个ZMMn的低字节部分是YMMn,而每个YMMn的低字节部分是XMMn。没有直接的寄存器可以只访问YMMn的高字节部分,或者ZMMn的高128位或者256位字部分。
SSE还定义了一个新的状态寄存器MXCSR,它包含的标志与RFLAGS中的算术标志大致平行(与x87状态字中的浮点标志一起)。
AVX-512还引入了8个OPMask寄存器,k0到k7。k0是一个特殊的情况,它的行为很像一些RISC ISA上的 “零 “寄存器:它不能被存储到,而且从它的加载总是产生一个全部为1的位掩码。

本部分寄存器总共: 33.

到目前为止一共: 134.

边界寄存器(Bounds registers)

英特尔在MPX中加入了BR,其目的是提供硬件加速的边界检查。但好像名声并不是很大,暂且放这里把。
BND0 - BND3:单独的128位寄存器,每个都包含一对绑定的地址。
BNDCFG: 绑定配置,内核模式。
BNDCFU: 绑定配置,用户模式。
BNDSTATUS: 绑定状态,在一个#BR被提出后。

本部分寄存器总共: 7.

到目前为止一共: 141.

调试寄存器(Debug registers)

正如他们的名字那样,帮助和加速软件调试器的寄存器,如GDB。
有6个调试寄存器,分为两种类型:

  • DR0到DR3包含线性地址,每个地址都与一个断点条件相关。
  • DR6和DR7是调试状态和控制寄存器。DR6的低位表示遇到了哪些调试条件(在进入调试异常处理程序时),而DR7控制哪些断点地址被启用以及它们的断点条件(例如,当某一地址被写入时)。
  • 那么,DR4和DR5呢?不清楚! 但它们确实有编码,但分别被当做DR6和DR7,或者在CR4.DE[位3]=1时产生#UD异常。

本部分寄存器总共: 6.

到目前为止一共: 147.

控制寄存器 (Control registers)

x86-64 定义了一组控制寄存器,可用于管理和检查 CPU 的状态。有 16 个“主”控制寄存器,所有这些都可以通过MOV variant变体访问:

Name Purpose
CR0 Basic CPU operation flags
CR1 Reserved
CR2 Page-fault linear address
CR3 Virtual addressing state
CR4 Protected mode operation flags
CR5 Reserved
CR6 Reserved
CR7 Reserved
CR8 Task priority register (TPR)
CR9 Reserved
CR10 Reserved
CR11 Reserved
CR12 Reserved
CR13 Reserved
CR14 Reserved
CR15 Reserved

所有reserved的控制寄存器在访问时都会产生#UD,不把它们算在本文里。
除了 “主 “CRn控制寄存器之外,还有 “扩展 “控制寄存器,由XSAVE功能集引入。截至目前,XCR0是唯一指定的扩展控制寄存器。
扩展控制寄存器使用XGETBVXSETBV而不是MOV的变种。

本部分寄存器总共: 6.

到目前为止一共: 153.

“System table pointer registers”

这就是英特尔SDM对这些寄存器的称呼。这些寄存器保存着各种保护模式表的大小和指针。
据我所知,它们有四个:

  • GDTR:存放GDT的大小和基址。
  • LDTR:保存LDT的大小和基址。
  • IDTR:保存IDT的大小和基址。
  • TR:保存TSS的选择器和TSS的基址。

GDTR、LDTR和IDTR在64位模式下都是80位:16个低位是寄存器的表的大小,高64位是表的其实地址。
TR同样也是80位。16位用于选择器(其行为与段选择器相同),然后另外64位用于TSS的基本地址。

本部分寄存器总共: 4.

到目前为止一共: 157.

Memory-type-ranger registers

这些是有趣的例子:与到目前为止所涉及的所有其他寄存器不同,这些不是多核芯片中某个特定CPU所独有的,相反,它们是所有内核共享的。
MTTR的数量似乎因CPU型号而异,并且在很大程度上被页中的所取代page attribute table,该表是用MSR编程的。

本部分寄存器总共: ?.

到目前为止一共: >157.

MSR Model specific registers

像XCR一样,MSR可以通过一对指令间接地(通过标识符)访问,RDMSR和WRMSR。
MSR本身是64位的,但起源于32位的时代,所以RDMSR和WRMSR从两个32位的寄存器中读取和写入。EDX和EAX。
举个例子:下面是访问IA32_MTRRCAP MSR的设置和RDMSR调用,其中包括(除其他外)系统上可用的MTRR的实际数量。

MOV ECX, 0xFE ; 0xFE = IA32_MTRRCAP
RDMSR  ; The bits of IA32_MTRRCAP are now in EDX:EAX

RDMSR和WRMSR是特权指令,所以Ring 3代码不能直接访问MSR。我知道的一个例外是时间戳计数器(TSC),它存储在IA32_TSC MSR中,但是可以通过RDTSC和RDTSCP从非特权上下文中读取。
另外两个是FSBASE和GSBASE,除非启用了 FSGSBASE 支持,否则可以直接从环 3 修改 FSBASE 和 GSBASE。Linux 在 5.9 中启用了 FSGSBASE。它们分别存储为IA32_FS_BASE和IA32_GS_BASE。正如在段寄存器部分所提到的,这些存储在x86-64 CPU上的FS和GS段基。这使得它们成为相对频繁使用的目标(根据MSR标准),所以它们有自己专门的R/W操作码。

  • RDFSBASE和RDGSBASE用于读取
  • WRFSBASE和WRGSBASE用于写入
    但回到本文主题:有多少个MSR? 我们从SDM 335592-sdm-vol-4中寻找答案。

许多MSRs已经从一代IA-32处理器延续到下一代,并延续到Intel 64处理器。MSR的一个子集和相关的位域,被认为是架构MSR。由于历史原因(从奔腾4处理器开始),这些 “架构性MSR “被赋予 “IA32_”的前缀。
根据SDM中的Table 2-2, 最高序号的MSR是6097/17D1H,即IA32_HW_FEEDBACK_CONFIG。
然而,在记录的MSR序号是存在着明显的洞。SDM直接从3506/DB2H(IA32_THREAD_STALL)跳到6096/17D0H(IA32_HW_FEEDBACK_PTR)。在空的范围之上,还有一些范围被明确地标记为保留的范围。
为了计算MSR的实际数量,只提取SDM第4卷中的表2-2中的值。

  • 解压缩 SDM

    $ pdfjam 335592-sdm-vol-4.pdf 19-67 -o 2-2.pdf
    
  • 利用pdftotext,把PDF转换为纯文本。

    $ pdftotext 2-2.pdf table.txt
    # edit table.txt by hand
    
  • 使用 IA32_ 前缀,筛选字符串

    $ tr -s '[:space:]' '\n' < table.txt \
        | grep 'IA32_' \
        | tr -d '.' \
        | sed 's/\[.*$//' \
        | sort | uniq | wc -l
    

最后得到400个MSR,这要比6096合理多了。

本部分寄存器总共: 400.

到目前为止一共: >557.

参考

  • sandpile.org MSR 可视化。
  • Vol. 3A § 8.7.1 (“State of the Logical Processors”) of the Intel SDM has a useful list of
    nearly all of the registers that are either unique to or shared between x86-64 cores.
  • The OSDev Wiki has collection of helpful pages on various x86-64
    registers, including a great page on the behavior of the segment
    base MSRs.

总而言之,在相对较新的x86-64 CPU内核上大约有557个寄存器。但一些我不确定的外设情况。
现代英特尔CPU使用集成的APICs作为其SMT实现的一部分。这些APICs有自己的寄存器库,可以被内存映射,以便由x86 CPU读取和潜在的修改。本文没有计算它们,因为(1)它们是内存映射的,因此表现得更像来自任意硬件的映射寄存器,而不是CPU寄存器
英特尔的SDM 说Last Branch Records 存储在离散的非MSR寄存器中,本文也没有单独计算这些。

你可能感兴趣的:(汇编)