x86,她來了!

cxuan 發佈 2023-11-15T16:42:54.811772+00:00

X86 寄存器總結首先來認識一下 INTEL 體系下的 x86 指令集的相關知識。x86 是 intel 公司開發的一種 32 位指令集,自從 80386 開始一直沿用至今,它是一種複雜指令集的架構,intel 官方把這種指令集叫做 "IA-32" 。

X86 寄存器總結

首先來認識一下 INTEL 體系下的 x86 指令集的相關知識。

x86 是 intel 公司開發的一種 32 位指令集,自從 80386 開始一直沿用至今,它是一種複雜指令集的架構,intel 官方把這種指令集叫做 "IA-32" 。

X86_64 是一種 64 位指令集,所以我們常說的 x86_64 和 x86 的主要區別就是 32 位和 64 位的問題,這倆指令集體系的指令長度不同。指令集是 CPU 的語言,32 位指令集表示 CPU 一次能夠處理 32 位數據,64 位指令集表示 CPU 一次能夠處理 64 位的數據,更為高效。

這個 32 位和 64 位也表示著我們常說的 32 位作業系統或者 64 位作業系統。

x86 中的 X 表示一個範圍,這個範圍裡面泛指很多指令集型號,後來為了統一,把 x86 就指代的是 32 位指令集。

x86 家族下的指令集主要有

  • 8086、8088 - 16 位寄存器
  • 80186、80286 是兩個過度產品
  • 80386、80486 以及後面的各種型號都是 32 位寄存器。

x86 寄存器

熟悉我的小夥伴都知道,我寫過幾篇彙編語言的文章,而且還在持續更新中,我寫的彙編語言就是基於 8086 彙編為前提的,8086 彙編中的寄存器是 16 位的,而 x86 寄存器的位數都是 32 位的,主要分為:

  • 8 個通用寄存器,分別是 EAX、EBX、ECX、EDX、ESI、EDI、ESP、EBP
  • 1 個標誌寄存器:EFlags
  • 6 個段寄存器:CS、DS、ES、FS、GS、SS
  • 5 個控制寄存器:CR0 - CR4
  • 8 個調試寄存器:DR0 - DR7
  • 4 個系統地址寄存器:GDTR、IDTR、LDTR、TR
  • 其他寄存器:EIP、TSC 等。

通用寄存器

我們在學彙編的時候知道(不過你沒學過彙編也不會妨礙,我會儘量平鋪直敘些),十六位的寄存器比如 AX、BX、CX、DX 它們都是可以再細分的,如下圖所示。

拿 AX 和 AH、AL 來舉例子,我們可以把數據暫存在 AX 中,也可以把數據暫存在 AL 和 AH 中,它們可以單獨使用,也可以一起使用。

需要注意的是,暫存在 AX 中的數據,其內部真實情況還是寫入了 AL 中,當數據超過 AL 所能暫存的最大值後才會寫入到 AH 中,不能把數據直接暫存在 AH 中。AX = AL + AH ,好好體會這個公式,其他三個寄存器類似,就是暫存的數據和指令類型不同。

回到 32 位的 x86 寄存器中來,也是同樣的情況,EAX、EBX、ECX、EDX 這四個 32 位寄存器都可以再細分,如下所示:

除了這 4 個 32 位寄存器可以分為 8 位寄存器之外,其他通用寄存器不可再分為 8 位的寄存器(ESI、EDI、ESP、EBP)。

下面來介紹一下這幾個寄存器的主要作用:

可再分成 8 位的通用寄存器 - 4 個。

  • EAX:累加器(Accumulator),它的低 16 位是 AX,AX 可以再細分為 AH 和 AL。EAX 是很多乘除法的默認寄存器,比如我們學 8086 的時候,被除數就會默認放在 AX 中或者 AX 和 DX 中。EAX 同理。還可以存放函數的返回值、存放段的偏移地址等。
  • EBX:基地址寄存器(Base Register),它的低 16 位是 BX ,BX 還可以再細分為 BH 和 BL。主要用於在尋址內存單元時存放基地址。
  • ECX:計數寄存器(Count Register),它的低 16 位是 CX,CX 可以再分為 CH 和 CL。ECX 主要用於在循環指令中控制循環次數;其中 CL 在移位指令中用於指明移位位數。
  • EDX:數據寄存器(Data Register),它的低 16 位是 DX,DX 可以再分為 DH 和 DL。EDX 用於存放乘除法的高位運算結果,也可用於存放 IO 埠地址。

