X64 汇编/宏
当我调用一个函数时, 我并不知道它会修改哪些寄存器, 因此我通常会决定将所有易失性寄存器保存起来, 然后在调用结束之后恢复寄存器的值. 同时, 我会确保 rsp 是 16 字节对齐的. 如果 rsp 已经是 16 字节对齐, 那我什么都不用做; 如果它不是, 那我会主动推入 8 个随机字节到栈上. 在函数调用结束后, 为了使栈保持平衡, 我必须确认在这次调用前我是否推入了额外的 8 个字节数据. 这些步骤看起来很麻烦, 但使用汇编编写的话只需要如下几行:
push rax
push rcx
push rdx
push rdi
push rsi
push r8
push r9
push r10
push r11
push rbp
mov rbp, rsp
and rsp, -16
;-----------------------
; call any function here
;-----------------------
mov rsp, rbp
pop rbp
pop r11
pop r10
pop r9
pop r8
pop rsi
pop rdi
pop rdx
pop rcx
pop rax
它能很好的工作. 但对于我来说, 每次调用函数时都要重复的复制和粘贴代码太麻烦了. 我可以使用宏来进行优化. 宏能在编译期展开, 因而不会对性能有任何的影响.
NASM 支持两种形式的宏, 分别是单行和多行宏. 我们先从多行宏开始. 多行宏以 %macro
指令开头, 并以 %endmacro
结尾. 它的一般形式如下:
%macro name number_of_parameters
instruction
instruction
instruction
%endmacro
我们可以使用多行宏改写我们的代码, 将它们封装为 prepcall 和 postcall 两个宏.
%macro prepcall 0
push rax
push rcx
push rdx
push rdi
push rsi
push r8
push r9
push r10
push r11
push rbp
mov rbp, rsp
and rsp, -16
%endmacro
%macro postcall 0
mov rsp, rbp
pop rbp
pop r11
pop r10
pop r9
pop r8
pop rsi
pop rdi
pop rdx
pop rcx
pop rax
%endmacro
然后就可以在代码中使用它:
prepcall
call printf
postcall
至于单行宏, 它和 C 中的语法非常相似. 所有单行宏都必须从 %define
指令开始, 形式如下:
%define macro_name(parameter) value
我们可以创建以下的三个单行宏, 下面的代码会读取你的命令行输入, 然后打印出来.
%define arg0 rsp + 8
%define arg1 rsp + 16
%define arg2 rsp + 24
section .data
msg db "%s", 0x0A
section .text
global _start
extern printf
_start:
mov rdi, msg
mov rsi, [arg0]
call printf
mov rdi, msg
mov rsi, [arg1]
call printf
mov rdi, msg
mov rsi, [arg2]
call printf
syscall
mov rax, 60
mov rdi, 0
syscall
$ nasm -f elf64 -o main.o main.asm
$ ld main.o -o main -lc --dynamic-linker /lib64/ld-linux-x86-64.so.2
$ ./main arg2 arg3
./main
arg2
arg3