x86 AT&T 汇编语法基础与寻址方式
数据定义
段定义
.text
定义代码段.data
定义数据段.bss
定义未初始化数据段.rodata
定义只读数据段
数据段(.data)定义数据元素
伪指令 | 数据类型 |
---|---|
.ascii |
文本字符串 |
.asciz |
文本字符串(后面加\0 结尾) |
.byte |
字节值(8位整数) |
.short |
16位整数 |
.int |
32位整数 |
.long |
32位整数 |
.quad |
64位整数 |
.octa |
128位整数 |
.single |
单精度浮点数(同.float) |
.float |
单精度浮点数 |
.double |
双精度浮点数 |
.globl
伪指令将标定义为全局可链接的,跨文件可以访问的符号。
代码样例:
1 | msg # 将 msg 定义为全局的 |
定义未初始化数据(.bss段)
.comm
定义全局的通用内存区域.lcomm
定义局部的通用内存区域
格式如下:
1 | .comm symbol, length |
代码样例:
1 | .bss |
定义静态符号
使用 .equ
伪指令定义静态符号,也可以把它当作一个宏定义。
格式举例:
1 | 0x80 LINUX_SYS_CALL, |
立即数,数据传递方向与寄存器
立即数
所有数字前面加上美元符号,表示立即数,如 $100
表示十进制的 100
的立即数,$0x100
表示十六进制的 0x100
立即数。
数据传递方向
数据传递方向为从左到右 (与 Intel 语法的从右到左相反)
寄存器寻址
寄存器寻址需要在寄存器名称前面加上百分号 %
指令位宽
涉及不同位宽的指令,需要在指令后面加上位宽后缀。
位宽后缀 | 数据位宽 |
---|---|
b | 8 位 |
w | 16 位 |
l | 32 位 |
q | 64 位 |
代码样例:
1 | movl $0x12345678, %eax # 将立即数 0x12345678 传递给 EAX 寄存器 |
内存寻址方式
1. 寄存器
机器码中的 ModRM 字段中,mod == 11 时,r/m 表示为操作数为寄存器。
r/m 值 | 默认时 | REX.B == 1 时 |
---|---|---|
000 | EAX / RAX | R8 |
001 | ECX / RCX | R9 |
010 | EDX / RDX | R10 |
011 | EBX / RBX | R11 |
100 | ESP / RSP | R12 |
101 | EBP / RBP | R13 |
110 | ESI / RSI | R14 |
111 | EDI / RDI | R15 |
2. 直接寻址
32位模式下,直接以数据段的标签来表示内存地址的方式,进行寻址。举例:
1 | .data |
标签值会最终会转化为一个 32 位的内存地址。
在 64 位 x86-64 模式下,不能再使用一个 32 位的内存地址值来进行寻址了。这个32位的标签值被重新释义为:相对于程序计算器( PC )的偏移。所以在 64 位模式下,出现了一种叫相对 PC 寻址,举例:
1 | .data |
机器码中的 ModRM 字段中,mod == 00,r/m == 101 时,表示为(1)直接寻址 或者 (2)相对 PC 寻址,指令会带一个32位的整数作为内存地址值。
3. 寄存器间接寻址
将内存地址值存放在一个通用寄存器中,通过寄存器的值作为内存地址来间接访问内存的数据。用小括号把寄存器括起来表示,举例:
1 | movl %ebx, (%eax) # 32位下,(%eax) 表示将 EAX 寄存器里面的值作为内存地址进行寻址 |
机器码中的 ModRM 字段中,mod == 00 时,表示采用寄存器间接寻址; r/m 表示寄存器的值。
注:实际机器码中不存在 (%ebx)
/ (%rbx)
/ (%r13)
这种寄存器间接寻址,汇编器会自动把这类寄存器间接寻址加上一个 0 的偏移量。因为 ebx/rbx/r13 的 r/m 值为 101,而 mod == 00,r/m == 101 时表示为(1)直接寻址 或者 (2)相对 PC 寻址。请看下面的基址加偏移量寻址
4. 基址加偏移量寻址
将寄存器中的值,加上一个 8 位或者 32 位有符号整数的偏移量,得到的和作为内存地址来访问内存的数据。将偏移量的值放在小括号外面,用小括号把寄存器括起来表示,举例:
1 | .text |
机器码中的 ModRM 字段中,当 r/m != 100 时,
- (1) mod == 01 时,表示采用 寄存器 + 8位偏移量, r/m表示对应寄存器;
- (2) mod == 10 时,表示采用 寄存器 + 32位偏移量, r/m表示对应寄存器。
注:实际机器码中不存在 (%esp) 或 value(%esp) 这类寻址,汇编器会自动把这 esp/rsp/r12 的间接寻址转换为下面的基址加变址寻址。因为 r/m == 100 时代表的 esp/rsp/r12 被表示为带 SIB 字段的基址加变址寻址,请参考下面的基址加变址寻址
5. 基址加变址寻址
基址加变址寻址的表达式如下:
1 | 偏移量 (基址寄存器, 变址寄存器, 比例因子) |
最终的内存地址值 EA = 基址寄存器值 + 变址寄存器 * 比例因子 + 偏移量
比例因子只能为 1, 2, 4, 8 。
注: esp/rsp/r12 不能做变址寄存器
1 | movl %eax, (%ebx, %ecx) # 没有比例因子时,默认比例因子为 1 |
机器码中的 ModRM 字段中,当 r/m == 100 时,表示使用基址加变址寻址,具体参数由后继的 SIB 字节表示
- (1) mod == 00 时,表示不带偏移量
- (2) mod == 01 时,表示带 8 位偏移量
- (3) mod == 10 时,表示带 32 位偏移量
ModRM 字段
ModRM 字段为 1 字节,具体可以按下面位域分为 3 段。用于对指令的操作数或者寻址做出说明。
1 | ModRM 字段: |
- reg 域表示寄存器
- r/m 域可以表示寄存器,也可以表示为内存寻址,具体由 mod 域决定
- mod == 00 时,r/m 域为内存寻址,不加偏移量
- mod == 01 时,r/m 域为内存寻址,带一个 8 位的偏移量
- mod == 10 时,r/m 域为内存寻址,带一个 32 位的偏移量
- mod == 11 时,r/m 域为寄存器
详细说明:(下面与64位来说明,32位下是同理只用低32位下的寄存器部分)
- 下表中,disp8 表示 8 位偏移量,disp32 表示 32 位偏移量
- SIB 表示后面带一个叫 SIB 的 1 字节的字段,用这个字段来说明寻址方式
Mod 值 | R/M 值 | 默认值(当REX.B == 0) | 当REX.B == 1 时 |
00 | 000 | (%rax) | (%r8) |
001 | (%rcx) | (%r9) | |
010 | (%rdx) | (%r10) | |
011 | (%rbx) | (%r11) | |
100 | SIB | SIB | |
101 | disp32 或者 disp32(rip) | disp32 或者 disp32(rip) | |
110 | (%rsi) | (%r14) | |
111 | (%rdi) | (%r15) | |
01 | 000 | disp8(%rax) | disp8(%r8) |
001 | disp8(%rcx) | disp8(%r9) | |
010 | disp8(%rdx) | disp8(%r10) | |
011 | disp8(%rbx) | disp8(%r11) | |
100 | disp8 + SIB | disp8 + SIB | |
101 | disp8(%rbp) | disp8(%r13) | |
110 | disp8(%rsi) | disp8(%r14) | |
111 | disp8(%rdi) | disp8(%r15) | |
10 | 000 | disp32(%rax) | disp32(%r8) |
001 | disp32(%rcx) | disp32(%r9) | |
010 | disp32(%rdx) | disp32(%r10) | |
011 | disp32(%rbx) | disp32(%r11) | |
100 | disp32 + SIB | disp32 + SIB | |
101 | disp32(%rbp) | disp32(%r13) | |
110 | disp32(%rsi) | disp32(%r14) | |
111 | disp32(%rdi) | disp32(%r15) | |
11 | 000 | %rax | %r8 |
001 | %rcx | %r9 | |
010 | %rdx | %r10 | |
011 | %rbx | %r11 | |
100 | %rsp | %r12 | |
101 | %rbp | %r13 | |
110 | %rsi | %r14 | |
111 | %rdi | %r15 |
特殊情况说明:
- 当 mod == 00 并且 r/m == 101 时,只使用一个 32 位的偏移量做寻址。32 位下为 32 位地址值,64 位下为相对程序计算器 PC 的偏移。
- 当 r/m = 100 时,表示后继带一个 SIB 的字节,用这个字节的信息来进行内存寻址。
SIB 字段
SIB 字段为 1 字节,具体可以按下面位域分为 3 段。用于对指令的内存寻址做出说明。
1 | SIB 字段: |
- scale 为比例因子,scale的值: 00 = 1, 01 = 2, 10 = 4, 11 = 8
- index 为变址寄存器
- base 为基址寄存器
注意:
RSP/ESP 寄存器不能用于 index 变址寄存器,所以,当 index == 100 时,只存在基址寄存器 base,变址寄存器 index 和比例因子 scale 忽略不用;另外,R12 时可以用于index 变址寄存器的。
RBP/EBP/R13 寄存器默认是作为基址寄存器来使用,x86 要求RBP/EBP/R13 寄存器做基址寄存器时,必须带一个 8 位或者 32 位的偏移量。所以,如果 mod == 00,r/m == 100,base == 101时,base基址寄存器字段并不是表示 RBP/EBP/R13,而是要忽略这个基址寄存器,不存在基址寄存器,只存在变址寄存器,并把一个 32 位的偏移量作为基地址。所以出现以下形式的寻址:
1 | movl %eax, values(, %edi, 8) # 这里是忽略基地址寄存器,不需要基地址寄存器的情况 |