x86 Architecture -x86 体系架构


系列目录: Processor Architecture - 处理器体系架构
原文: x86 Architecture

In this article - 此文

Intel x86 处理使用的是复杂指令集的计算机(complex instruction set computer (CISC),还有什么是RISC?RISC架构)体系架构,意味着使用的是少量的特定寄存器的数量,而不是使用巨量的通用寄存器的方式。也意味着复杂的特定指令将更有优势。

x86 处理器至少可以追溯到 8位的Intel 8080 处理器。x86指令集中的许多特性都是原由于处理器(及其Zilog Z-80变体)的向后兼容性。

Microsoft Win32 的Flat Mode(平坦模式1)使用的是 x86 处理。这个文档将近关注于在flat mode上的。

Register - 寄存器

x86 体系架构由以下非特权的整形寄存器组成。

寄存器描述
eax累加器,运算用(Accumulator)
ebx基地址寄存器(Base Register)
edx数据寄存器 - 用于I/O端口访问及算术函数(Data Register)
esi源索引寄存器(Source index register)
edi目标索引寄存器(Destination index register)
ebp基地址指针寄存器(Base poinster register)
esp栈指针寄存器(Stack pointer)

所有整形寄存器都是32位的。然而,有许多的16位或是8位的子寄存器。

寄存器描述
axeax的低16位寄存器(Low 16 bits of eax)
bxebx的低16位寄存器(Low 16 bits of ebx)
cxecx的低16位寄存器(Low 16 bits of ecx)
dxedx的低16位寄存器(Low 16 bits of edx)
siesi的低16位寄存器(Low 16 bits of esi)
diedi的低16位寄存器(Low 16 bits of edi)
bpebp的低16位寄存器(Low 16 bits of ebp)
spesp的低16位寄存器(Low 16 bits of esp)
aleax的低8位寄存器(Low 8 bits of eax)
ahax的高8位寄存器(High 8 bits of ax)
blebx的低8位寄存器(Low 8 bits of ebx)
bhbx的高8位寄存器(High 8 bits of bx)
clecx的低8位寄存器(Low 8 bits of ecx)
chcx的高8位寄存器(High 8 bits of cx)
dledx的低8位寄存器(Low 8 bits of edx)
dhdx的高8位寄存器(High 8 bits of dx)

操作子寄存器影响的仅仅只有子寄存器,而不会影响之外的其他子寄存器。例如,储存一些数据到 ax 寄存器,eax 剩余的高16位寄存器是不会有变化的。

当使用 ?(计算表达式) 命令,寄存器的前缀要带上 “at” 标记(@)。例如,你使用使用 ? @ax 而不是 ? ax 。确保调试器能认出 ax 是个寄存器(Register)而不是一个 标记符号(Symobl)。

然而,在r(寄存器)命令下不需要(@)。例如, r ax = 5 将会被正确解析。

还有其他两个表示处理器当前状态的重要寄存器:

寄存器描述
eip指令指针寄存器(instruction pointer)
flags标志寄存器(flags)

指令指针寄存器(instruction pointer)指向的是即将被执行的指令的地址。

标记寄存器(flags register)是单个bit位(single-bit)的标记集合。许多的指令都会修改 flags 的为,代表指令的结果。这些flags标记可以被后续的条件跳转指令(conditional jump)用于测试用。详情查考 x86 Flags

Calling Conventions - 调用约定

