跳轉到內容

x86 彙編/資料傳輸

來自 Wikibooks,開放世界中的開放書籍

一些最重要的和最常用的指令是那些移動資料的指令。沒有它們,暫存器或記憶體甚至無法擁有任何要操作的內容。

資料傳輸指令

[編輯 | 編輯原始碼]
mov src, dest GAS 語法
mov dest, src Intel 語法

mov 代表 移動。儘管它的名字是 移動 指令,但 mov 指令實際上是 複製 src 運算元到 dest 運算元。操作之後,兩個 運算元都包含相同的內容。

運算元

[編輯 | 編輯原始碼]
mov 指令的合法運算元
src 運算元 dest 運算元
立即值 暫存器 記憶體

(到 更大 的暫存器)

(相同大小)

(暫存器決定檢索的記憶體大小)
暫存器

(最多 32 位值)
記憶體

修改後的標誌

[編輯 | 編輯原始碼]
  • 此指令不會修改任何標誌
.data
value:
	.long 2

.text
	.globl _start

_start:
	movl $6, %eax                         # eax ≔ 6
	                                      #  └───────┐
	movw %eax, value                      # value ≔ eax
	                                      #   └───────────┐
	movl $0, %ebx                         # ebx ≔ 0  │    │
	                                      #       ┌──┘    │
	movb %al, %bl                         # bl ≔ al       │
	                                      # %ebx is now 6 │
	                                      #         ┌─────┘
	movl value, %ebx                      # ebx ≔ value
	
	movl $value, %esi                     # esi ≔ @value
	# %esi is now the address of value
	
	xorl %ebx, %ebx                       # ebx ≔ ebx ⊻ ebx
	                                      # %ebx is now 0
	
	movw value(, %ebx, 1), %bx            # bx ≔ value[ebx*1]
	                                      # %ebx is now 6
	
# Linux sys_exit
	movl $1, %eax                         # eax ≔ 1
	xorl %ebx, %ebx                       # ebx ≔ 0
	int $0x80

資料交換

[編輯 | 編輯原始碼]
xchg src, dest GAS 語法
xchg dest, src Intel 語法

xchg 代表 交換交換 具有誤導性,因為實際上沒有交換任何資料。

  1. xchg 指令將 src 運算元與 dest 運算元交換。它類似於執行三個 mov 操作
  2. dest 到一個臨時變數(另一個暫存器),
  3. 然後從 srcdest,最後

從臨時儲存到 src

除了不需要為臨時儲存預留任何暫存器。

運算元

這種三個連續 mov 指令的交換模式可以被某些架構中存在的 DFU 檢測到,這將觸發特殊處理。但是,xchg 的操作碼更短。

[編輯 | 編輯原始碼]

任何暫存器或記憶體運算元的組合,除了最多一個運算元可以是記憶體運算元。你不能交換兩個記憶體塊。

修改後的標誌

[編輯 | 編輯原始碼]

示例

 .data
 
 value:
        .long   2
 
 .text
        .global _start
 
 _start:
        movl    $54, %ebx
        xorl    %eax, %eax
 
        xchgl   value, %ebx
        # %ebx is now 2
        # value is now 54
 
        xchgw   %ax, value
        # Value is now 0
        # %eax is now 54
 
        xchgb   %al, %bl
        # %ebx is now 54
        # %eax is now 2
 
        xchgw   value(%eax), %ax
        # value is now 0x00020000 = 131072
        # %eax is now 0
 
 # Linux sys_exit 
        mov     $1, %eax
        xorl    %ebx, %ebx
        int     $0x80

無。

應用

[編輯 | 編輯原始碼]

如果其中一個運算元是記憶體地址,那麼該操作具有隱式 lock 字首,也就是說,交換操作是原子的。這可能會導致較大的效能損失。

  • 然而,在某些平臺上,交換兩個(非部分)暫存器將觸發暫存器重新命名。暫存器重新命名器是一個單元,它僅僅重新命名暫存器,因此實際上不需要移動任何資料。這非常快(被稱為“零延遲”)。重新命名暫存器可能有用,因為
  • 某些指令要求某些運算元位於特定暫存器中,但以後還需要資料,

或者如果其中一個運算元是累加器暫存器,則編碼一些操作碼會更短。

xchg 指令用於更改 16 位值的位元組順序(LE ↔ BE),因為 bswap 指令僅適用於 32 位和 64 位值。你可以透過定址部分暫存器來做到這一點,例如 xchg ah, al

