Cover image for GNU AS

GNU AS

字数 1.9k
阅读
访客

时间轴

时间轴

2025-09-27

init

本文介绍了 GNU AS 汇编器在 ARM64(AArch64)架构下的语法与伪指令用法,涵盖了标号、注释、符号(全局与局部)、对齐伪指令、数据定义伪指令(整数、浮点数、字符串)、重复块定义(.rept/.endr)、常量定义(.equ/.set)、函数相关伪指令、条件编译伪指令以及段相关伪指令(.section、.pushsection/.popsection)等内容,并对比了 AT&T 语法与 ARM 官方汇编语法的区别。

参考文档:

ARM64 的汇编器

  • ARM 公司的官方的汇编器
  • GNU AS 汇编器:aarch64-linux-gnu-as
  • gcc 采用 as 作为其汇编器
    • AT&T:源自贝尔实验室,为开发 UNIX 系统而产生的汇编语法(x86 下 gcc 默认输出 AT&T 语法)
    • ARM 格式:arm 官方汇编语法(AArch64 下 gcc 默认输出 ARM 统一汇编语言(UAL)格式)

语法

  • label: 任何以冒号结尾的标识符都被认为是一个标号
  • 注释
    • // 表示注释
    • # 在一行的开始,表示注释整行
  • 指令,伪指令,寄存器可以全部都是大写或者小写,GNU 风格默认小写

Symbol

代表它所在的地址,也可以当作变量或者函数来使用

  • 全局 symbol, 可以用.global 来声明
  • 局部 symbol,主要在局部范围内使用,开头以 0-99 之间的数字为标号名,通常和 b 指令结合使用
  • f: 指示编译向前搜索
  • b:指示编译器向后搜索

对齐伪指令

  • .align 对齐,填充数据来实现对齐。可以填充 0 或者使用 nop 指令。
    • 告诉汇编程序,align 后面的汇编必须从下一个能被 2^n 整除的地址开始分配
    • ARM64 系统中,第一个参数表示 2^n 大小

数据定义伪指令

整数与浮点伪指令汇总
指令数据类型/作用字节数补充说明
.byte定义 8 位整数1 B通常用于字符、控制位
.hword定义 16 位整数(half-word)2 B某些架构中也叫.short
.int/.long定义 32 位整数4 B.int是别名,效果一样
.quad定义 64 位整数(quad-word)8 B在 AArch64 中非常常用
.float定义 IEEE-754 单精度浮点数(32 位)4 B等价于 C 语言中的float
字符串定义伪指令
指令功能说明
.ascii "str"将字符串原样插入,不自动添加\0,适用于非 C 风格字符串
.asciz "str"在字符串末尾自动追加一个空字符\0,适用于 C 字符串(推荐)
.rept … .endr:重复块定义

语法:

1
2
3
.rept <count>
<内容>
.endr

作用:重复某段汇编代码或数据定义若干次,适用于初始化数组或填充空间。

例子:

1
2
3
.rept 3
.long 0
.endr

等价于:

1
2
3
.long 0
.long 0
.long 0
.equ / .set:常量定义(赋值操作)

这两个指令完全等价,只是语法风格略不同。
.equ

1
.equ abcd, 0x45

让 abcd 成为 常量宏定义,值为 0x45。
.set

1
.set abcd, 0x45

同样效果,也定义 abcd 为 0x45。

典型用途:用于定义寄存器地址、常量位掩码等。常见用法示例(结合 .equ 与 .rept):

1
2
3
4
5
6
.equ LED_BASE, 0x3F200000

.section .data
.rept 4
.int LED_BASE
.endr

表示将 LED_BASE 这个地址填充 4 次,每次 4 字节,共 16 字节。

.equ与 C 的#define区别:

