跳转到内容

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 中的数据将移动到 op1op2 将被替换为 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);
[编辑 | 编辑源代码]
华夏公益教科书