x86 体系结构

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

x86 处理器至少追溯到 8 位 Intel 8080 处理器的继承。 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

残雪

低 16 位 ecx

Dx

低 16 位 edx

低 16 位 esi

di

低 16 位 edi

Bp

低 16 位 ebp

sp

低 16 位 esp

低 8 位 的 eax

ah

高 8 位 ax

bl

低 8 位 ebx

Bh

高 8 位 bx

Cl

低 8 位 ecx

ch

高 8 位 cx

Dl

低 8 位 edx

dh

高 8 位 dx

在子注册上操作仅影响子注册,而不会影响子注册之外的所有部分。 例如,存储到 ax 寄存器会保持 eax 寄存器的高 16 位不变。

使用 ? (Evaluate Expression) 命令时,寄存器的前缀应为"at", () @ 。 例如,应该使用 ? @ax 而不是 ?ax。 这可确保调试器将 ax 识别为寄存器而不是符号。

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

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

Eip

指令指针

flag

flags

指令指针是正在执行的指令的地址。

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

调用约定

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

  • 函数必须保留除 eaxecxedx 之外的所有寄存器,这些寄存器可以在函数调用和 esp 中更改,这些寄存器必须按照调用约定进行更新。

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

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

  • Win32 (__stdcall)

    函数参数在堆栈上传递,从右向左推送,被调用者清理堆栈。

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

    函数参数在堆栈上传递,从右向左推送,"this"指针在 ecx 寄存器中传递,被调用者清理堆栈。

  • COM (__stdcall C++ 方法调用)

    函数参数在堆栈上传递,从右向左推送,然后将"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 标志:

标记代码 标志名称 标志状态 说明
溢出标志 0 1 nvov 无溢出 - 溢出
df 方向标志 0 1 updn 向上 - 向下方向
if 中断标志 0 1 diei 已禁用中断 - 已启用中断
S f 符号标志 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 特权级别 这是一个两位整数,其值介于 0 和 3 之间。 操作系统使用它来控制对硬件的访问。 应用程序不应使用。

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

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

下面是一个示例。 在上一个寄存器显示中,将显示标志状态 ng 。 这意味着符号标志当前设置为 1。 若要更改此参数,请使用以下命令:

r sf=0

这会将符号标志设置为零。 如果执行另一个寄存器显示, 则 ng 状态代码将不会显示。 而是会显示 pl 状态代码。

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

条件

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

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

条件名称 Flags 含义

Z

ZF=1

最后一个操作的结果为零。

NZ

ZF=0

最后一个操作的结果不为零。

C

CF=1

最后一个操作需要携带或借用。 (对于无符号整数,这表示 overflow.)

NC

CF=0

最后一道工序不需要带有或借用的。 (无符号整数,表示溢出。 )

S

SF = 1

上次操作的结果设置了其高位。

NS

SF = 0

上次操作的结果是其高清晰。

O

Of = 1

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

Of = 0

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

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

条件名称 Flags 表示 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。 值被视为无符号整数。

为 NA

CF = 1 或 ZF = 1

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

N

CF = 0,ZF = 0

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

B NAE

CF = 1

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

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

cmp eax, 5
jz equal

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

数据类型

  • 字节:8位

  • word:16位

  • dword:32位

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

  • tword:80位 (包含浮点扩展双精度)

  • oword:128位

图解

下表指示用于描述汇编语言说明的表示法。

表示法 含义

rr1r2.。。

寄存器

m

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

#n

立即常量

r/m

寄存器或内存

r/#n

注册或立即常量

r/m/#n

Register、memory 或 immediate 常量

字幕

上述条件部分中列出的条件代码。

T

"B"、"W" 或 "D" (字节、字或 dword)

accT

大小为"累加器":如果t = "B",则ax if t = "W",或者如果t = "D",则为eax

寻址模式

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

大多数模式的表示法可能不太困难。 例如, BYTE PTR [esi + edx * 8 + 3] 表示 "获取 esi 寄存器的值,将 edx 寄存器值的8倍添加到它,添加三个,然后在生成的地址访问字节。"

传送

Pentium 是双重问题,这意味着它可以在一个时钟周期内最多执行两个操作。 不过,一次能够同时执行两个操作 (称为 配对) 的规则非常复杂。

由于 x86 是 CISC 的处理器,因此不必担心跳过延迟槽。

同步内存访问

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

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

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

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

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

跳转预测

预测将进行无条件跳转。

根据条件跳转是最后一次执行还是不执行,预测是否执行条件跳转。 用于记录跳转历史记录的缓存的大小受到限制。

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

对准

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

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

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