.equ/.set#define
汇编阶段赋值,数值不可变预处理阶段文本替换
可用于表达式(如.equ val, 4+5只做文本拼接
在汇编器内部使用.ifdef等伪指令做条件判断可与#ifdef等配合使用
函数相关的伪指令
伪操作作用说明
.global定义一个全局的符号
.include引用头文件
.if .else .endif控制语句结构,用于条件编译
if 语句伪操作
指令含义说明
.ifdef symbol判断symbol是否已定义
.ifndef symbol判断symbol是否定义
.ifc str1,str2判断字符串str1str2是否相等
.ifeq expr判断表达式expr的值是否为 0
.ifeqs str1,str2等价于.ifc str1,str2
.ifge expr判断表达式expr的值是否 ≥ 0
.ifle expr判断表达式expr的值是否 ≤ 0
.ifne expr判断表达式expr的值是否 ≠ 0
与段相关的伪操作
  • .section 表示接下来的汇编会链接到哪个段里,例如代码段,数据段等
  • 每一个段以段名为开始,以下一个段名或者文件尾为结束
1
.section name, "flags"

后面可以添加 flags,表示段的属性

标志含义说明
aallocatable:该段在运行时需要被加载到内存中。
dGNU_MBIND section:GNU 使用的特殊绑定段。
eexcluded:该段不会被包含在可执行文件或共享库中。
wwritable:该段可写。
xexecutable:该段包含可执行代码。
Mmergeable:可以和其他具有相同属性的段合并(通常用于只读字符串等)。
Sstring:该段包含以 0 结尾的字符串。
Ggroup:该段属于某个 section group(如 COMDAT)。
Tthread-local-storage:该段用于线程局部存储(TLS)。
?unspecified group:该段属于前一个 section 的 group(如果有的话)。

举例:

1
.section ".idmap.text","awx"

.pushsection <name>
将接下来的代码或数据插入到指定的 section(段)中,同时保存当前 section 状态

.popsection
表示结束前面的 push,并恢复原来的 section

  • 成对使用

  • 作用仅在pushsectionpopsection之间的代码,对其他代码没有影响。

  • 其余代码仍然属于原先的段,比如.text.data

1
2
3
4
5
6
7
8
9
10
    .text
.globl _start
_start:
nop // 在默认的 .text 段中

.pushsection .mydata, "a"
.long 0x12345678 // 被插入到 .mydata 段中
.popsection

nop // 又回到 .text 段

_start和两个nop都属于.text段;

.long 0x12345678被插入到了自定义的.mydata段中。

  • .macro 和.endm 组成一个宏
  • .macro 后面跟着的是宏的名称,在后面是宏的参数
  • 在宏里使用参数,需要添加前缀“\”
1
.macro plus1 p, p1

定义了一个名为 plus1 的宏,有两个参数 p 和 p1

在宏里使用参数需要前缀,“\p"表示第一个参数,”\p1"表示第二个参数

  • 宏参数定义的时候可以设置一个初始化值
1
.macro reserve_str p1=0 p2

第一个参数 p1 有一个初始化的值,0。这个时候可以使用 reserve_str a,b 或者 reserve_str, b 来调用这个宏

在宏里使用参数可能出现的问题
在宏里使用参数可能出现的问题

解决办法:

  • 使用空格或者使用 altmacro+&

使用空格或者使用altmacro+&
使用空格或者使用altmacro+&

  • 使用”\()“表示连接

使用"()"表示连接
使用"()"表示连接

linux内核使用"()"的例子
linux内核使用"()"的例子

ARM64 特有的特性

ARM64 编译选项
  • -EB:用于大端模式的 CPU,-EL:用于小端模式的 CPU
  • -mabi:指定 ABI 模式,ilp32 用于 ELF32,lp64 用于 ELF64,默认值为 lp64
  • -mcpu=processor+extension:指定 CPU 型号,例如 cortex-a72
  • -march=,用于指定支持的架构,例如 armv8.2-a
  • ARM64 支持的 extension,见 GNU 汇编器 as_v2.34 9.1.2 章
特殊字符
  • //表示注释
  • #若在一行开始表示注释,不在一行开始也可表示立即数
  • :low12 表示低 12 位
1
2
adrp x0, foo
ldr x0, [x0, #:lo12:foo]
  • ldr 伪操作
  • .bss 切换到 bss 段
  • .dword/.xword 64 位数据
  • name .reg register_name 为寄存器命名
1
foo .req w0