x86 体系架构有一些不同的调用约定。还好的是,他们都是下面一些寄存器的备份恢复与方法返回规则:

  • 函数必须备份恢复所有寄存器,除了 eaxecxedx,通过call可以改变这些寄存器,而 esp,必须通过对应的调用约定来更新。(译者jave.lin:文档真的不用心,call汇编是先push eip + sizeof(eip),就是先把call的以下个指令的地址push到栈中,在跳转到方法签名中的函数指令地址,所以有两个步骤在里头,但这个文档说的不够详细,这微软写x86指令文档这么儿戏的吗?手动哭笑一下。
  • eax 寄存器用于函数结果的<=32位的返回值。如果结果是64为的,那么如果储存在 edx:eax 寄存器对中。

下面是 x86 体系架构的调用约定项:

  • Win32( __stdcall
    函数的参数是通过从右向左的传入栈中,并且是在被调用函数(callee)中清理栈的。
  • C++内置的方法调用(大家了解的:thiscall
    函数参数也是从右向左传入栈,this指针是通过 ecx 寄存器传入的,也是在callee中清理栈的。
  • COM(__stdcall C++的方法调用)(译者jave.lin:这不就和上面的Win32一样了么?
    从右向左,this指针会传入,callee清理栈(描述意思和上面thiscall一样,这里简写,不知道为何要多此一举
  • __fastcall
    头两个 DWORD或是更小的字(DWORD-or-smaller) 参数通过 ecxedx 寄存器传入。剩下的参数都会传入栈,也是从右向左,也是callee清理栈。
  • __cdecl译者jave.lin:cdecl就是:c declaration,就是C中声明用的,为何微软不写这些,这些多么容易理解的点
    函数参数也是从右向左传入栈,但清理栈在调用函数(caller)处理。__cdecl调用约定一般用于变长参数数量。(译者jave.lin:如printf(const char *format, …),因为参数在调用函数是知道的,所以可以清理变长的参数栈

(函数 从左向右入栈 的有:FortranPascal语言,但实际我没去验证过,在此篇博客上看到:系统栈的工作原理

下面我来列个表格,好理解一写

Calling Convention Type(调用约定类型)Passing Parameters (参数传递)Cleaning Stack(栈清理)
__stdcall从右到左被调用函数
__fastcall从右到左被调用函数
__cdecl从左到右调用函数

C++内置的对象方法调用还需传递this对象指针

Debugger Display of Registers and Flags - 调试器显示寄存器与标记

这里有个调式器显示寄存器的示例:

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

在用户模式(user-mode)下调式,你可以忽略掉 iopl 与最后一整行(cs=001b ss=0023 ds=0023 es=0023 fs=0038 gs=0000 efl=00000286)的调式信息。

x86 Flag - x86 的标记位

在之前的例子中,第二行结尾两个字母的代码都是flags(标记为)(nv up ei ng nz na po nc)。这些都是 单bit位的寄存器,并且各方面都用到。

下面是 x86 中的 flags 的列表:

下面的列表格式微软中的文档乱七八糟的,排版出了问题,没人发现,没人处理?这里我自己排版一下,x86 Flags 的文档也可以看这个

Flag CodeFlag NameValue Flag StatusStatus Description
of溢出标记(Overflow Flag)0 nv,1 ov无溢出(No overflow), 溢出(Overflow)
df方向标记(Direction Flag)0 up, 1 dn上方(Direction up), 下方(Direction down)
if中断标记(Interrupt Flag)0 di, 1 ei中断禁用(Interrupts disabled), I中断启用(nterrupts enabled)
sf符号(签名)标记(Sign Flag)0 pl, 1 ng正数或零(Positive (or zero)), 负数(Negative)
zf零标记(Zero Flag)0 nz, 1 zr非零(Nonzero), 零(Zero)
af辅助进位标记(Auxiliary Carry Flag)0 na, 1 ac无辅助进位(No auxiliary carry), 有辅助进位(Auxiliary carry)
pf校验标记(Parity Flag)0 pe, 1 po偶校验(Parity even), 奇校验(Parity odd)
cf进位标记(Carry Flag)0 nc, 1 cy无进位(No carry), 有进位(Carry)
tf捕获标记(Trap Flag)-如果 tf 等于1,在执行一个指令后处理器将会抛出 STATUS_SINGLE_STEP 异常。这个标记用于调式器实现 单步调试(single-step)的跟踪。这不应用于其他的应用程序。

iopl
I/O特权等级(I/O Privilege Level)

这是一个2 bit 位的整型,值为:0~3之间。用于操作系统控制访问硬件用的。不应该用于应用程序。

当寄存器被显示在调试命令窗口中作为结果,它是以flag status方式显示。然而,如果你想调整flag,可用 r (Registers) 命令,你应该它当做是flag code。

在WinDbg(Windows 调试)的寄存器窗口中,flag code可展示或是修改flags。flag status方式是不支持的。

这里有个例子。之前的寄存器显示中,ng 的flag status有显示。这意味着符号标记的bit为当前为1。要修改的话,使用下面的命令:

r sf = 0

这是将符号标记设置0。如果你要显示其他的寄存器,ng 标记状态码(status code)将不会显示。而是会显示 pl 状态码。

符号标记(Sign Flag),零标记(Zero Flag),和进位标记(Carry Flag)都是最常用的标记。

Conditions - 条件

一个条件描述的是一个或多个标记的状态。在x86 所有操作条件都可有条件术语表示。

汇编器使用一个或多个字母来表示条件。一个条件可以用乘号缩写来表示。例如:AE(“above or equal(高于或等于)”)与NB(“not below(不低于)”)是一样的条件。下面列表中列出一些常见的条件和他们的意思:

Condition Name(条件名称)Flags(标记)Meaning(意思)
ZZF=1最后的操作结果为0。
NZZF=0最后的操作结果非0。
CCF=1最后的操作需要进位或借位。(对于无符号整型而言,这代表溢出。)
NCCF=0最后的操作不需要进位或借位。(对于无符号整型而言,这代表溢出。)
SSF=1最后的操作结果有高位bit集。
NSSF=0最后的操作结果没有高位bit集。
OOF=1当处理有符号整型操作是,最后的操作有上溢出或是下溢出。
NOOF=0当处理有符号整型操作是,最后的操作无上溢出或是下溢出。

条件也可以用于比较两个值。cmp 指令比较两个操作数,然后设置标记位,就像一个数减去另一个数。下面的条件可用于检查 cmp value1, value2的结果。

Condition Name(条件名称)Flags(标记)Meaning after a CMP operation(CMP操作后的意思)。
EZF=1value1==value2
NEZF=0value1!=value
GE NLSF=OFvalue>=value2。这些值被当作有符号整型。
LE NGZF=1 or SF!=OFvalue1<=value2。这些值被当作有符号整型。
G NLEZF=0 and SF=OFvalue1>value2。这些值被当作有符号整型。
L NGESF!=OFvalue1<value2。这些值被当作有符号整型。
AE NBCF=0value1>=value2。这些值被当作无符号整型。
BE NACF=1 or ZF=1value1<=value2。这些值被当作无符号整型。
A NBECF=0 and ZF=0value1>value2。这些值被当作无符号整型。
B NAECF=1value1<value2。这些值被当作无符号整型。

条件通常用于对 cmp 或是 test 指令结果的操作。例如:

cmp eax, 5
jz equal

用表达式 (eax - 5) 来计算比较 eax 寄存器 与 5 数字,并根据结果设置对应的标记位。如果差为0,那么 zr 标记位将被设置,并且 jz 条件会为帧,因此将会跳转处理。

Data Types - 数据类型

  • byte : 8 bits
  • word : 16 bits
  • dword : 32 bits
  • qword : 64 bits(包含双精度浮点)
  • tword : 80 bits(包含扩展的双精度浮点)
  • owrod : 128 bits

Notation - 符号

下面表格的符号代表用于汇编语言指令的描述:

NotationMeaning
r, r1, r2…寄存器
m内存地址(查看后续的寻址模式小节了解更多信息)。
#n立即数
r/m寄存器 或 内存
r/#n寄存器 或 立即数
r/m/#n寄存器 或 内存,或 立即数
cc之间的条件小节列表中的条件代码。
T“B”, “W"或"D”(byte, word 或 dword)
accTSize T accumulator(累加器T的大小):如果是al T = “B”,如果是 ax T=“W”,或是如果 eax T = “D”

Addressing Modes - 寻址模式

有一些不同的寻址模式,但他们所有采用 T ptr [expr] 的形式,T 是数据类型(查看之前的 Data Type 数据类型小节), expr 表达式涉及 常数与寄存器。

多系数的模式的符号可以不太难的就推导出来。例如,BYTE PTR [esi + edx * 8 + 3] 意思是“取 esi 寄存器的值,加上 edx 值的8倍的值,再加上3,然后以结果为地址取去一个字节。”

Pipelining - 流水线

什么是Pipelining,可以参考:2

崩腾有双工问题,这意味着它可以在一个时钟周期可以执行两个行为。然而,什么时候能同时执行两个行为(成对的行为)是非常复杂的。

因为 x86 是CISC处理器,你不需要担心 跳过延迟槽(jump delay slots) 的问题

Synchronized Memory Access - 同步的内存访问

加载、修改,与储存指令都会遇到一个 lock 修改指令的前置问题,如下:

  1. 在发出指令前,CPU将会flush掉所有pending的内存操作,以确保内存数据一致性。预先取到的所有数据将放弃。
  2. 当发出指令时,CPU将独占的访问总线。确保load(加载)/modify(修改)/store(储存)操作的原子性。

xchg 指令将自动遵循前面的规则,无论何时都会使用内存数据来调整数值。

所有其他的指令默认无锁。

Jump Prediction - 跳转预测

无条件跳转(jumps)都是可被预测。

有条件跳转的预测可能会也可能不会,依赖于他们最后一次的执行。记录跳转历史的缓存是有大小限制的。

如果CPU没有记录最后一次的条件跳转是否被执行,那么反向条件跳转将被预测执行,而正向条件跳转不会被执行。

是文档太绕还是我理解不了,-_-!!!

Alignment - 对齐

x86 处理器将自动修正没对齐的内存访问,但会有性能惩罚。也没有异常抛出。

如果对象的地址大小是整数倍的,那么这一内存访问是被认为是对齐的。例如,所有的BYTE访问都被认为是对齐的(都是1(字节)的倍数),WORD方位偶数地址是对齐的,而DWORD的地址为了对齐必须是4(个字节)的倍数。

lock 不应该用于无对齐的内存访问。


  1. FLAT 模式 - 因为和 16 位 Windows 下的把代码分成 DATA,CODE 等段的内存模式不同,WIN32 只有一种内存模式,即 FLAT 模式, 意思是"平坦"的内存模式,再没有 64K 的段大小限制,所有的 WIN32 的应用程序运行在一个连续、平坦、巨大的 4GB 的空间中。这同时也意 味着您无须和段寄存器打交道,您可以用任意的段寄存器寻址任意的地址空间,这对于程序员来说是非常方便的。在Win32下编程,有许多重要的规则需要遵 守。有一条很重要的是:Windows 在内部频繁使用 ESI,EDI,EBP,EBX 寄存器,而且并不去检测这些寄存器的值是否被更改,这样当您要 使用这些寄存器时必须先保存它们的值,待用完后再恢复它们。 ↩︎

  2. How Pipelining Works计算机体系结构——流水线技术(Pipelining)计算机体系结构——流水线技术 ↩︎

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值