banner
Zein

Zein

x_id

匯編/機器指令

匯編是指令集的助記符,由編譯器 + 風格 + 指令集架構 (ISA) 的組合決定語言特性,沒統一標準:
編譯器:
1)MASM:僅支持 Windows 平台,唯一完美支持按需編譯的編譯器,不支持輸出 bin 格式。
2)NASM:跨平台,支持多種輸出格式 (bin/coff/omf/elf/…)

風格:
1)Intel 風格
2)AT&T 風格

主流指令集架構:
1)x86-64/x64/amd64/Intel64
2)ARM64/AArch64
3)RISC-V
4)MIPS

x86 匯編#

以 intel 風格的匯編器為例

內存和尋址模式#

.DATA 聲明靜態數據區#

數據類型修飾原語:
DB: Byte, 1 Byte
DW: Word, 2 Bytes
DD: Double Word, 4 Bytes
匯編中只有一維數組,只有沒有二維和多維數組。一維數組其實就是內存中的一塊連續區域。另外,DUP 和字符串常量也是聲明數組的兩種方法

.DATA
var     DB 64    ; 聲明一字節大小的變量 var,並將其初始化為 64
var2    DB ?     ; 聲明一字節大小的未初始化變量 var2,其初值未定義,直到在程序中明確賦值
        DB 10    ; 聲明一字節大小的常量,值為 10,The Byte's location is var2 + 1.由於沒標籤,這個值並不能通過標籤訪問 
X       DW ?     ; 聲明一字大小(16位)的未初始化變量 X,初值未定義,直到程序中明確賦值。 
Y       DD 30000 ; 聲明一個 4-byte 值, referred to as location Y, initialized to 30000.

Z       DD 1, 2, 3      ; 聲明數組Z,每個元素4字節大小,初始化為 1, 2, 3.Z標籤表示數據存儲的起始位置 Z+下標*4 是元素地址
bytes   DB 10 DUP(?)    ; 聲明數組bytes,每個元素1字節大小;10 DUP(?)表示聲明 10 個未初始化的字節
arr     DD 100 DUP(0)   ; 聲明數組arr,每個元素4字節大小;100 DUP(0)表示聲明 100 個初始化為0的元素
str     DB 'hello',0    ; 聲明 6 bytes starting at the address str, 初始化為 hello and the null (0) byte.

內存尋址 Addressing Memory#

MOV 將在內存和寄存器間移動數據 (默認移動 32 位數據),接受兩個參數:第一個參數是目的地,第二個是源。源和目的其實都是地址;[寄存器] 表示引用寄存器的值作為地址;[var] 表示引用符號 var 表示的地址;

;合法尋址的例子:加[]表示存儲的是地址,引用地址指向內存內容
mov eax, [ebx]        ; Move the 4 bytes in memory at the address contained in EBX into EAX
mov [var], ebx        ; Move the contents of EBX into the 4 bytes at memory address var. (Note, var is a 32-bit constant).
mov eax, [esi-4]      ; Move 4 bytes at memory address ESI + (-4) into EAX
mov [esi+eax], cl     ; Move the contents of CL into the byte at address ESI+EAX
mov edx, [esi+4*ebx]  ; Move the 4 bytes of data at address ESI+4*EBX into EDX
;非法尋址的例子:
mov eax, [ebx-ecx]      ; 只能對寄存器的值相加,不能相減
mov [eax+esi+edi], ebx  ; 最多只能有 2 個寄存器參與地址計算

數據類型 (大小) 原語(Size Directives)#

修飾指針類型:就是表示引動數據的位數
BYTE PTR - 1 Byte
WORD PTR - 2 Bytes
DWORD PTR - 4 Bytes

mov BYTE PTR [ebx], 2   ; Move 2 into the single byte at the address stored in EBX.
mov WORD PTR [ebx], 2   ; Move the 16-bit integer representation of 2 into the 2 bytes starting at the address in EBX.
mov DWORD PTR [ebx], 2  ; Move the 32-bit integer representation of 2 into the 4 bytes starting at the address in EBX.

指令#

