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位子暫存器。
x |
低 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 位元的 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 辨識為緩存器,而不是符號。
不過,r (Registers) 命令中不需要 (@) 。 例如,r ax=5 一律會正確解譯。
另外兩個緩存器對於處理器的目前狀態很重要。
eip |
指令指標器 |
旗標 |
旗幟 |
指令指標是所執行指令的位址。
旗標暫存器是單一位旗標的集合。 許多指示會改變旗標來描述指示的結果。 然後,這些旗標可以透過條件式跳躍指示進行測試。 如需詳細資訊,請參閱 x86 旗標。
呼叫慣例
x86 架構有數個不同的呼叫慣例。 幸運的是,它們都遵循相同的寄存器保存和函式返回規則:
函式必須保留所有暫存器,除了 eax、ecx和 edx之外,這些暫存器可以在函式呼叫中被更改,而 esp則必須依照呼叫慣例進行更新。
如果結果為 32 位或更小,則 eax 快存器會收到函式傳回值。 如果結果為64位,則結果會儲存在 edx:eax 組中。
以下是 x86 架構上所使用的呼叫慣例清單:
Win32 (__stdcall)
函式參數會在堆疊上傳遞、由右至左推入,而被呼叫者會清除堆疊。
原生C++方法呼叫 (也稱為 thiscall)
函式參數會在堆疊上傳遞,由右至左推入,“this” 指標會傳入 ecx 暫存器,而被呼叫者會清除堆疊。
COM (__stdcall 用於 C++ 方法呼叫)
函式參數透過堆疊傳遞,以右至左順序推入,接著將“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 旗標:
旗幟代碼 | 旗標名稱 | 價值 | 旗標狀態 | 描述 |
---|---|---|---|---|
的 |
溢位旗標 | 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
這會將符號旗標設定為零。 如果您執行另一個寄存器顯示, 狀態碼將不會出現。 相反地,會顯示 pl 狀態代碼。
[標誌旗標]、[零旗標] 和 [攜帶旗標] 是最常用的旗標。
條件
條件 描述一或多個旗標的狀態。 x86 上的所有條件式作業都會以條件表示。
組合器會使用一或兩個字母縮寫來表示條件。 條件可以透過多個縮寫來表示。 例如,「AE」(大於或等於)與「NB」(不低於)是相同的條件。 下表列出一些常見條件及其意義。
條件名稱 | 標誌 | 意義 |
---|---|---|
Z |
ZF=1 |
最後一個作業的結果為零。 |
紐西蘭 |
ZF=0 |
上次操作的結果不是零。 |
C |
CF=1 |
上次作業需要攜帶或借用。 (對於不帶正負號的整數,這表示溢位。 |
數控 |
CF=0 |
上次作業不需要攜帶或借用。 (對於不帶正負號的整數,這表示溢位。 |
S |
SF=1 |
上次操作的結果其高位元已設定。 |
NS |
SF=0 |
最後一個操作的結果其最高位被清除。 |
O |
OF=1 |
當被視為帶正負號的整數作業時,最後一個作業會造成溢位或下溢。 |
不 |
OF=0 |
當被視為帶正負號的整數作業時,最後一個作業不會造成溢位或下溢。 |
條件也可以用來比較兩個值。 cmp 指令會比較其兩個操作數,然後設定旗標,就像從另一個操作數減去一個操作數一樣。 下列條件可用來檢查 cmpvalue1的結果,value2。
條件名稱 | 標誌 | 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 或 test 指令的結果上。 例如
cmp eax, 5
jz equal
藉由計算表達式(eax - 5),並根據結果設定標誌,對比 eax 暫存器與數字 5。 如果減法的結果為零,則會設定 zr 旗標,並且 jz 條件將成立,因此會發生跳轉。
數據類型
位元組:8 位
字組:16 位元
dword:32 位元
qword:64 位元(包含雙精度浮點數)
tword:80 位元(包括浮點擴展雙精度數)
oword:128 位
表示法
下表指出用來描述彙編語言指示的表示法。
表示法 | 意義 |
---|---|
rr1r2... |
暫存器 |
m |
記憶體位址 (如需詳細資訊,請參閱後續尋址模式一節。 |
#n |
即時常數 |
r/m |
暫存器或記憶體 |
r/#n |
暫存器或立即常數 |
r/m/#n |
緩存器、記憶體或即時常數 |
cc |
上述 [條件] 區段中所列的條件代碼。 |
T |
“B”、“W” 或 “D”(位元組、單字或雙字組) |
accT |
大小 T 累積器:al 如果 T = “B”;ax 如果 T = “W”;eax 如果 T = “D” |
尋址模式
有不同的尋址模式,但它們全都採用 T ptr [expr]的形式,其中 T 是一些數據類型(請參閱先前的數據類型一節),expr 是涉及常數和緩存器的一些表達式。
大部分模式的表示法可以推斷,而不需要太多困難。 例如,BYTE PTR [esi+edx*8+3] 表示「取得 esi 暫存器的值,將其加上 edx 暫存器的值的八倍,再加上三,然後存取結果位址的位元組」。
流水線
Pentium 是雙發射架構,這表示它可以在一個時鐘周期內執行最多兩個指令。 然而,當它能夠同時執行兩個動作的規則,即所謂的 配對,是非常複雜的。
因為 x86 是 CISC 處理器,因此您不必擔心跳躍延遲插槽。
同步記憶體存取
載入、修改和儲存指令可以接收 鎖定 前綴,這會將指令修改為如下所示:
發出指令之前,CPU 會排清所有擱置中的記憶體作業,以確保一致性。 系統會放棄所有數據預先擷取。
發出指示時,CPU 將具有總線的獨佔存取權。 這可確保載入/修改/儲存作業的原子性。
每當 xchg 指令與記憶體交換值時,就會自動遵守先前的規則。
所有其他指示預設為非鎖定。
跳躍預測
預計會採用無條件跳躍。
條件式跳躍會根據上次執行時是否被採用,預測在這次會被採用或不被採用。 錄製跳躍歷程記錄的快取大小有限。
如果 CPU 沒有記錄上次執行時條件式跳躍是否被採用,它會預測回溯條件式跳躍為採用,而前向條件式跳躍為不採用。
對齊
x86 處理器會自動更正未對齊的記憶體存取,但這會導致效能下降。 不會引發例外狀況。
如果位址是物件大小的整數倍數,則記憶體存取被視為對齊。 例如,所有 BYTE 存取都會對齊(所有專案都是 1 的整數倍數)、連位址的 WORD 存取都對齊,而 DWORD 位址必須是 4 的倍數才能對齊。
鎖定 前綴不應用於未對齊的記憶體存取。