不可再分成 8 位的通用寄存器 - 4 個。

  • ESI/EDI:這倆是一對兒的關係,分別叫做源索引寄存器(Source Index Register)目標索引寄存器(Destination Index Register),它倆只能分成兩個 16 位寄存器 SI 和 DI。這倆用於存放尋址的偏移量。它們和 EBX 一起使用可以實現更靈活的內存尋址。ESI 和 EDI 最常用的就是字符串檢索,在字符串操作指令中,DS:ESI 指向源字符串,ES:EDI 指向目標字符串。
  • EBP/BSP:分別是基指針寄存器(Base Pointer Register)堆棧指針寄存器(Stack Pointer Register),低 16 位分別是 BP 和 SP,不能再細分。BP 作為通用寄存器,也可存儲算術邏輯運算的操作數和運算結果,作為指針寄存器,BP 可以直接存取內存數據。SP 是堆棧指針寄存器,它和 SS 一起指向堆棧的棧頂,並且 SP 只用於訪問棧頂。

標誌寄存器

EFLAGS 屬於狀態寄存器,也被稱作標誌寄存器。狀態/標誌寄存器對於程序的執行過程和運行結果來說至關重要。EFLAGS 如下圖所示。

EFLAGS 中的系統標誌和 IOPL 欄位用於控制 IO 訪問、可屏蔽硬體中斷、調試、任務切換以及虛擬 - 8086 模式。其他是一些通用標誌,這裡有 CF - 進位標誌,PF - 恢復標誌 ,AF - 輔助進位標誌, ZF - 零標誌,SF - 負號標誌,DF - 方向標誌,OF - 溢出標誌。其中的第 1、3、5、15、18 - 31 位作為保留位。

通過使用 LAHF/SAHF/PUSHF/POPF/POPFD 等指令,可以將 EFLAGS 寄存器中的標誌位成組移到棧中或 EAX 寄存器,或者從內存等位置將結果保存到 EFLAGS 寄存器中。

下面我們對這些標誌位進行說明。

  1. 狀態標誌位(Status Flag)

EFLAGS 寄存器中的 0、2、4、6、7、11 位是算數指令的結果標誌,算數指令包括 ADD、SUB、MUL、DIV。這些狀態標誌位的作用如下:

  • CF(0 位):Carry Flag 無符號運算數的運算結果標識,如果算數操作產生的結果在最高有效位發生進位或者借位,就將其置為 1 。比如對於位數為 N 的無符號數來說,其對應的二進位最高有效位是 N - 1 位,其第 N 位是它的假想最高位。如果最高有效位向更高有效位進位,就表示運算進位,CF = 1,借位同樣如此。
  • PF(2 位):Parity flag 奇偶標誌位,如果運算結果轉換成二進位後,其包含偶數個 1 時,則 PF 位為 1 ,否則為 0。
  • AF(4 位):Adjust flag 輔助進位,如果算術操作在結果的第 3 位發生進位或借位則將該標誌置1,否則清零。這個標誌在BCD(binary-code decimal)算術運算中被使用。
  • ZF(6 位):Zero flag 零標誌位,判斷運算結果是否為 0 ,如果為 0,ZF = 1。
  • SF(7 位):Sign flag 負號標誌位,判斷運算結果的二進位最高有效位是否為 1 - 表示負數,如果是負數,SF = 1。
  • OF(11 位):Overflow flag 溢出標誌,有符號數的運算結果標誌。

簡單來說就是:

算數指令會產生三類結果:無符號整型、有符號整型和 BCD ,BCD 就是用二進位來表示的整型。如果把結果當成是有符號數,那麼會影響 OF 標誌,產生進位或借位;如果把結果當成無符號數,會影響 CF 標誌,產生進位或借位;如果結果是 BCD ,就會影響 AF 、SF、PF、ZF 標誌。

  1. 控制標誌位(DF-Direction flag)

DF 控制著串傳輸指令,它是一個方向標誌位。在串傳輸比如 movS 指令中,控制每次操作後的 SI DI 的遞增或遞減。df = 0 表示每次操作後 SI、DI 遞增;df = 1 表示每次操作後 SI、DI 遞減。cld 指令和 std 指令控制著遞增和遞減。cld 指令將 df 置 0 ,遞增;std 指令將 df 置 1 ,遞減。df = 0 可以理解為正向傳遞;df = 1 為逆向傳遞。

  1. 系統標誌和 IOPL 域(System Flags and IOPL Field)

