x86 彙編/其他指令
有一些專門的指令用於與 堆疊 互動。
push arg
此指令遞減堆疊指標,並將作為引數指定的 data 儲存到堆疊指標指向的位置。
pop arg
此指令將儲存在堆疊指標指向的位置的資料載入到指定的引數中,然後遞增堆疊指標。例如
mov eax, 5 mov ebx, 6 |
|
push eax |
堆疊現在是: [5] |
push ebx |
堆疊現在是: [6] [5] |
pop eax |
最上面的項(即 6)現在儲存在 eax 中。堆疊現在是: [5] |
pop ebx |
ebx 現在等於 5。堆疊現在為空。 |
pusha
此指令將所有通用暫存器按以下順序推入堆疊:AX、CX、DX、BX、SP、BP、SI、DI。推入的 SP 值是在指令執行之前的值。它對於在可能更改這些暫存器的操作之前儲存狀態很有用。
popa
此指令按與 PUSHA 相反的順序從堆疊中彈出所有通用暫存器。也就是說,DI、SI、BP、SP、BX、DX、CX、AX。用於在呼叫 PUSHA 後恢復狀態。
pushad
此指令的工作原理類似於 pusha,但將 32 位通用暫存器推入堆疊,而不是它們的 16 位對應物。
popad
此指令的工作原理類似於 popa,但從堆疊中彈出 32 位通用暫存器,而不是它們的 16 位對應物。
由於大多數指令以某種方式改變標誌,因此標誌暫存器被認為是非常易變的。因此,在微處理器架構設計中,它不能直接查詢或更改(除了少數幾個單獨的標誌,例如 DF)。相反,一個專用的 push 和 pop 指令(嘗試)從堆疊中檢索或儲存一個值。使用它們是“慢的”,因為,由於只有一個標誌暫存器,所有掛起的(潛在的)寫入或讀取必須先執行,然後才能獲得或覆蓋實際值。此外,可以讀取或覆蓋的內容取決於許可權。
pushf
此指令遞減堆疊指標,然後將堆疊指標指向的位置載入為標誌暫存器內容的遮蔽副本。RF 和 VM 標誌始終在副本中被清除。在某些情況下,可能會出現 GPF。
popf
此指令儘可能嘗試使用堆疊指標指向的記憶體位置的內容載入標誌暫存器,然後遞增堆疊指標的內容。一些標誌可能會保留其原始值,即使請求這樣做也是如此。如果缺乏更改某些或任何值的許可權,則會發生 GPF。
在 OS 開發(如 執行緒)之外,這些指令的標準使用情況是檢查 cpuid 指令是否可用。如果可以更改 EFLAGS 暫存器中的 ID 標誌,則支援 cpuid 指令。
檢查 cpuid 的函式示例 |
|---|
|
這裡,我們假設我們擁有檢索和覆蓋標誌暫存器的適當許可權。在此示例中,使用此函式的程式語言要求 pushfq ; put RFLAGS on top of stack
mov rax, [rsp] ; preserve copy for comparison
xor [rsp], $200000 ; flip bit in copy
popfq ; _attempt_ to overwrite RFLAGS
pushfq ; obtain possibly altered RFLAGS
pop rcx ; rcx ≔ rsp↑; inc(rsp, 8)
xor rax, rcx ; cancel out any _unchanged_ bits
shr eax, 20 ; move ID flag into bit position 0
|
雖然 標誌 暫存器用於報告已執行指令的結果(溢位、進位等),但它還包含影響處理器操作的標誌。這些標誌使用特殊指令設定和清除。
IF 標誌告訴處理器是否應該接受硬體中斷。在正常執行期間應保持設定。事實上,在保護模式下,使用者級程式不能執行這些指令中的任何一個。
sti
設定中斷標誌。如果設定,處理器可以接受來自外圍硬體的中斷。
cli
清除中斷標誌。硬體中斷不能中斷執行。程式仍然可以生成中斷,稱為軟體中斷,並改變執行流程。不可遮蔽中斷 (NMI) 不能使用此指令阻止。
DF 標誌告訴處理器在使用 字串 指令時以何種方式讀取資料。也就是說,在 movs 指令之後是遞減還是遞增 esi 和 edi 暫存器。
std
設定方向標誌。暫存器將遞減,向後讀取。
cld
清除方向標誌。暫存器將遞增,向前讀取。
CF 標誌通常在算術指令後被修改,但它也可以手動設定或清除。
stc
設定進位標誌。
clc
清除進位標誌。
cmc
對進位標誌求反(反轉)。
sahf
將 AH 暫存器的內容儲存到標誌暫存器的低位元組中。
lahf
將標誌暫存器的低位元組的內容載入到 AH 暫存器中。
| in src, dest | GAS 語法 |
| in dest, src | Intel 語法 |
IN 指令幾乎總是與 AX 和 DX(或 EAX 和 EDX)運算元相關聯。DX(src)通常包含要讀取的埠地址,而 AX(dest)接收來自埠的資料。在受保護模式作業系統中,IN 指令通常被鎖定,普通使用者無法在他們的程式中使用它。
| out src, dest | GAS 語法 |
| out dest, src | Intel 語法 |
OUT 指令與 IN 指令非常相似。OUT 將資料從給定的暫存器(src)輸出到給定的輸出埠(dest)。在受保護模式下,OUT 指令通常被鎖定,因此普通使用者無法使用它。
x86 指令集有一個 NOP(無操作)指令助記符
nop
它有一個單位元組操作碼,0x90。此指令除了遞增指令指標(EIP)之外沒有副作用。儘管它的名字是“什麼也不做”的指令,但它對於執行速度最佳化很有用。它經常被最佳化編譯器/彙編器使用,並且可以在反彙編程式碼中看到散佈在周圍,但幾乎從未在手動編寫的彙編程式碼中使用。
為了說明,一些nop 指令的應用是
- 將以下指令與記憶體塊的開頭對齊;
- 對齊一系列跳轉目標;
- 在對可執行檔案進行二進位制修補時填充空間,例如移除分支,而不是保留死程式碼(從未執行的程式碼)。
| 多位元組無操作指令 |
|---|
|
來自 AMD[1] 和 Intel[2] 的 x86 擴充套件(包括 x86-64)包括多位元組無操作指令。實際上,任何沒有副作用的有效指令都可以用作無操作指令。下面列出了一些上述手冊中推薦的版本。 Size (bytes) Opcode (hexadecimal) Encoding --------------------------------------------------------------------------------- 1 90 NOP 2 66 90 66 NOP 3 0F 1F 00 NOP DWORD ptr [EAX] 4 0F 1F 40 00 NOP DWORD ptr [EAX + 00H] 5 0F 1F 44 00 00 NOP DWORD ptr [EAX + EAX*1 + 00H] 6 66 0F 1F 44 00 00 NOP DWORD ptr [AX + AX*1 + 00H] 7 0F 1F 80 00 00 00 00 NOP DWORD ptr [EAX + 00000000H] 8 0F 1F 84 00 00 00 00 00 NOP DWORD ptr [AX + AX*1 + 00000000H]
|
這些指令是在奔騰 II 中新增的。
sysenter
此指令導致處理器進入受保護系統模式(監管模式或“核心模式”)。
sysexit
此指令導致處理器退出受保護系統模式,並進入使用者模式。
RDTSC
RDTSC 是在奔騰處理器中引入的,該指令讀取自復位以來的時鐘週期數,並將結果返回到 EDX:EAX 中。這可以用作獲得低開銷、高解析度 CPU 定時的途徑。雖然在現代 CPU 微體系結構(多核、超執行緒)和多 CPU 機器上,您無法保證核心和 CPU 之間的時間戳計數器同步。此外,由於節能或動態超頻,CPU 頻率可能會有變化。因此,該指令的可靠性可能不如首次引入時,在用於效能測量時應謹慎使用。
可以使用結果的低 32 位,但需要注意的是,在 600 MHz 處理器上,暫存器每 7.16 秒就會溢位
-
( )
而使用完整的 64 位,則溢位間隔為 974.9 年
-
( )
以下程式(使用 NASM 語法)是使用 RDTSC 測量一小塊程式碼執行所需週期的示例
global main
extern printf
section .data
align 4
a: dd 10.0
b: dd 5.0
c: dd 2.0
fmtStr: db "edx:eax = %llu edx = %d eax = %d", 0x0A, 0
section .bss
align 4
cycleLow: resd 1
cycleHigh: resd 1
result: resd 1
section .text
main: ; Using main since we are using gcc to link
;
; op dst, src
;
xor eax, eax
cpuid
rdtsc
mov [cycleLow], eax
mov [cycleHigh], edx
;
; Do some work before measurements
;
fld dword [a]
fld dword [c]
fmulp st1
fmulp st1
fld dword [b]
fld dword [b]
fmulp st1
faddp st1
fsqrt
fstp dword [result]
;
; Done work
;
cpuid
rdtsc
;
; break points so we can examine the values
; before we alter the data in edx:eax and
; before we print out the results.
;
break1:
sub eax, [cycleLow]
sbb edx, [cycleHigh]
break2:
push eax
push edx
push edx
push eax
push dword fmtStr
call printf
add esp, 20 ; Pop stack 5 times 4 bytes
;
; Call _exit(2) syscall
; noreturn void _exit(int status)
;
mov ebx, 0 ; Arg one: the 8-bit status
mov eax, 1 ; Syscall number:
int 0x80
為了彙編、連結和執行該程式,我們需要執行以下操作
$ nasm -felf -g rdtsc.asm -l rdtsc.lst
$ gcc -m32 -o rdtsc rdtsc.o
$ ./rdtsc
- ↑ a b "5.8 "Code Padding with Operand-Size Override and Multibyte NOP"". AMD Software Optimization Guide for AMD Family 15h Processors, document #47414. p. 94. http://support.amd.com/TechDocs/47414_15h_sw_opt_guide.pdf.
- ↑ "NOP". Intel 64 and IA-32 Architectures Software Developer's Manual. 2B: Instruction Set Reference. http://www.intel.com/content/dam/www/public/us/en/documents/manuals/64-ia-32-architectures-software-developer-vol-2b-manual.pdf.