0%

译文: Say hello to x64 Assembly [part 5]

Say hello to x86_64 Assembly 系列的第五篇,这篇会看到宏(macros)。但这与 x86_64 并没有关系,仅仅是 nasm 这种汇编工具与其预处理的。若有兴趣的话可以接着阅读。

宏(Macros)

NASM 支持以下两种形式的宏:

  • 单行(single-line)
  • 多行(multiline)

所有单行定义的宏必须以%define开始,像下面的形式一样:

1
%define macro_name(parameter) value

Nasm 宏的行为与 C 非常相似。比如,我们可以这样创建单行宏:

1
2
%define argc rsp + 8
%define cliArg1 rsp + 24

之后可以在代码中这么使用他们:

1
2
3
4
5
6
;;
;; argc 将会被 扩展为 rsp + 8 (argc will be expanded rsp + 8)
;;
mov rax, [argc]
cmp rax, 3
jne .mustBe3args

多行宏以%macro起始,以%endmacro结束。通常的形式是这样的:

1
2
3
4
5
%macro number_of_parameters
instruction
instruction
instruction
%endmacro

例如(译者注:1 代表参数的个数为 1):

1
2
3
4
%macro bootstrap 1
push ebp
mov ebp, esp
%endmacro

然后这样使用它:

1
2
_start:
bootstrap

再举几个 PRINT 宏的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
%macro PRINT 1
pusha
pushf
jmp %%astr
%%str db %1, 0
%%strln equ $-%%str
%%astr: _syscall_write %%str, %%strln
popf
popa
%endmacro

%macro _sytcall_write 2
mov rax, 1
mov rdi, 1
mov rsi, %%str
mov rdx, %%strln
syscall
%endmacro

让我们浏览一遍、懂得他们是怎样工作的:第一行定义了 PRINT 宏,参数为 1。之后利用 pushapushf 指令把所有数据与标志(flag, pushf 指令)入栈。然后跳向 %%astr 标签继续执行。注意到宏定义的所有的标签都以 %% 开始。现在转向 _syscall_write 宏,它的参数为 2。**_syscall_write** 是怎么执行的呢?是否还记得我们使用的 write 系统调用来打印字符至标准输出。它长成这样:

1
2
3
4
5
6
7
8
9
10
;; write syscall number
mov rax, 1
;; file descriptor, standard output
mov rdi, 1
;; message address
mov rsi, msg
;; length of message
mov rdx, 14
;; call write syscall
syscall

在我们的 _syscall_write 宏中,我们先使用两条指令把 1 放入 rax(write system call number)与 rdi(stdout file descriptor)。之后把 %%str 放入 rsi 寄存器(指向字符串),%%str 是局部标签,它获取 PRINT 宏的参数(要注意宏的参数可以使用 $parameter_number),%%str 以 0 结束(每个字符串必须以 0 结束)。%%strln 为计算出的字符串的长度。最后进行了系统调用。

现在便可以这样使用它:

1
label: PRINT "Hello World!"

有用的标准宏(Useful standard macros)

NASM 支持下列形式的标准宏:

STRUC

可以为数据指令(data structure)使用STRUCENDSTRUC。例如:

1
2
3
4
struc person
name: resb 10
age: resb 1
endstruc

现在可以这样为上面的指令创建一个实例:

1
2
3
4
5
6
7
8
9
10
section .data
p: istruc person
at name db "name"
at age db 25
iend

section .text

_start:
mov rax, [p + person.name]

%include

可以 include 其他的汇编文件,然后跳转到这些文件中的某一行继续执行,或者调用其中的函数。

结语

译者罗嗦几句:感觉这结语没啥好翻译的,与前面都差不多;也并没学会宏,使用的话看还是再找些资料读吧。

原文