EFLAGS 寄存器中的這部分標誌用於控制作業系統或是執行操作,它們不允許被應用程式所修改。這些標誌的作用如下:

  • TF(8 位):Trap flag 跟蹤標誌,將該位設置為 1 表示允許單步調試操作,清零則不允許單步調試。單步執行過程中,在每個指令執行之後都會產生一個調試異常,這樣我們就可以觀察每個指令運行後的狀態。
  • IF(9 位):Interrupt enable flag 這個標誌用於控制處理器對可屏蔽中斷請求的響應,IF = 1 表示響應可屏蔽中斷;IF = 0 表示禁止可屏蔽中斷。
  • IOPL(12-13 位):I/O privilege level field 指示當前運行的 IO 特權級(IO Level),當前運行程序或任務的 CPL 必須小於等於 IOPL 才允許訪問。只有當 CPL 特權級為 0 時,程序才能使用 POPF 和 IRET 指令修改這個欄位。
  • NT(14 位):Nested Task 嵌套任務標誌,它控制著被中斷任務和調用任務之間的連結關係。在使用 CALL 指令、中斷或者異常執行任務調用時,處理器會設置這個標誌。在通過 IRET 從任務返回時,處理器會檢查這個 NT 標誌,使用 POPF POPFD 也可以修改這個標誌。
  • RT(16 位):Resume flag 恢復標誌,控制處理器對調試斷點的響應。設置這個值時,這個標誌會臨時禁止斷點指令產生的調試異常;當恢復時,斷點指令將會產生異常。
  • VM(17 位):Virtual-8086 Mode 標誌,設置此位時,就開啟虛擬 8086 模式;當復位這個標誌時,就回到保護模式下。

段寄存器

段寄存器主要有 6 個,CS、DS、ES、FS、GS、SS,這些段寄存器也被稱為 sreg,相對的,reg 是用來表示寄存器的。

段寄存器存在的意義是為了讓內存更好的分段來用的,內存地址的訪問通過段寄存器 + 段內偏移來實現。這些 CS、DS、ES、FS、GS、SS 就是幹這事的,也可以把段寄存器 + 段內偏移來理解成小區 + 房號的組合,小區就是段基址,你家的房號就是段內偏移。這樣通過小區和房號就能唯一定位你的位置(內存物理地址),並且這個位置是 16 位的。下面說下這六個兄弟都是哪個小區:

CS:一般用來描述代碼段,代碼段保存正在執行的指令。處理器從代碼段讀取指令時,使用有 CS 寄存器中的段選擇符與 EIP 寄存器聯合構成的邏輯地址。取指執行是計算機一個非常重要的概念。它所表明的含義就是取得 CS:IP 地址(在 x86 體系下就是 EIP,32 位)處的指令來執行。

DS:數據段寄存器,一般用來保存程序的數據結構。

ES/FS/GS:這三個段寄存器可以一起理解,它們指的是額外的數據段,其實也可以和 DS 一起理解。例如,可以創建如下的四個數據段:第一個數據段保存當前程序模塊的數據結構,第二個數據段保存更高級別程序模塊導出的數據,第三個數據段保存動態創建的數據結構,最後一個數據段保存另一個程序共享出來的數據。

SS:一般用來描述棧段的選擇符,這裡的棧段用於存儲程序和當前執行處理器程序的棧幀,所有在棧上的操作都可以通過 SS:SP 來定位,並且 SS:SP 一定是指向棧頂的。

控制寄存器

控制寄存器有四個,比較簡單粗暴,分為 CR0、CR1、CR2、CR3 ,控制寄存器用於確定處理器的操作模式以及當前執行任務的特性,這幾個寄存器都是 32 位的,各自的作用如下圖所示:

這幾個寄存器是與分頁機制密切相關,因此,在進程管理及虛擬內存管理中會涉及到這幾個寄存器。對控制寄存器的讀寫是通過 mov 指令來實現。

  • CR0 :含有控制處理器操作模式和狀態的系統控制標識,它分為兩種,一種是協處理器控制位,一種是保護控制位。先說下協處理器控制位:其中擴展類型位 ET、任務切換位 TS、仿真位 EM 和數學存在位 MP 用於控制 x86 的浮點,也就是數學協處理器的操作。

協處理器是個什麼概念呢,它就是一種晶片,用於減輕系統微處理器特定處理任務的晶片。

CR0 的 ET位(標誌)用於選擇與協處理器進行通信所使用的協議,即指明系統中使用的是 80387 還是 80287 協處理器。TS、MP 和 EM 位用於確定浮點指令或 WAIT 指令是否應該產生一個設備不存在(Device Not Available,DNA)異常。這個異常可用來僅為使用浮點運算的任務保存和恢復浮點寄存器。對於沒有使 用浮點運算的任務,這樣做可以加快它們之間的切換操作。

保護控制位:

(1)PE:CR0 的 第 0 位是啟用保護(Protection Enable)標誌。當設置該位時即開啟了保護模式;當復位時即進入實地址模式。這個標誌僅開啟段級保護,而並沒有啟用分頁機制。若要啟用分頁機制,那麼 PE 和 PG 標誌都要置位。(2)PG:CR0 的第 31 位是分頁(Paging)標誌。當設置該位時即開啟了分頁機制;當復位時則禁止分頁機制,此時所有線性地址等同於物理地址。在開啟這個標誌之前必須已經或者同時開啟 PE 標誌。即若要啟用分頁機制,那麼 PE 和 PG 標誌都要置位。

