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);