← SEARCHABLE ARCHIVE
Assembly · 2026-08-15

如何閱讀 x86-64 暫存器與指令

從部分暫存器、資料大小、MOV 與 LEA 到機器碼編碼

INTERACTIVE READING MODE →

如何閱讀 x86-64 暫存器與指令

本部落格文章以教學為目的。 實際程式碼會隨 CPU 世代、作業系統、ABI、組譯器、編譯器與建置選項而變化,請先確認目標環境。

初次看到 rax、eax、ax、al 時,它們很像四個不同的暫存器。真正重要的不是背下所有助記符,而是追蹤:讀取多寬的值、值從哪裡來、寫到哪裡,以及哪些處理器狀態被改變。

分清三個層次

C 運算式        total += values[i];
組合語言表示    add rax, qword ptr [rcx + rdx*8]
機器碼位元組    48 03 04 D1

C 表達型別與意圖;組合語言把指令和運算元寫成人可讀形式;機器碼則是 CPU 解碼的位元組。原始碼一行與指令一條並沒有一對一保證。

通用暫存器與部分暫存器

x86-64 有 rax、rbx、rcx、rdx、rsi、rdi、rbp、rsp 以及 r8 到 r15,共 16 個 64 位元通用暫存器。rsp 的堆疊角色由硬體指令使用;哪個暫存器承載函式參數則由 ABI 決定。

名稱存取範圍
rax完整 64 位元
eax低 32 位元
ax低 16 位元
al低 8 位元
ah第 8~15 位元的舊式名稱

64 位元模式下,寫入 32 位元通用暫存器會把高 32 位元清為 0。

mov rax, 0FFFFFFFFFFFFFFFFh
mov eax, 7
; RAX = 0000000000000007h

8 位元或 16 位元寫入則保留其餘位元。

mov rax, 1122334455667788h
mov al, 0AAh
; RAX = 11223344556677AAh

這也是編譯器常在 setcc al 前先用 xor eax, eax 清零的原因。舊式高位元組 ah、bh、ch、dh 不能出現在需要 REX prefix 的指令中,新程式通常應避免使用。

資料寬度是指令語意的一部分

mov al,  byte ptr  [rcx]    ; 1 位元組
mov ax,  word ptr  [rcx]    ; 2 位元組
mov eax, dword ptr [rcx]    ; 4 位元組
mov rax, qword ptr [rcx]    ; 8 位元組

位址相同,但存取寬度不同。寬度錯誤可能讀寫相鄰欄位、跨越頁面邊界,或破壞結構配置契約。

mov 是複製

一般 mov 不會清除來源,多數形式也不能直接從記憶體搬到另一個記憶體位置。

mov rax, [rsi]
mov [rdi], rax

但這不是「x86 指令永遠只能碰一個記憶體位置」的普遍規則;字串與 read-modify-write 指令有自己的格式,必須查閱指令手冊。

擴展方式顯示 signedness

同一個位元組 0xFF 可以是無號 255,也可以是有號 -1。

movzx eax, byte ptr [rcx]   ; 255
movsx eax, byte ptr [rcx]   ; 符號擴展為 -1
movsxd rax, eax             ; signed 32 位元擴展到 64 位元

movzx 用 0 填滿新高位,movsx 複製原符號位。反組譯中沒有 C 型別名稱,因此要把擴展、比較與分支一起看,才能推測有號或無號語意。

lea 計算位址但不讀記憶體

lea rax, [rcx + rdx*4 + 16]

rax 得到有效位址,該位址內的值不會被讀取。

lea rax, [rcx + 8]          ; rax = rcx + 8
mov rax, [rcx + 8]          ; 讀取該處的 8 位元組值
lea eax, [rcx + rcx*2]      ; eax = rcx * 3

編譯器也會用 lea 執行不改變旗標的加法與常數倍運算。

算術指令覆寫目的地

add rax, rdx
sub rcx, 8
and eax, 0FFh
xor edx, edx       ; rdx 整體也變成 0

Intel 語法的二運算元指令通常是 destination = destination op source。這些指令也會更新條件旗標;後面的 jcc、setcc、cmovcc 必須追到實際產生旗標的那一條指令。

Intel 與 AT&T 語法

; Intel:目的地在前
mov rax, qword ptr [rbx + 8]

# AT&T:來源在前
movq 8(%rbx), %rax

表示法不同不代表 CPU 指令不同,但若把運算元順序看反,資料流也會完全相反。

指令長度不是固定的

x86-64 指令長度為 1 到 15 位元組,可包含 prefix、opcode、ModR/M、SIB、displacement 與 immediate。

48 89 D8                mov rax, rbx
B8 2A 00 00 00          mov eax, 42
48 8B 44 D1 10          mov rax, [rcx + rdx*8 + 16]

不必背位元組,但在檢查指令邊界、patch、relocation,或反組譯起點偏移一個位元組時,這個結構非常重要。

追蹤一個小函式

System V ABI 下,int transform(int x) { return x * 3 + 5; } 可能成為:

transform:
    lea eax, [rdi + rdi*2]
    add eax, 5
    ret

輸入在 edi,回傳值在 eax。但硬體的環繞結果並不等於 C 的 signed overflow 語意;後者是未定義行為。

閱讀檢查表

  1. 是 Intel 還是 AT&T 語法?
  2. 運算元寬度是多少?
  3. 讀取什麼、覆寫什麼?
  4. 32 位元寫入是否把高位清零?
  5. 有效位址如何計算?
  6. 是否產生或消耗旗標?
  7. ABI 是否賦予參數、回傳或保存角色?

總結

閱讀 x86-64 要從狀態變化開始。部分暫存器重疊,32 位元寫入清除高半部;mov 複製資料,擴展指令暴露整數解讀方式,而 lea 不讀記憶體,只計算有效位址。

官方資料