x86 彙編/FASM 語法
FASM,也稱為Flat Assembler,是針對 x86 架構的最佳化彙編器。FASM 用匯編語言編寫,因此它可以自彙編/自引導。它執行在各種作業系統上,包括 DOS、Linux、Unix 和 Windows。它支援 x86 和 x86-64 指令集,包括 SIMD 擴充套件 MMX、SSE - SSE4 和 AVX。
FASM 支援所有流行的用於定義十六進位制數字的語法
0xbadf00d ; C-Like Syntax
$badf00d ; Pascal-Like Syntax
0badf00dh ; h Syntax, requires leading zero to be valid at assembly time
FASM 支援幾種獨特的標籤功能。
FASM 支援不使用識別符號或標籤名稱的標籤。
- @@: 代表一個匿名標籤。可以定義任意數量的匿名標籤。
- @b 指向在原始碼中向後查詢時遇到的最近的 @@。@r 和 @b 等效。
- @f 指向在原始碼中向前查詢時遇到的最近的 @@。
@@:
inc eax
push eax
jmp @b ; This will result in a stack fault sooner or later
jmp @f ; This instruction will never be hit
@@: ; if jmp @f was ever hit, the instruction pointer would be set to this anonymous label
invoke ExitProcess, 0 ; Winasm only
區域性標籤,以 . (句點)開頭。您可以在其全域性標籤父級的上下文中引用區域性標籤。
entry globallabel
globallabel:
.locallabelone:
jmp globallabel2.locallabelone
.locallabeltwo:
globallabel2:
.locallabelone:
.locallabeltwo:
jmp globallabel.locallabelone ; infinite loop
FASM 支援幾個獨特的運算子來簡化彙編程式碼。
$ 描述了定址空間中的當前位置。它用於確定程式碼塊或資料塊的大小。 $ 在 MASM 中的等效項是 SIZEOF 運算子。
mystring db "This is my string", 0
mystring.length = $ - mystring
# 是符號連線運算子,用於將多個符號組合成一個。它只能在宏(如 rept 或自定義/使用者定義的宏)的主體內部使用,因為它會將宏引數的名稱替換為其值。
macro contrived value {
some#value db 22
}
; ...
contrived 2
; assembles to...
some2 db 22
` 用於獲取傳遞給宏的符號的名稱,將其轉換為字串。
macro print_contrived value {
formatter db "%s\n"
invoke printf, formatter, `value
}
; ...
print_contrived SOMEVALUE
; assembles to...
formatter db "%s\n"
invoke printf, formatter, "SOMEVALUE"
FASM 有一些有用的內建宏,可以簡化彙編程式碼的編寫。
rept 指令用於將重複的彙編指令壓縮成一個塊。該指令以 rept 開頭,然後是一個數字或變數,指定緊隨其後的花括號內的彙編指令應重複的次數。計數器變數可以別名為符號,或用作 rept 塊內指令的一部分。
rept 2 {
db "Hello World!", 0Ah, 0
}
; assembles to...
db "Hello World!", 0Ah, 0
db "Hello World!", 0Ah, 0
; and...
rept 2 helloNumber {
hello#helloNumber db "Hello World!", 0Ah, 0 ; use the symbol concatenation operator '#' to create unique labels hello1 and hello2
}
; assembles to...
hello1 db "Hello World!", 0Ah, 0
hello2 db "Hello World!", 0Ah, 0
struc 指令允許將資料彙編成類似於具有成員的 C 結構體的格式。 struc 的定義使用區域性標籤來定義成員值。
struc 3dpoint x, y, z
{
.x db x,
.y db y,
.z db z
}
some 3dpoint 1, 2, 3
; assembles to...
some:
.x db 1
.y db 2
.z db 3
; access a member through some.x, some.y, or some.z for x, y, and z respectively
FASM 支援定義自定義宏,作為將多個指令或條件彙編組合成一個較大指令的方法。它們需要一個名稱,並且可以有一個可選的引數列表,用逗號隔開。
macro name arg1, arg2, ... {
; <macro body>
}
宏可以透過方括號語法支援可變數量的引數。
macro name arg1, arg2, [varargs] {
; <macro body>
}
FASM 宏語法可以使用每個運算元後的 * 運算子來要求宏定義中的運算元。
; all operands required, will not assemble without
macro mov op1*, op2*, op3*
{
mov op1, op2
mov op2, op3
}
FASM 宏語法允許過載指令的語法,或者建立新的指令。下面,mov 指令被過載以支援第三個運算元。如果未提供第三個運算元,則會組裝常規的移動指令。否則,op2 中的資料將移動到 op1,op2 將被替換為 op3。
; not all operands required, though if op1 or op2 are not supplied
; assembly should fail
; could also be defined as 'macro mov op1*, op2*, op3' to force requirement of the first two arguments
macro mov op1, op2, op3
{
if op3 eq
mov op1, op2
else
mov op1, op2
mov op2, op3
end if
}
這是一個完整的 Win32 彙編程式示例,它將“Hello World!”列印到控制檯,然後等待使用者按任意鍵退出應用程式。
format PE console ; Win32 portable executable console format
entry _start ; _start is the program's entry point
include 'win32a.inc'
section '.data' data readable writable ; data definitions
hello db "Hello World!", 0
stringformat db "%s", 0ah, 0
section '.code' code readable executable ; code
_start:
invoke printf, stringformat, hello ; call printf, defined in msvcrt.dll
invoke getchar ; wait for any key
invoke ExitProcess, 0 ; exit the process
section '.imports' import data readable ; data imports
library kernel, 'kernel32.dll',\ ; link to kernel32.dll, msvcrt.dll
msvcrt, 'msvcrt.dll'
import kernel, \ ; import ExitProcess from kernel32.dll
ExitProcess, 'ExitProcess'
import msvcrt, \ ; import printf and getchar from msvcrt.dll
printf, 'printf',\
getchar, '_fgetchar'
這是一個 x86_64 GNU+Linux 的示例。
format ELF64 executable 3 ;; ELF64 Format for GNU+Linux
segment readable executable ;; Executable code section
;; Some definitions for readabilty purposes
define SYS_exit 60
define SYS_write 1
define stdout 1
define exit_success 0
_start: ;; Entry point for our program
mov eax, SYS_write ;; SYS_write( // Call the write(2) syscall
mov edi, stdout ;; STDOUT_FILENO, // Write to stdout
mov esi, hello_world ;; hello_world, // Buffer to write to STDOUT_FILENO: hello_world
mov edx, hello_world_length ;; hello_world_length, // Buffer length
syscall ;; );
mov eax, SYS_exit ;; SYS_exit( // Call the exit exit(2) syscall
mov edi, exit_success ;; EXIT_SUCCESS, // Exit with success exit code, required if we don't want a segfault
syscall ;; );
segment readable ;; Read-only constant data section
hello_world: db "Hello world", 10 ;; const char *hello_world = "Hello world\n";
hello_world_length = $ - hello_world ;; const size_t hello_world_length = strlen(hello_world);