分類指令描述例子
數據移動mov將源操作數的值複製到目標操作數mov ax, bx ; 將 bx 寄存器的值複製到 ax 寄存器
push將數據壓入棧中push ax ; 將 ax 寄存器的值壓入棧
pop將棧頂數據彈出到目標操作數pop bx ; 將棧頂的值彈出並存儲到 bx 寄存器
lea加載有效地址,將地址存儲到目標寄存器lea ax, [bx + 4] ; 將 [bx + 4] 的有效地址存入 ax
算術 / 邏輯運算add執行加法操作add ax, bx ; 將 bx 加到 ax 寄存器中
sub執行減法操作sub ax, bx ; 從 ax 中減去 bx 的值
inc對操作數加 1inc ax ; 將 ax 寄存器的值增加 1
dec對操作數減 1dec bx ; 將 bx 寄存器的值減少 1
imul有符號乘法imul ax, bxax = ax * bx,有符號乘法
idiv有符號除法idiv bx ; 將 ax 除以 bx,結果存儲在 axdx
and按位與操作and ax, bx ; 將 axbx 按位與操作結果存回 ax
or按位或操作or ax, bx ; 將 axbx 按位或操作結果存回 ax
xor按位異或操作xor ax, bx ; 將 axbx 按位異或操作結果存回 ax
not按位取反操作not ax ; 將 ax 寄存器的所有位取反
neg求負操作neg ax ; 將 ax 寄存器的值取反(即加上相反數)
shl左移操作shl ax, 1 ; 將 ax 左移 1 位,移出的位被丟棄
shr右移操作shr bx, 1 ; 將 bx 右移 1 位,移出的位被丟棄
控制流jmp無條件跳轉jmp label ; 跳轉到 label 標籤處
je / jz如相等則跳轉(je:jump if equal,jz:jump if zero)je label ; 如果零標誌設置(表示相等),跳轉到 label
jne如果不相等則跳轉(jump if not equal)jne label ; 如果零標誌未設置(表示不相等),跳轉到 label
jg如果大於則跳轉(jump if greater)jg label ; 如果大於跳轉到 label
jl如果小於則跳轉(jump if less)jl label ; 如果小於跳轉到 label
cmp比較兩個操作數(通過設置標誌寄存器)cmp ax, bx ; 比較 axbx 的值(設置標誌寄存器)
call調用過程,跳轉到子程序並將返回地址壓入棧call subroutine ; 調用 subroutine 子程序
ret從過程返回,彈出返回地址並跳轉回調用點ret ; 從當前過程返回到調用點

Calling Convention#

子過程(函數)調用需遵守一套共同的協議,規定如何調用及如何從過程返回。例如,給定一組 calling convention rules,程序員無需查看子函數的定義就可以確定如何將參數傳給它。進一步地,給定一組 calling convention rules,高級語言編譯器只要遵循這些 rules,就可以使得匯編函數和高級語言函數互相調用。

C Language Calling Convention#

Calling conventions 有多種。C 語言調用約定使用最廣泛。遵循這個約定,可以使匯編代碼安全地被 C/C++ 調用,也可以從匯編代碼調用 C 函數庫。

1)強烈依賴硬件棧的支持 (hardwared-supported stack)
2)基於 push, pop, call, ret 指令
3)子過程參數通過棧傳遞: 寄存器保存在棧上,子過程用到的局部變量也放在棧上

大部分處理器上實現的大部分高級過程式語言,都使用與此相似的調用慣例。調用慣例分為兩部分。第一部分用於 調用方caller),第二部分用於被調用方callee)。需要強調的是,錯誤地使用這些規則將導致棧被破壞,程序很快出錯;因此在你自己的子過程中實現 calling convention 時需要格外仔細。

image

調用方規則 Caller Rules#

調用方需保存現場再調用子過程:
1)調用方保存的寄存器
caller-saved registers:EAX, ECX, EDX; 這幾個寄存器可能會被被 callee 修改,保存它們在調用結束後恢復棧的狀態。

2)將傳給子過程的參數 push onto stack:參數按逆序 push 入棧(最後一個參數先入)。由於棧是向下生長的,第一個參數會被存儲在最低地址(這個特性使得變長參數列表成為可能)。

3)使用 call 指令,調用子過程 (函數):call 會自動將返回地址 push onto stack,然後開始執行子過程代碼。子過程返回後(call 執行結束後),被調用方會將返回值放到 EAX 寄存器,調用方可從中讀取。為恢復機器狀態,調用方需要做:
a.從棧上刪除傳遞的參數
b.恢復由調用方保存的寄存器EAX, ECX, EDX)—— 從棧上 pop 出來;調用方可以認為,除這三個之外,其他寄存器值沒有被修改過。

