x86 体系结构

Intel x86 处理器使用复杂的指令集计算机(CISC)体系结构,这意味着有少量的特殊用途寄存器,而不是大量的通用寄存器。 这也意味着复杂的特殊用途指令将占主导地位。

x86 处理器的起源至少可以追溯到 8 位 Intel 8080 处理器。 x86 指令集中的许多特点是因为要保持与 x86 处理器及其 Zilog Z-80 变体的向后兼容性。

Microsoft Win32 在 32 位平面模式下使用 x86 处理器。 本文档将仅重点介绍平面模式。

寄存器

x86 体系结构由以下无特权整数寄存器组成。

eax

蓄电池

ebx

基本寄存器

ecx

计数器寄存器

edx

数据寄存器 - 可用于 I/O 端口访问和算术函数

esi

源索引寄存器

edi

目标索引寄存器

ebp

基本指针寄存器

esp

堆栈指针

所有整数寄存器均为 32 位。 然而,它们中的许多具有 16 位或 8 位子寄存器。

ax

低 16 位 eax

bx

低 16 位 ebx

cx

低 16 位 ecx

dx

低 16 位 edx

si

低 16 位 esi

di

低 16 位 edi

bp

低 16 位 ebp

sp

esp 的低 16 位

al

低 8 位 eax

高 8 位 AX

bl

低 8 位 ebx

bh

高 8 位 bx

cl

低 8 位 ecx

ch

高 8 位 cx

dl

低 8 位 edx

dh

高 8 位 dx

在子寄存器上操作仅影响子寄存器本身,并且不影响子寄存器之外的部分。 例如,存储到 ax 寄存器会使 eax 寄存器的高 16 位保持不变。

使用(计算表达式)命令时,寄存器应以“@”符号@作为前缀。 例如,应使用 ? @ax 而不是 ? ax。 这可确保调试器将 ax 识别为寄存器而不是符号。

但是, r (Registers) 命令中不需要 (@) 。 例如,r ax=5 会始终被正确解释。

另外两个寄存器对于处理器的当前状态非常重要。

eip

指令指针

标志

flags

指令指针是所执行指令的地址。

标志寄存器是单比特标志的集合。 许多指令更改标志以描述指令的结果。 然后,可以通过条件跳转指令测试这些标志。 有关详细信息 ,请参阅 x86 标志

调用约定

x86 体系结构具有多个不同的调用约定。 幸运的是,它们都遵循相同的寄存器保留和函数返回规则:

  • 函数必须保留除 eaxecxedx 之外的所有寄存器,这些寄存器可以跨函数调用进行更改, 并且 esp 必须根据调用约定进行更新。

  • 如果结果为 32 位或更小, eax 寄存器将接收函数返回值。 如果结果为 64 位,则结果存储在 edx:eax 对中。

