跳轉到內容

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)。相反,一個專用的 pushpop 指令(嘗試)從堆疊中檢索或儲存一個值。使用它們是“慢的”,因為,由於只有一個標誌暫存器,所有掛起的(潛在的)寫入或讀取必須先執行,然後才能獲得或覆蓋實際值。此外,可以讀取或覆蓋的內容取決於許可權。

pushf

此指令遞減堆疊指標,然後將堆疊指標指向的位置載入為標誌暫存器內容的遮蔽副本。RFVM 標誌始終在副本中被清除。在某些情況下,可能會出現 GPF

popf

此指令儘可能嘗試使用堆疊指標指向的記憶體位置的內容載入標誌暫存器,然後遞增堆疊指標的內容。一些標誌可能會保留其原始值,即使請求這樣做也是如此。如果缺乏更改某些或任何值的許可權,則會發生 GPF

OS 開發(如 執行緒)之外,這些指令的標準使用情況是檢查 cpuid 指令是否可用。如果可以更改 EFLAGS 暫存器中的 ID 標誌,則支援 cpuid 指令。

檢查 cpuid 的函式示例

這裡,我們假設我們擁有檢索和覆蓋標誌暫存器的適當許可權。在此示例中,使用此函式的程式語言要求 Boolean 值恰好為 0 或 1

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 指令之後是遞減還是遞增 esiedi 暫存器。

std

設定方向標誌。暫存器將遞減,向後讀取。

cld

清除方向標誌。暫存器將遞增,向前讀取。

進位標誌

[編輯 | 編輯原始碼]

CF 標誌通常在算術指令後被修改,但它也可以手動設定或清除。

stc

設定進位標誌。

clc

清除進位標誌。

cmc

對進位標誌求反(反轉)。

sahf

將 AH 暫存器的內容儲存到標誌暫存器的低位元組中。

lahf

將標誌暫存器的低位元組的內容載入到 AH 暫存器中。

I/O 指令

[編輯 | 編輯原始碼]
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]

因為這種填充程式碼是可執行的,所以它應該佔用儘可能少的執行資源,不應該降低解碼密度,並且不應該修改除遞增指令指標 (rIP) 之外的任何處理器狀態。[1]


系統指令

[編輯 | 編輯原始碼]

這些指令是在奔騰 II 中新增的。

sysenter

此指令導致處理器進入受保護系統模式(監管模式或“核心模式”)。

sysexit

此指令導致處理器退出受保護系統模式,並進入使用者模式。

雜項指令

[編輯 | 編輯原始碼]

讀取時間戳計數器

[編輯 | 編輯原始碼]

RDTSC

RDTSC 是在奔騰處理器中引入的,該指令讀取自復位以來的時鐘週期數,並將結果返回到 EDX:EAX 中。這可以用作獲得低開銷、高解析度 CPU 定時的途徑。雖然在現代 CPU 微體系結構(多核、超執行緒)和多 CPU 機器上,您無法保證核心和 CPU 之間的時間戳計數器同步。此外,由於節能或動態超頻,CPU 頻率可能會有變化。因此,該指令的可靠性可能不如首次引入時,在用於效能測量時應謹慎使用。

可以使用結果的低 32 位,但需要注意的是,在 600 MHz 處理器上,暫存器每 7.16 秒就會溢位

 

 

 

 

( 0 )

而使用完整的 64 位,則溢位間隔為 974.9 年

 

 

 

 

( 1 )

以下程式(使用 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

參考文獻

[編輯 | 編輯原始碼]
  1. 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. 
  2. "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. 
華夏公益教科書