发布于 ,更新于 

x86 AT&T 汇编语法基础与寻址方式

数据定义

段定义

  1. .text 定义代码段
  2. .data 定义数据段
  3. .bss 定义未初始化数据段
  4. .rodata 定义只读数据段

数据段(.data)定义数据元素

伪指令 数据类型
.ascii 文本字符串
.asciz 文本字符串(后面加\0结尾)
.byte 字节值(8位整数)
.short 16位整数
.int 32位整数
.long 32位整数
.quad 64位整数
.octa 128位整数
.single 单精度浮点数(同.float)
.float 单精度浮点数
.double 双精度浮点数

.globl 伪指令将标定义为全局可链接的,跨文件可以访问的符号。

代码样例:

1
2
3
4
5
6
    .globl    msg                           # 将 msg 定义为全局的
pi:
.float 3.14159
.long 100, 200, 300, 500, 400
msg:
.asciz "This is a text message."

定义未初始化数据(.bss段)

  • .comm 定义全局的通用内存区域
  • .lcomm 定义局部的通用内存区域

格式如下:

1
.comm    symbol, length

代码样例:

1
2
3
.section  .bss
.comm symbolName1, 10000 # 定义10000字节,符号名为 symbolName1
.lcomm symbolName2, 200 # 定义200字节,符号名为 symbolName2

定义静态符号

使用 .equ 伪指令定义静态符号,也可以把它当作一个宏定义。
格式举例:

1
2
   .equ    LINUX_SYS_CALL, 0x80
movl $LINUX_SYS_CALL, %eax

立即数,数据传递方向与寄存器

立即数

所有数字前面加上美元符号,表示立即数,如 $100 表示十进制的 100 的立即数,$0x100 表示十六进制的 0x100 立即数。

数据传递方向

数据传递方向为从左到右 (与 Intel 语法的从右到左相反)

寄存器寻址

寄存器寻址需要在寄存器名称前面加上百分号 %

指令位宽

涉及不同位宽的指令,需要在指令后面加上位宽后缀。

位宽后缀 数据位宽
b 8 位
w 16 位
l 32 位
q 64 位

代码样例:

1
2
   movl   $0x12345678, %eax      # 将立即数 0x12345678 传递给 EAX 寄存器
movb $al, %bl # 将寄存器 AL 的值复制给 BL

内存寻址方式

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
2
3
4
5
6
7
8
9
10
11
    .section  .data
data1:
.int 0x12345678

.section .bss
.comm data2, 8

.section .text
movl data1, %eax # 直接用内存位置的标签 data1 来表示内存寻址
addl %eax, %eax
movl %eax, data2 # 直接用内存位置的标签 data2 来表示内存寻址

标签值会最终会转化为一个 32 位的内存地址。

在 64 位 x86-64 模式下,不能再使用一个 32 位的内存地址值来进行寻址了。这个32位的标签值被重新释义为:相对于程序计算器( PC )的偏移。所以在 64 位模式下,出现了一种叫相对 PC 寻址,举例:

1
2
3
4
5
6
7
8
9
10
11
    .section  .data
data1:
.int 0x12345678

.section .bss
.comm data2, 8

.section .text
movl data1(%rip), %eax # 直接用内存位置的标签 data1 来表示相对PC寻址
addl %eax, %eax
movl %eax, data2(%rip) # 直接用内存位置的标签 data2 来表示相对PC寻址

机器码中的 ModRM 字段中,mod == 00,r/m == 101 时,表示为(1)直接寻址 或者 (2)相对 PC 寻址,指令会带一个32位的整数作为内存地址值。

3. 寄存器间接寻址

将内存地址值存放在一个通用寄存器中,通过寄存器的值作为内存地址来间接访问内存的数据。用小括号把寄存器括起来表示,举例:

1
2
   movl    %ebx, (%eax)    # 32位下,(%eax) 表示将 EAX 寄存器里面的值作为内存地址进行寻址
movq %rbx, (%rax) # 64位下,(%rax) 表示将 RAX 寄存器里面的值作为内存地址进行寻址

机器码中的 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
2
3
4
5
6
7
8
9
    .section .text
movl %ebx, 4(%edi)
movl %eax, -8(%esi)
movl %edx, 0x12345678(%eax)
movl %ecx, values(%edx)

.section .data
values:
.int 100, 200, 300

机器码中的 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
2
3
4
5
6
movl    %eax, (%ebx, %ecx)              # 没有比例因子时,默认比例因子为 1
movl %eax, (%ebx, %ecx, 4) # EA = ebx + ecx * 4
movl %eax, (%ebx, %ecx, 8) # EA = ebx + ecx * 8
movl %eax, values(, %edi, 4) # EA = values + edi * 4
movl %eax, (, %ecx, 8) # EA = ecx * 8
movl %eax, values(%esi, %edi, 4) # EA = values + esi + edi * 4

机器码中的 ModRM 字段中,当 r/m == 100 时,表示使用基址加变址寻址,具体参数由后继的 SIB 字节表示

  • (1) mod == 00 时,表示不带偏移量
  • (2) mod == 01 时,表示带 8 位偏移量
  • (3) mod == 10 时,表示带 32 位偏移量

ModRM 字段

ModRM 字段为 1 字节,具体可以按下面位域分为 3 段。用于对指令的操作数或者寻址做出说明。

1
2
3
4
5
6
ModRM 字段:
┌───┬───┬───┬───┬───┬───┬───┬───┐
7 6 5 4 3 2 1 0
├───┴───┼───┴───┴───┼───┴───┴───┤
│ mod │ reg │ r/m │
└───────┴───────────┴───────────┘
  • 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

特殊情况说明:

  1. 当 mod == 00 并且 r/m == 101 时,只使用一个 32 位的偏移量做寻址。32 位下为 32 位地址值,64 位下为相对程序计算器 PC 的偏移。
  2. 当 r/m = 100 时,表示后继带一个 SIB 的字节,用这个字节的信息来进行内存寻址。

SIB 字段

SIB 字段为 1 字节,具体可以按下面位域分为 3 段。用于对指令的内存寻址做出说明。

1
2
3
4
5
6
SIB 字段:
┌───┬───┬───┬───┬───┬───┬───┬───┐
7 6 5 4 3 2 1 0
├───┴───┼───┴───┴───┼───┴───┴───┤
│ scale │ index │ base │
└───────┴───────────┴───────────┘
  1. scale 为比例因子,scale的值: 00 = 1, 01 = 2, 10 = 4, 11 = 8
  2. index 为变址寄存器
  3. base 为基址寄存器

注意:

  1. RSP/ESP 寄存器不能用于 index 变址寄存器,所以,当 index == 100 时,只存在基址寄存器 base,变址寄存器 index 和比例因子 scale 忽略不用;另外,R12 时可以用于index 变址寄存器的。

  2. RBP/EBP/R13 寄存器默认是作为基址寄存器来使用,x86 要求RBP/EBP/R13 寄存器做基址寄存器时,必须带一个 8 位或者 32 位的偏移量。所以,如果 mod == 00,r/m == 100,base == 101时,base基址寄存器字段并不是表示 RBP/EBP/R13,而是要忽略这个基址寄存器,不存在基址寄存器,只存在变址寄存器,并把一个 32 位的偏移量作为基地址。所以出现以下形式的寻址:

1
2
   movl    %eax,  values(, %edi, 8)      # 这里是忽略基地址寄存器,不需要基地址寄存器的情况 
movl %eax, (, %edi, 8) # 忽略disp32的话,汇编器会默认加上一个为 0 的 disp32 值








汇编语言程序设计