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 |
cx | 低 16 位 ecx |
dx | 低 16 位 edx |
si | 低 16 位 esi |
di | 低 16 位 edi |
Bp | 低 16 位 ebp |
sp | 低 16 位 esp |
al | 低 8 位 eax |
啊 | 高 8 位斧头 |
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 识别为寄存器而不是符号。
但是, r (Registers) 命令中不需要 (@) 。 例如, r ax=5 将始终正确解释。
另外两个寄存器对于处理器的当前状态很重要。
Eip | 指令指针 |
flag | flags |
指令指针是正在执行的指令的地址。
标志寄存器是单位标志的集合。 许多指令会更改标志来描述指令的结果。 然后,可以通过条件跳转指令测试这些标志。 有关详细信息 ,请参阅 x86 标志 。
调用约定
x86 体系结构具有几种不同的调用约定。 幸运的是,它们都遵循相同的寄存器保留和函数返回规则:
- 函数必须保留除 eax、 ecx 和 edx 之外的所有寄存器,这些寄存器可以在函数调用中更改, 并且 esp 必须根据调用约定进行更新。
- 如果结果为 32 位或更小, 则 eax 寄存器接收函数返回值。 如果结果为 64 位,则结果存储在 edx:eax 对中。
下面是在 x86 体系结构上使用的调用约定的列表:
- Win32 (__stdcall)
函数参数在堆栈上传递,向右推送到左侧,被调用方清理堆栈。
- 本机 C++ 方法调用 (也称为 thiscall)
函数参数在堆栈上传递,向右向左推送,“this”指针在 ecx 寄存器中传递,被调用方清理堆栈。
- 适用于 C++ 方法调用的 COM (__stdcall)
函数参数在堆栈上传递,向右向左推送,然后在堆栈上推送“this”指针,然后调用该函数。 被调用方清理堆栈。
- __fastcall
前两个 DWORD 或更小的参数在 ecx 和 edx 寄存器中传递。 其余参数在堆栈上传递,从右向左推送。 被调用方清理堆栈。
- __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 标志:
标志代码 | 标志名称 | 值 | 标志状态 | 说明 |
---|---|---|---|---|
of | 溢出标志 | 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 特权级别这是一个两位整数,值介于零和 3 之间。 操作系统使用它来控制对硬件的访问。 应用程序不应使用它。 |
当寄存器显示为调试器命令窗口中某些命令的结果时,它是显示的 标志状态 。 但是,如果要使用 r (Registers) 命令更改标志,则应通过 标志代码引用它。
在 WinDbg 的“寄存器”窗口中,标志代码用于查看或更改标志。 不支持标志状态。
下面是一个示例。 在前面的寄存器显示中,将显示标志状态 ng 。 这意味着签名标志当前设置为 1。 若要更改此项,请使用以下命令:
r sf=0
这会将符号标志设置为零。 如果执行另一个寄存器显示, 则不会显示 ng 状态代码。 相反,将显示 pl 状态代码。
标志、零标志和携带标志是最常用的标志。
条件
条件描述一个或多个标志的状态。 x86 的所有条件操作都以条件表示。
汇编程序使用一两个字母缩写来表示条件。 条件可以由多个缩写表示。 例如,AE (“高于或等于”) 的条件与 NB (“不低于”) 相同。 下表列出了一些常见条件及其含义。
条件名称 | Flags | 含义 |
---|---|---|
Z | ZF=1 | 上次操作的结果为零。 |
NZ | ZF=0 | 上次操作的结果不是零。 |
C | CF=1 | 上次操作需要携带或借款。 (对于无符号整数,这表示 overflow.) |
NC | CF=0 | 上次操作不需要携带或借款。 (对于无符号整数,这表示 overflow.) |
S | SF=1 | 上一个操作的结果具有其高位集。 |
NS | SF=0 | 最后一个操作的结果具有较高的位清除。 |
O | OF=1 | 当被视为有符号整数操作时,最后一个操作会导致溢出或下溢。 |
是 | OF=0 | 当被视为有符号整数运算时,最后一个操作不会导致溢出或下溢。 |
条件还可用于比较两个值。 cmp 指令比较其两个操作数,然后设置标志,就像从另一个操作数中减去一个操作数一样。 以下条件可用于检查 cmpvalue1、 value2 的结果。
条件名称 | 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。 值被视为无符号整数。 |
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 条件为 true,因此将采用跳转。
数据类型
- 字节:8 位
- word:16 位
- dword:32 位
- qword:64 位 (包括浮点双精度)
- 第二个:80 位 (包括浮点扩展双精度)
- oword:128 位
符号
下表指示用于描述程序集语言指令的表示法。
表示法 | 含义 |
---|---|
r、 r1、 r2… | 寄存器 |
m | 内存地址 (请参阅“成功寻址模式”部分了解详细信息。) |
#n | 即时常量 |
r/m | 注册或内存 |
r/#n | 注册或即时常量 |
r/m/#n | 寄存器、内存或即时常量 |
Cc | 前面“条件”部分中列出的条件代码。 |
T | “B”、“W”或“D” (字节、单词或 dword) |
accT | 大小 T 累加器: 如果T = “B”, ax if T = “W”, 或 eax if T = “D” |
寻址模式
有多种不同的寻址模式,但它们都采用 T ptr [expr] 形式,其中 T 是一些数据类型 (请参阅前面的数据类型部分) , expr 是涉及常量和寄存器的一些表达式。
可以推断大多数模式的表示法,而不会造成太大的困难。 例如, BYTE PTR [esi+edx*8+3] 表示“获取 esi 寄存器的值,将其添加到 edx 寄存器的值的 8 倍,添加 3 个,然后在生成的地址访问字节。
流水线
奔驰是双重问题,这意味着它可以在一个时钟刻度内执行多达两个操作。 但是,当规则能够同时执行两个操作 (称为 配对) 是非常复杂的。
由于 x86 是 CISC 处理器,因此无需担心跳转延迟槽。
同步内存访问
加载、修改和存储说明可以接收 锁 前缀,该前缀将修改指令,如下所示:
- 发出指令之前,CPU 将刷新所有挂起的内存操作,以确保一致性。 将放弃所有数据预提取。
- 发出指令时,CPU 将具有对总线的独占访问权限。 这可确保加载/修改/存储操作的原子性。
每当 xchg 指令与内存交换值时,xchg 指令都会自动遵守以前的规则。
所有其他指令默认为非锁。
跳转预测
预计将采取无条件跳跃。
条件跳转预计将被采用,具体取决于他们上次执行的时间。 用于录制跳转历史记录的缓存的大小有限。
如果 CPU 没有记录条件跳转是否是在上次执行条件跳转时拍摄的,则它会预测后向条件跳跃,而前向条件跳跃与不采用。
对准
x86 处理器会在性能损失时自动更正未对齐的内存访问。 不会引发异常。
如果地址是对象大小的整数倍数,则内存访问被视为对齐。 例如,所有 BYTE 访问都对齐 (一切都是 1 个) 的整数倍数,对偶数的 WORD 访问是对齐的,DWORD 地址必须是 4 的倍数才能对齐。
锁 前缀不应 用于未对齐的内存访问。
转载请注明:落伍老站长 » 微软提供的资料:x86 体系结构