還值得注意的是,常見的 nop(無操作)指令,0x90,是 xchgl %eax, %eax 的操作碼。

基於比較的資料交換
[編輯 | 編輯原始碼] GAS 語法
cmpxchg arg2, arg1 Intel 語法

cmpxchg arg1, arg2

cmpxchg 代表 比較和交換交換 具有誤導性,因為實際上沒有交換任何資料。

  1. cmpxchg 指令有一個隱式 運算元:al/ax/eax,取決於 arg1 的大小。
  2. 該指令將 arg1al/ax/eax 進行比較。
  3. 否則,al/ax/eax 將變為 arg1

xchg 不同,它沒有隱式的 lock 字首,如果指令需要是原子的,則必須新增 lock 字首。

運算元

[編輯 | 編輯原始碼]

arg2 必須是暫存器。 arg1 可以是暫存器或記憶體運算元。

修改後的標誌

[編輯 | 編輯原始碼]
  • ZF ≔ arg1 = (al|ax|eax) [取決於 arg1 的大小]
  • CFPFAFSFOF 也會被更改。

以下示例展示瞭如何使用 cmpxchg 指令來建立一個自旋鎖,用於保護 result 變數。最後一個獲取自旋鎖的執行緒將能夠設定 result 的最終值。

自旋鎖示例
global main 

extern printf
extern pthread_create
extern pthread_exit
extern pthread_join

section .data
	align 4
	sLock:		dd 0	; The lock, values are:
				; 0	unlocked
				; 1	locked	
	tID1:		dd 0
	tID2:		dd 0
	fmtStr1:	db "In thread %d with ID: %02x", 0x0A, 0
	fmtStr2:	db "Result %d", 0x0A, 0

section .bss
	align 4
	result:		resd 1

section .text
	main:			; Using main since we are using gcc to link

				;
				; Call pthread_create(pthread_t *thread, const pthread_attr_t *attr,
				;			void *(*start_routine) (void *), void *arg);
				;
	push	dword 0		; Arg Four: argument pointer
	push	thread1		; Arg Three: Address of routine
	push	dword 0		; Arg Two: Attributes
	push	tID1		; Arg One: pointer to the thread ID
	call	pthread_create

	push	dword 0		; Arg Four: argument pointer
	push	thread2		; Arg Three: Address of routine
	push	dword 0		; Arg Two: Attributes
	push	tID2		; Arg One: pointer to the thread ID
	call	pthread_create

				;
				; Call int pthread_join(pthread_t thread, void **retval) ;
				;
	push	dword 0		; Arg Two: retval
	push	dword [tID1]	; Arg One: Thread ID to wait on
	call	pthread_join
	push	dword 0		; Arg Two: retval
	push	dword [tID2]	; Arg One: Thread ID to wait on
	call	pthread_join

	push	dword [result]
	push	dword fmtStr2
	call	printf
	add	esp, 8		; Pop stack 2 times 4 bytes

	call exit

thread1:
	pause
	push	dword [tID1]
	push	dword 1	
	push	dword fmtStr1
	call	printf
	add	esp, 12		; Pop stack 3 times 4 bytes

	call	spinLock

	mov	[result], dword 1
	call	spinUnlock

	push	dword 0		; Arg one: retval
	call	pthread_exit

thread2:
	pause
	push	dword [tID2]
	push	dword 2	
	push	dword fmtStr1
	call	printf
	add	esp, 12		; Pop stack 3 times 4 bytes

	call	spinLock

	mov	[result], dword 2
	call	spinUnlock

	push	dword 0		; Arg one: retval
	call	pthread_exit

spinLock:
	push	ebp
	mov	ebp, esp
	mov	edx, 1		; Value to set sLock to
spin:	mov	eax, [sLock]	; Check sLock
	test	eax, eax	; If it was zero, maybe we have the lock
	jnz	spin		; If not try again
	;
	; Attempt atomic compare and exchange:
	; if (sLock == eax):
	;	sLock		<- edx
	;	zero flag	<- 1
	; else:
	;	eax		<- edx
	;	zero flag	<- 0
	;
	; If sLock is still zero then it will have the same value as eax and
	; sLock will be set to edx which is one and therefore we aquire the
	; lock. If the lock was acquired between the first test and the
	; cmpxchg then eax will not be zero and we will spin again.
	;
	lock	cmpxchg [sLock], edx
	test	eax, eax
	jnz	spin
	pop	ebp
	ret

