宏与高级伪指令(Macros & Advanced)
GAS 支持两类"宏":汇编器宏(.macro)和 C 预处理器宏(经 gcc 驱动编译 .S 文件时)。
GAS .macro
.macro pushmany reg1, reg2
pushq \reg1
pushq \reg2
.endm
# 使用
pushmany %rbx, %rbp
宏参数用 \参数名 引用;给寄存器传参时写 %\reg 或在调用处传 %rbx 后宏内写 \reg。
带默认值的参数(默认值只能缺省末尾的参数):
.macro add3 a, b, c=0
movq \a, %rax
addq \b, %rax
addq \c, %rax
.endm
add3 $1, $2 # c 取默认值 0
add3 $1, $2, $3 # 显式给出全部参数
字符串内也可替换参数(\msg),配合 .previous 在宏里切换节区后自动切回:
.macro logmsg msg
.section .rodata
1: .asciz "\msg"
.previous
.endm
宏内定义符号可用 GAS 的
\@(每次展开都不同的编号)生成唯一符号,避免重复展开时冲突。
条件汇编
.ifdef DEBUG
# 调试代码
.endif
.set OPT, 3
.if OPT >= 2
nop
.endif
.if的表达式只能引用已定义的常量符号;%0这类宏参数计数是 C 预处理器的概念,GAS 不支持。
符号可在命令行定义,便于构建系统切换功能:
as --defsym DEBUG=1 foo.s -o foo.o # 相当于 .set DEBUG, 1
也可比较字符串:.ifc "x86_64", "x86_64"(相等成立)/.ifnc(不等成立)。
重复块
.rept 8
pushq %rax
.endr
.irp reg, rbx, rbp, r12
pushq %\reg
.endr
包含与符号控制
| 伪指令 | 作用 |
|---|---|
.include "file" |
包含其他源文件 |
.globl sym |
导出符号 |
.local sym |
局部符号 |
.hidden sym |
符号不参与动态符号导出 |
.comm sym, size, align |
公共(未初始化)变量 |
.weak sym |
弱符号 |
结构/偏移定义技巧
用 .equ 定义结构体字段偏移(最直观、最常用):
.equ OFFSET_A, 0
.equ OFFSET_B, 8
.equ OFFSET_C, 12
# 用偏移访问"结构体"
movl OFFSET_B(%rdi), %eax # 读 p->b(rdi = 结构体指针)
GAS 也支持 .struct 方式(在绝对节区中定义符号值,配合 .skip 推进偏移):
.struct 0
field_a: .skip 8
field_b: .skip 4
.text # 用完切回代码段
C 预处理器(用 gcc/cc 编译 .S 大写后缀文件时可用)
#ifdef __x86_64__
movq %rdi, %rax
#else
movl %edi, %eax
#endif
注意 .S 文件经 cpp 预处理时,# 是预处理指令符,汇编注释建议改用 // 或 /* */。
内联汇编(C 中,AT&T 语法)
GCC 扩展内联汇编模板:
asm [volatile] (
"指令串"
: 输出操作数列表
: 输入操作数列表
[: 破坏(clobber)列表]
);
- 操作数用
%0 %1 %2 ...占位(按输出+输入顺序编号);汇编语句内要用两个%(%%rax)表示真实寄存器。 - 多条指令用
\n\t分隔。
long a = 3, b = 5, result;
asm volatile (
"movq %1, %%rax\n\t"
"addq %2, %%rax\n\t"
"movq %%rax, %0"
: "=r"(result) /* 输出:任意寄存器,= 表示只写 */
: "r"(a), "r"(b) /* 输入 */
: "%rax", "cc" /* 破坏:手工用的寄存器和标志位 */
);
常用约束字母:
| 约束 | 含义 |
|---|---|
r |
任意通用寄存器 |
m |
内存操作数 |
i |
立即数 |
= |
修饰符:只写输出 |
+ |
修饰符:读写(输入输出同一操作数) |
& |
修饰符:early clobber(输出在输入读完前就被写,须与输入不同寄存器) |
"0" 等数字 |
与第 N 个操作数使用同一寄存器 |
clobber 列表要点:
- 手工用到的寄存器必须列出,否则编译器可能把数据放进去被破坏。
"cc":指令修改了标志寄存器。"memory":内存可能被修改,禁止编译器把内存访问跨过这条 asm 重排(内存屏障)。volatile:禁止编译器因"输出未用"而删除该 asm。
若偏好 Intel 语法,可用 -masm=intel 或 .intel_syntax noprefix 切换,但本知识库统一 AT&T。