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 |
殘雪 |
Ecx的低 16 位 |
Dx |
低 16 位 edx |
si |
esi的低 16 位 |
di |
低 16 位 edi |
Bp |
低 16 位 ebp |
sp |
esp的低 16 位 |
鋁 |
低 8 位 eax |
啊 |
高 8 位 的 ax |
bl |
低 8 位 ebx |
Bh |
高 8 位 bx |
Cl |
Ecx的低 8 位 |
ch |
高 8 位 cx |
Dl |
低 8 位 edx |
dh |
高 8 位 dx |
在子登錄上操作只會影響子註冊,而且子註冊以外的部分都不會影響。 例如,儲存至 ax 暫存器會維持 Eax 暫存器的高 16 位不變。
使用 ? (Evaluate Expression) 命令時,暫存器前面應該加上 「at」 符號 ( @ ) 。 例如,您應該使用? @ax而不是? ax。 這可確保偵錯工具會將 ax 辨識為暫存器,而不是符號。
不過, r (Registers) 命令中不需要 (@) 。 例如, r ax=5 一律會正確解譯。
處理器目前狀態有兩個其他暫存器很重要。
Eip |
指令指標 |
flags |
flags |
指令指標是所執行指令的位址。
旗標暫存器是單一位旗標的集合。 許多指示都會改變旗標來描述指令的結果。 然後,這些旗標可以透過條件式跳躍指示進行測試。 如需詳細資訊 ,請參閱 x86 旗標 。
呼叫慣例
x86 架構有數個不同的呼叫慣例。 幸運的是,它們全都遵循相同的暫存器保留和函式傳回規則:
函式必須保留所有暫存器,但 eax、 ecx和 edx除外,它可以在函式呼叫和 esp 之間變更,而 esp必須根據呼叫慣例進行更新。
如果結果為 32 位或更小, eax 暫存器就會接收函式傳回值。 如果結果為 64 位,則結果會儲存在 edx:eax 配對中。
以下是 x86 架構上所使用的呼叫慣例清單:
Win32 (__stdcall)
函式參數會在堆疊上傳遞、由右至左推送,而被呼叫端會清除堆疊。
原生 C++ 方法呼叫 (也稱為 thiscall)
函式參數會在堆疊上傳遞、由右至左推入、在 ecx 暫存器中傳遞「this」 指標,而被呼叫者會清除堆疊。
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 旗標:
旗標程式碼 | 旗標名稱 | 值 | 旗標狀態 | Description |
---|---|---|---|---|
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 如果 T = 「W」,則為 eax if 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 的倍數才能對齊。
鎖定前置詞不應該用於未對齊的記憶體存取。
意見反應
https://aka.ms/ContentUserFeedback。
即將登場:在 2024 年,我們將逐步淘汰 GitHub 問題作為內容的意見反應機制,並將它取代為新的意見反應系統。 如需詳細資訊,請參閱:提交並檢視相關的意見反應