x86_64汇编系列:
- x86_64汇编之一:AT&T汇编语法
- x86_64汇编之二:x86_64的基本架构(寄存器、寻址模式、指令集概览)
- x86_64汇编之三:x86_64汇编和x86_32汇编的区别
- x86_64汇编之四:函数调用、调用约定
- x86_64汇编之五:System V AMD64调用约定下的函数调用
汇编语法主要有两大派系:AT&T语法 和 Intel语法。
GAS (GNU Assembler) 编译器默认是基于AT&T语法;MASM、NASM等编译器默认基于Intel语法。
需要说明的是,GAS汇编器除了支持AT&T语法之外,自己也定义了一些额外的directives
,用于辅助完成汇编操作。关于GAS汇编器及其语法可以参考GAS的官方文档:https://sourceware.org/binutils/docs/as/
由于AT&T语法在Linux平台上使用比较广泛,本文将主要介绍AT&T语法。下面的文档对AT&T汇编语法作出了详细的总结,本文就不再赘述:
https://gist.github.com/lvhlvh/f6c456b95182d99385f2694257ac77e2
https://cs61.seas.harvard.edu/site/2018/Asm1/
下面仅针对上述文档做些补充和强调
1. 指令的长度后缀
AT&T语法要求在指令后面加上后缀表示长度,根据操作的是1字节、2字节、4字节、8字节,分别对应后缀b, w, l, q
,不加后缀默认是w
。例如movq $1, %rax
。
2. 跳转和函数调用指令的语法
AT&T语法中的跳转和函数调用指令(jmp, jcc, call
等)比较特殊,如下表所示:
上表给出了如果跳转和函数调用的操作数分别是寄存器、立即数、内存,该如何表示地址:
- 寄存器:如果想要跳到寄存器
%rax
内容对应的地址,不是写成jmpq %rax
,而是写成jmpq *%rax
- 立即数:如上表所示,不用加
*
号 - 内存:如果想要跳转到
%rip + 0x10
对应的地址,不是写成jmpq 0x10(%rip)
,而是写成jmpq *0x10(%rip)
3. 指令寄存器相对寻址 (%rip-relative
)
在x86_64汇编中,我们可以使用指令寄存器相对寻址这种寻址方式来定位全局变量和函数。
例如,C代码中有一个全局变量a
,我们可以使用a(%rip)
来定位到该变量,而不是使用a
来定位。
使用这种寻址方式能帮助我们编写出 position-independent code (PIC) 的代码,进而获取 position-independent executables (PIEs) 的执行文件。关于PIC和PIE的概念,涉及程序链接的细节,这里暂时不讲。
注意:汇编器在解析汇编代码时,不是将a(%rip)
中的a
解析成a
的地址,而是会将a
解析成变量a
的地址和当前%rip
的偏移,这也是为什么这种语法寻址a
可行的原因。
参考:https://cs61.seas.harvard.edu/site/2018/Asm1/
4. 一个AT&T汇编文件代码分为哪些部分
一个AT&T汇编文件分为多个section,在文档https://gist.github.com/lvhlvh/f6c456b95182d99385f2694257ac77e2中有说明,这些section包括:
.text
:存放代码对应的指令.bss
:存放未初始化的初始化的全局和静态变量,在运行时该区域初始是全0。.bss
区域也可以用.comm/.lcomm
和声明(https://sourceware.org/binutils/docs/as/bss.html#bss).rodata
:存放只读数据和变量,例如字符串字面量.data
:存放余下的数据和变量,可读可写
下面是一个C语言程序的内存布局,和汇编代码有一定的对应关系。
5. 如何编译和运行AT&T汇编代码
使用as
命令对汇编文件进行汇编生成目标文件:as xxx.s -o xxx.o
然后使用ld
命令对目标文件进行链接生成可执行文件:ld xxx.o -o xxx
注意,ld
命令进行链接要求目标文件的.text
段必须有一个入口点,ld
默认认为_start
标签对应的代码是入口点。
例如下面的汇编代码:
.section .data
output:
.string "Hello\n"
.text
.globl _start
_start:
movq $1, %rax
movq %rax, %rsp # stack alignment
movq $1, %rdi
movq $output, %rsi
movq $6, %rdx
syscall
movq $60, %rax
xor %rdi, %rdi
syscall
上面的汇编代码可以正常汇编和链接,但是,如果将代码中的_start
标签去掉,或者改成其他名称,在执行ld
命令之后就会报告一个警告信息:
ld: warning: cannot find entry symbol _start; defaulting to 00000000004000b0
ld
会默认给我们找一个入口点,有可能这个默认入口点是错误的,这样就会导致运行可执行文件出现错误。