spinUnlock:
	push	ebp
	mov	ebp, esp
	mov	eax, 0
	xchg	eax, [sLock]
	pop	ebp
	ret

exit:
				;
				; Call exit(3) syscall
				;	void exit(int status)
				;
	mov	ebx, 0		; Arg one: the status
	mov	eax, 1		; Syscall number:
	int 	0x80

為了編譯、連結和執行程式,我們需要執行以下操作:

$ nasm -felf32 -g cmpxchgSpinLock.asm
$ gcc -o cmpxchgSpinLock cmpxchgSpinLock.o -lpthread
$ ./cmpxchgSpinLock


帶有零擴充套件的移動

[編輯 | 編輯原始碼]
movz src, dest GAS 語法
movzx dest, src Intel 語法

movz 代表 帶有零擴充套件的移動。與常規的 mov 一樣,movz 指令將資料從 src 運算元複製到 dest 運算元,但 dest 中未由 src 提供的剩餘位將用零填充。此指令適用於將較小的 無符號 值複製到更大的暫存器。

運算元

[編輯 | 編輯原始碼]

Dest 必須是暫存器,而 src 可以是另一個暫存器或記憶體運算元。為了使此操作有意義,dest 必須 大於 src

修改後的標誌

[編輯 | 編輯原始碼]

沒有。

 .data
 
 byteval:
        .byte   204
 
 .text
        .global _start
 
 _start:
        movzbw  byteval, %ax
        # %eax is now 204
 
        movzwl  %ax, %ebx
        # %ebx is now 204
 
        movzbl  byteval, %esi
        # %esi is now 204
 
 # Linux sys_exit 
        mov     $1, %eax
        xorl    %ebx, %ebx
        int     $0x80

帶有符號擴充套件的移動

[編輯 | 編輯原始碼]
movs src, dest GAS 語法
movsx dest, src Intel 語法

movsx 代表 帶有符號擴充套件的移動movsx 指令將 src 運算元複製到 dest 運算元,並將 src 未提供的剩餘位用 src 的符號位(MSB)填充。

此指令適用於將 帶符號 的較小值複製到更大的暫存器。

運算元

[編輯 | 編輯原始碼]

movsx 接受與 movzx 相同的運算元。

任何暫存器或記憶體運算元的組合,除了最多一個運算元可以是記憶體運算元。你不能交換兩個記憶體塊。

[編輯 | 編輯原始碼]

movsx 也不會修改任何標誌。

 .data
 
 byteval:
        .byte   -24 # = 0xe8
 
 .text
        .global _start
 
 _start:
        movsbw  byteval, %ax
        # %ax is now -24 = 0xffe8
 
        movswl  %ax, %ebx
        # %ebx is now -24 = 0xffffffe8
 
        movsbl  byteval, %esi
        # %esi is now -24 = 0xffffffe8
 
 # Linux sys_exit 
        mov     $1, %eax
        xorl    %ebx, %ebx
        int     $0x80

移動字串

[編輯 | 編輯原始碼]

movsb

移動位元組。

movsb 指令將 esi 指定的記憶體位置中的一個位元組複製到 edi 指定的位置。如果方向標誌已清除,則在操作後 esiedi 會遞增。否則,如果方向標誌已設定,則指標會遞減。在這種情況下,複製將以相反的方向進行,從最高地址開始,向較低的地址移動,直到 ecx 為零。

運算元

[編輯 | 編輯原始碼]

沒有顯式運算元,但

  • ecx 確定迭代次數,
  • esi 指定源地址,
  • edi 指定目標地址,
  • DF 用於確定方向(它可以透過 cldstd 指令更改)。

修改後的標誌

[編輯 | 編輯原始碼]

此指令不會修改任何標誌。

section .text
  ; copy mystr into mystr2
  mov esi, mystr    ; loads address of mystr into esi
  mov edi, mystr2   ; loads address of mystr2 into edi
  cld               ; clear direction flag (forward)
  mov ecx,6
  rep movsb         ; copy six times
 
section .bss
  mystr2: resb 6
 
section .data
  mystr db "Hello", 0x0

movsw

移動字

movsw 指令將 esi 指定的位置中的一個字(兩個位元組)複製到 edi 指定的位置。它基本上與 movsb 做同樣的事情,只是用字而不是位元組。

運算元

[編輯 | 編輯原始碼]