;保存現場
push eax
push ecx
push edx

push [var] ; Push last parameter first
push 216   ; Push the second parameter
push eax   ; Push first parameter last

call _myFunc ; Call the function (assume C naming);call 會自動將返回地址push onto stack

;恢復現場。
add esp, 24  ;清理棧空間,恢復棧指針,刪除傳遞的 6 個參數,棧指針向高地址移動24
mov result, eax   ; 讀取返回值到 result 變量

被調用方規則 Callee Rules#

1)將原棧幀基址寄存器 EBP 的值入棧,然後 copy ESP to EBP;作為子過程棧幀的新基址
2)在棧上為局部變量分配空間;棧自頂向下生長,故隨著變量的分配,棧頂指針不斷減小
3)調用方保存的寄存器
callee-saved
—— 將他們壓入棧。包括 EBX, EDI, ESI; 這幾個寄存器是被調用方負責保存和恢復

)執行子過程的代碼,** 將返回值保存在 EAX;** 當子過程返回後:
a. 恢復應由被調用方保存的寄存器(EDI, ESI) —— 從棧上 pop 出來
b. 釋放局部變量
c. 恢復調用方 base pointer EBP —— 從棧上 pop 出來
d. 最後,執行 ret,返回給調用方 (caller)

.486           ;告訴匯編器使用什麼什麼指令集架構編譯
.MODEL FLAT   ;扁平內存模型

.CODE         ;標記匯編文件中代碼段的開始

PUBLIC _myFunc ;外部可見/可鏈接/非本文件私有的函數
_myFunc PROC
  ;1)
  push ebp
  mov  ebp, esp
  ;2)
  sub esp, 4      ; 為一個局部變量分配空間(4 字節)
  ;3)
  push edi        ; 保存寄存器值,EDI 會被修改
  push esi        ; 保存寄存器值,ESI 會被修改
  ;)子程序主體
  mov eax, [ebp+8]   ; 將第一個參數的值移入 EAX
  mov esi, [ebp+12]  ; 將第二個參數的值移入 ESI  
  mov edi, [ebp+16]  ; 將第三個參數的值移入 EDI
  
  mov [ebp-4], edi   ; 將 EDI 存入局部變量
  add [ebp-4], esi   ; 將 ESI 加入局部變量
  add eax, [ebp-4]   ; 將局部變量的內容加到 EAX 中,作為最終結果
  ;)a
  pop esi          ; 恢復寄存器 ESI 的值
  pop edi          ; 恢復寄存器 EDI 的值
  ;)b
  mov esp, ebp     ; 釋放局部變量
  ;)c
  pop ebp           ; 恢復調用方的基指針值
  ;)d
  ret               ;將棧中的返回地址彈出,跳轉到返回地址繼續執行調用方的代碼
_myFunc ENDP

END             ;標記程序的結束


riscV 匯編#

arm 匯編#

Intel/AT&T 語法風格差異#

AT&T syntaxIntel syntax
insn source, destinationinsn destination, source

內存操作#

AT&T 內存尋址使用的是 () Intel 內存尋址使用的是 []

AT&T syntaxIntel syntax
movl -12(%rbp), %eaxmov eax, DWORD PTR -12[rbp]

尋址#

AT&T 語法:disp (base, index, scale)
Intel 語法:[base + index*scale + disp]

最終地址為 base + disp + index * scale

AT&T syntaxIntel syntax
movl -12(%rbp), %eaxmov eax, DWORD PTR -12[rbp]
leaq 0(,%rax,4), %rdxlea rdx, 0[0+rax*8]

以特定匯編格式輸出#

objdump 反匯編:linux 下 objdump 默認反匯編出 AT&T 格式,添加 -M 指定輸出格式

gcc -c test.c               // 先用 gcc 編譯成二進制文件
objdump -d test.o           // 默認輸出 AT&T 格式
objdump -M intel -d test.o  // 可指定輸出 intel 格式

也可選擇輸出特定風格的匯編編譯器,再生成匯編文件查看

gcc -S test.c              //  默認輸出 AT&T 格式
gcc -S -masm=intel test.c  //  輸出 intel 的語法格式

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。