x86 彙編/資料傳輸
一些最重要的和最常用的指令是那些移動資料的指令。沒有它們,暫存器或記憶體甚至無法擁有任何要操作的內容。
| mov src, dest | GAS 語法 |
| mov dest, src | Intel 語法 |
mov 代表 移動。儘管它的名字是 移動 指令,但 mov 指令實際上是 複製 src 運算元到 dest 運算元。操作之後,兩個 運算元都包含相同的內容。
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 代表 交換。交換 具有誤導性,因為實際上沒有交換任何資料。
xchg指令將src運算元與dest運算元交換。它類似於執行三個mov操作- 從
dest到一個臨時變數(另一個暫存器), - 然後從
src到dest,最後
從臨時儲存到 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 代表 比較和交換。交換 具有誤導性,因為實際上沒有交換任何資料。
cmpxchg指令有一個隱式 運算元:al/ax/eax,取決於arg1的大小。- 該指令將
arg1與al/ax/eax進行比較。 - 否則,
al/ax/eax將變為arg1。
與 xchg 不同,它沒有隱式的 lock 字首,如果指令需要是原子的,則必須新增 lock 字首。
arg2 必須是暫存器。 arg1 可以是暫存器或記憶體運算元。
ZF≔arg1= (al|ax|eax) [取決於arg1的大小]CF、PF、AF、SF、OF也會被更改。
以下示例展示瞭如何使用 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 指定的位置。如果方向標誌已清除,則在操作後 esi 和 edi 會遞增。否則,如果方向標誌已設定,則指標會遞減。在這種情況下,複製將以相反的方向進行,從最高地址開始,向較低的地址移動,直到 ecx 為零。
沒有顯式運算元,但
ecx確定迭代次數,esi指定源地址,edi指定目標地址,- DF 用於確定方向(它可以透過
cld和std指令更改)。
此指令不會修改任何標誌。
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 類似,但執行取決於各種標誌位。 有以下可用指令:
… = 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 |
|
|
Dest 必須是暫存器。 Src 可以是暫存器或記憶體運算元。
cmov 指令可以用來消除 分支,因此使用 cmov 指令可以避免分支預測錯誤。 但是,需要謹慎使用 cmov 指令:依賴鏈會變長。
通用位元組或字傳輸指令
mov- 將指定來源的位元組或字複製到指定目標。
push- 將指定字複製到堆疊頂端。
pop- 將堆疊頂端的字複製到指定位置。
pusha- 將所有暫存器複製到堆疊。
popa- 將堆疊中的字複製到所有暫存器。
xchg- 交換位元組或交換字。
xlat- 使用記憶體中的表格將
al中的位元組翻譯。
這些是 I/O 埠傳輸指令
in- 將特定埠的位元組或字複製到累加器。
out- 將累加器的位元組或字複製到特定埠。
特殊地址傳輸指令
lea- 將運算元的有效地址載入到指定暫存器。
lds- 從記憶體中載入 DS 暫存器和其他指定暫存器。
les- 從記憶體中載入 ES 暫存器和其他指定暫存器。
標誌位傳輸指令
lahf- 將標誌暫存器的低位元組載入到
ah中。 sahf- 將
ah暫存器儲存到標誌暫存器的低位元組。 pushf- 將標誌暫存器複製到堆疊頂端。
popf- 將堆疊頂端的字複製到標誌暫存器。