修改後的標誌

  • 此指令不會修改任何標誌

示例

section .code
  ; copy mystr into mystr2
  mov esi, mystr
  mov edi, mystr2
  cld
  mov ecx,4
  rep movsw
  ; mystr2 is now AaBbCca\0
 
section .bss
  mystr2: resb 8
 
section .data
  mystr db "AaBbCca", 0x0

載入有效地址

[編輯 | 編輯原始碼]
lea src, dest GAS 語法
lea dest, src Intel 語法

lea 代表 載入有效地址lea 指令計算 src 運算元的地址,並將其載入到 dest 運算元中。

運算元

[編輯 | 編輯原始碼]

src

  • 立即數
  • 暫存器
  • 記憶體

dest

  • 暫存器

修改後的標誌

[編輯 | 編輯原始碼]
  • 此指令不會修改任何標誌

計算有效地址的方式與 mov 指令相同,但它不是將該地址的 *內容* 載入到 dest 運算元中,而是載入地址本身。

lea 不僅可以用於計算地址,還可以用於通用的無符號整數運算(需要注意的是,標誌位不會被修改,這可能是一種優勢)。 這非常強大,因為 src 運算元最多可以包含 4 個引數:基址暫存器、索引暫存器、標量乘數和位移量,例如 [eax + edx*4 -4](Intel 語法)或 -4(%eax, %edx, 4)(GAS 語法)。 標量乘數被限制為常數值 1、2、4 或 8,分別對應位元組、字、雙字或四字偏移量。 這本身允許將通用暫存器乘以常數值 2、3、4、5、8 和 9,如下所示(使用 NASM 語法)

lea ebx, [ebx*2]      ; Multiply ebx by 2
lea ebx, [ebx*8+ebx]  ; Multiply ebx by 9, which totals ebx*18

條件移動

[編輯 | 編輯原始碼]
cmovcc src, dest GAS 語法
cmovcc dest, src Intel 語法

cmov 代表 *條件移動*。 它與 mov 類似,但執行取決於各種標誌位。 有以下可用指令:

可用 cmovcc 組合
… = 1 … = 0
ZF cmovz, cmove cmovnz, cmovne
OF cmovo cmovno
SF cmovs cmovns
CF cmovc, cmovb, cmovnae cmovnc, cmovnb, cmovae
CF ∨ ZF cmovbe N/A
PF cmovp, cmovpe cmovnp, cmovpo
SF = OF cmovge, cmovnl cmovnge, cmovl
ZF ∨ SF ≠ OF cmovng, cmovle N/A
CF ∨ ZF cmova N/A
¬CF SF = OF
¬ZF cmovnbe, cmova cmovg, cmovnle
Note

cmov 指令需要在平臺上可用。 可以使用 cpuid 指令檢查這一點。

運算元

[編輯 | 編輯原始碼]

Dest 必須是暫存器。 Src 可以是暫存器或記憶體運算元。

cmov 指令可以用來消除 分支,因此使用 cmov 指令可以避免分支預測錯誤。 但是,需要謹慎使用 cmov 指令:依賴鏈會變長。

8086 微處理器的資料傳輸指令

[編輯 | 編輯原始碼]

通用位元組或字傳輸指令

mov
將指定來源的位元組或字複製到指定目標。
push
將指定字複製到堆疊頂端。
pop
將堆疊頂端的字複製到指定位置。
pusha
將所有暫存器複製到堆疊。
popa
將堆疊中的字複製到所有暫存器。
xchg
交換位元組或交換字。
xlat
使用記憶體中的表格將 al 中的位元組翻譯。

輸入/輸出

[編輯 | 編輯原始碼]

這些是 I/O 埠傳輸指令

in
將特定埠的位元組或字複製到累加器。
out
將累加器的位元組或字複製到特定埠。

地址傳輸指令

[編輯 | 編輯原始碼]

特殊地址傳輸指令

lea
將運算元的有效地址載入到指定暫存器。
lds
從記憶體中載入 DS 暫存器和其他指定暫存器。
les
從記憶體中載入 ES 暫存器和其他指定暫存器。

標誌位

[編輯 | 編輯原始碼]

標誌位傳輸指令

lahf
將標誌暫存器的低位元組載入到 ah 中。
sahf
ah 暫存器儲存到標誌暫存器的低位元組。
pushf
將標誌暫存器複製到堆疊頂端。
popf
將堆疊頂端的字複製到標誌暫存器。
華夏公益教科書