宏与高级伪指令(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。