下面是在 x86 体系结构上使用的调用约定的列表:

  • Win32 (__stdcall

    函数参数在堆栈上传递,从右向左压入,由被调用的函数清理堆栈。

  • 本机C++方法调用(也称为 thiscall)

    函数参数通过堆栈从右到左推入,“this”指针通过 ecx 寄存器传递,由被调用方负责清理堆栈。

  • COM(用于 C++ 方法调用的 __stdcall

    函数参数在栈中传递,从右向左推入,然后在栈上推入“this”指针,最后调用该函数。 被调用者清理堆栈。

  • __fastcall

    前两个 DWORD 或较小的参数在 ecxedx 寄存器中传递。 其余参数通过堆栈传递,从右向左推送。 被调用方清理堆栈。

  • __cdecl

    函数参数在堆栈上传递,从右到左推入,调用方负责清理堆栈。 __cdecl调用约定用于具有可变长度参数的所有函数。

调试器中寄存器和标志的显示

下面是一个调试器寄存器显示的示例:

eax=00000000 ebx=008b6f00 ecx=01010101 edx=ffffffff esi=00000000 edi=00465000
eip=77f9d022 esp=05cffc48 ebp=05cffc54 iopl=0         nv up ei ng nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000286

在用户模式调试中,可以忽略 iopl 和调试器显示的完整最后一行。

x86 标志

在前面的示例中,第二行末尾的双字母代码是 标志。 这些是单比特寄存器,具有多种用途。

下表列出了 x86 标志:

标记代码 标志名称 价值 标志状态 DESCRIPTION
溢出标志 0 1 nvov 无溢出 - 溢出
df 方向标志 0 1 updn 方向向上 - 方向向下
如果 中断标志 0 1 diei 已禁用中断 - 已启用中断
sf 标记标志 0 1 plng 正 (或零) - 负数
zf 零标志 0 1 nzzr 非零 - 零
af 辅助携带标志 0 1 naac 无辅助携带 - 辅助携带
pf 奇偶校验标志 0 1 pepo 奇校验 - 偶校验
cf 携带标志 0 1 nccy 不携带 - 携带
tf 陷阱标志 如果 tf 等于 1,处理器在执行一个指令后将引发STATUS_SINGLE_STEP异常。 调试器使用此标志来实现单步跟踪。 它不应由其他应用程序使用。
iopl I/O 特权级别 I/O 特权级别这是一个两位整数,其值介于零和 3 之间。 操作系统使用它用于控制对硬件的访问。 应用程序不应使用它。

当寄存器在调试器命令窗口由于某些命令而显示时,显示的是标志状态。 但是,如果要使用 r (Registers) 命令更改标志,则应通过 标志代码引用它。

在 WinDbg 的“寄存器”窗口中,标志位码用于查看或更改标志。 不支持标记状态。

以下是一个示例。 在前述寄存器显示中,标志状态 ng 出现。 这意味着签名标志当前设置为 1。 若要更改此项,请使用以下命令:

r sf=0

这会将符号标志设置为零。 如果再次进行寄存器显示操作,则不会显示 ng 状态代码。 相反,将显示 pl 状态代码。

标志、零标志和携带标志是最常用的标志。

条件

条件描述一个或多个标志的状态。 x86 上的所有条件操作均以条件表示。

汇编程序使用一两个字母缩写来表示条件。 条件可以由多个缩写表示。 例如,AE(“高于或等于”)与 NB 相同(“不低于”)。 下表列出了一些常见条件及其含义。

条件名称 旗帜 含义

Z

ZF=1

上次操作的结果为零。

新西兰

ZF=0

上次操作的结果不是零。

C

CF=1

上次操作需要进位或借位。 (对于无符号整数,这表示溢出。

NC

CF=0

上次操作不需要进位或借位。 (对于无符号整数,这表示溢出。

S

SF=1

上次作的结果具有其高位集。

NS 系列

SF=0

最后一个操作的结果高位被清除。

O

OF=1

当被视为有符号整数运算时,最后一次操作导致了溢出或下溢。

OF=0

当被视为有符号整数运算时,最后一个运算不会导致溢出或下溢。

条件还可用于比较两个值。 cmp 指令用于比较其两个操作数,然后设置标志位,就像从一个操作数中减去另一个操作数一样。 以下条件可用于检查 cmpvalue1value2 的结果。

条件名称 旗帜 CMP操作后的含义。

E

ZF=1

value1 == value2

NE

ZF=0

value1 != value2

GE NL

SF=OF

value1>= value2。 值被视为有符号整数。

LE NG

ZF=1 或 SF!=OF

value1<= value2。 值被视为有符号整数。

G NLE

ZF=0 和 SF=OF

value1>value2。 值被视为有符号整数。

L NGE

SF!=OF

value1<value2。 值被视为有符号整数。

AE NB

CF=0

value1>= value2。 值被视为无符号整数。

BE NA

CF=1 或 ZF=1

value1<= value2。 值被视为无符号整数。

A NBE

CF=0 和 ZF=0

value1>value2。 值被视为无符号整数。

B NAE

CF=1

value1<value2。 值被视为无符号整数。

条件通常用于处理 cmp测试 指令的结果。 例如,

cmp eax, 5
jz equal

通过计算表达式( eax - 5)并根据结果设置标志,将 eax 寄存器与数字 5 进行比较。 如果减法的结果为零,则 zr 标志将被设置,jz 条件将成立,因此将执行跳转。

数据类型

  • 字节:8 位

  • word:16 位

  • dword:32 位

  • qword:64 位(包括浮点双精度)

  • tword: 80 位元(包括浮点数扩展双精度)

  • oword:128 位

Notation

下表指示用于描述程序集语言指令的表示法。

Notation 含义

rr1r2...

寄存器

m

内存地址(有关详细信息,请参阅后面的寻址模式部分。)

#n

即时常量

r/m

寄存器或内存

r/#n

注册或即时常量

r/m/#n

寄存器、内存或即时常量

cc

前面“条件”部分中列出的条件代码。

T

“B”、“W”或“D”(字节、单词或 dword)

accT

大小 T 累加器:al 如果 T = “B”,ax 如果 T = “W”,或者 eax 如果 T = “D”

寻址模式

有多种不同的寻址模式,但它们都采用 T ptr [expr] 形式,其中 T 是一些数据类型(请参阅前面的数据类型部分), expr 是涉及常量和寄存器的一些表达式。

大多数模式的表示法可以推断出来,而不会造成太大的困难。 例如, BYTE PTR [esi+edx*8+3] 表示“获取 esi 寄存器的值,将其添加到 edx 寄存器的值的八倍,再添加三个,然后在生成的地址访问字节。

管道

奔腾是双发射的,这意味着它可以在一个时钟周期内执行多达两个操作。 但是,有关何时能够同时执行两个动作(称为配对)的规则非常复杂。

由于 x86 是 CISC 处理器,因此无需担心跳转延迟槽。

同步内存访问

加载、修改和存储指令可以接收 前缀,该前缀将指令修改如下所示:

  1. 发出指令之前,CPU 将刷新所有挂起的内存操作,以确保一致性。 所有数据预提取将被放弃。

  2. 发出指令时,CPU 将具有对总线的独占访问权限。 这可确保加载/修改/存储操作的原子性。

每当 xchg 指令与内存交换值时,它都会自动遵守以前的规则。

所有其他指令默认为非锁定。

跳转预测

预计将采取无条件跳跃。

根据条件跳转的上次执行结果,预测要采取还是不采取条件跳转。 用于录制跳转历史记录的缓存的大小有限。

如果 CPU 没有记录条件跳转上次是执行还是未执行,它会预测后向条件跳转为已执行,而前向条件跳转为未执行。

对齐

x86 处理器会自动更正未对齐的内存访问,但会导致性能下降。 不会引发异常。

如果地址是对象大小的整数倍数,则内存访问被视为对齐。 例如,所有 BYTE 访问都对齐(一切都是 1 的整数倍数),WORD 对偶数地址的访问是对齐的,DWORD 地址必须是 4 的倍数才能对齐。

前缀不应用于未对齐的内存访问。

另请参阅

x64 体系结构

X86-64 维基百科