(3)WP:對於 intel 80486 或以上的 CPU 來說,CR0 第16 位是防寫(Write Protect)標誌。當設置這個標誌時,會禁止超級用戶向用戶級只讀頁面執行寫操作;復位時相反。這個標誌有助於 UNIX 類系統實現寫時複製技術。

(4)對於 intel 80486 或以上的 CPU 來說,CR0 第 5 位是協處理器錯誤標誌(Numeric Error)。

  • CR1 作保留用
  • CR2 和 CR3 用於分頁機制,CR2 是頁故障線性地址寄存器,保存最後一次出現頁故障的全 32 位線性地址。在報告頁異常時,處理器會把引起異常的線性地址存放在 CR2 中。因此作業系統中的頁異常處理程序可以通過檢查 CR2 的內容來確定線性地址空間中哪一個頁面引發了異常。
  • CR3 含有存放目錄表頁面的物理地址,因此該寄存器也被稱為頁目錄基地址寄存器 PDBR(Page-Directory Base address Register)

內存管理寄存器

內存管理器有四個,分別是 GDTR、LDTR、IDTR 和 TR 。

GDTR、LDTR、IDTR 和 TR 都是段基址寄存器,這些寄存器中都包含著分段機制的重要信息。GDTR、LDTR 和 IDTR 用於尋址存放描述符表的段。TR 用於尋址一個特殊的任務狀態段 TSS,TSS 段中包含著當前執行任務的重要信息。

因為進入保護模式後,是無法直接尋址到內存段、數據段和棧段的,這些段的信息都被保存在 GDT 全局描述符表中,全局描述符表就是記錄各個段信息的表。

  • 全局描述符寄存器 GDTR

GDTR 寄存器用於存儲 GDT 全局描述符表中 16 位的表長和 32 位的線性地址。LGDT 和 SGDT 指令分別用於加載和保存 GDTR 寄存器的內容。在機器剛加電或處理器復位後,基地址被默認設置為 0 ,而長度被設置位 0xFFFF。

  • 中斷描述符寄存器 IDTR

和 GDTR 類似,只不過它是用來保存中段描述符表 IDT 的寄存器,使用 LIDT 和 SIDT 用於加載和保存 IDTR 寄存器的內容。在機器剛加電或處理器復位後,基地址被默認設置為 0 ,而長度被設置位 0xFFFF。

  • 局部描述符表 LDTR

LDTR 用於存放局部描述符表 LDT 的 32 位線性基地址、16 位段限長和描述符屬性值。指令 LLDT 和 SLDT 分別用於加載和保存 LDTR 寄存器描述符的部分。包含 LDT 表的段必須在 GDT 表中有一個段描述符項。

  • 任務寄存器 TR

TR 寄存器用於存放當前任務 TSS 段的 16 位段選擇符、32 位基地址、16 位段長度和描述屬性值。它引用 GDT 表中一個 TSS 類型的描述符。指令 LTR 和 STR 分別用於加載和保存 TR 寄存器的段選擇符部分。

其他寄存器

EIP:指令的偏移地址。其本質上並不能直接被指令訪問。這個寄存器指令由控制轉移指令、中斷及異常所控制。讀操作通過執行 call 指令並取得棧中所存放的地址來實現,而寫操作則通過修改程序棧中的返回指令指針並執行RET/IRET 指令來完成,因此儘管這個寄存器相當重要,但其實並不是作業系統在實現過程中所需關注的焦點。

TSC:(時間戳寄存器)每個時鐘周期時其值加 1,重啟時清零。

浮點寄存器:由於在 80486 微處理器內部設有浮點運算器,因此在其內部有相應的寄存器,其中包括 8 個 80 位通用數據寄存器、1 個 48 位指令指針寄存器、1 個 48 位數據指針寄存器、1 個 16 位控制字寄存器、1個 16 位狀態字寄存器和 1 個 16 位標記字寄存器。

系統指令

系統指令用於處理系統級命令,比如加載系統寄存器、中斷。大多數指令只能處於特權級為 0 的作業系統軟體執行。

這裡需要說明一下,作業系統操作是有等級權限的,不是應用程式在何時何地都可以訪問或者操作內核的,特權級有 4 個 level,分別為 0 ~ 3。

作業系統位於 0 級特權,可以直接控制硬體,掌控各種核心數據;系統程序分別位於 1 級特權或 2 級特權,主要是一些虛擬機、驅動程序等系統服務;而一般的應用程式運行在 3 級特權。

下面列出了一些作業系統指令,並且還提到了是否受到保護:

關鍵字: