一文搞懂 AT&T 汇编

1 x86_64 汇编风格

x86_64架构下的CPU有两种主要的汇编风格: Intel风格和AT&T风格。

这里风格是指汇编代码的书写语法,它们通过汇编器汇编生成的二进制指令是一样的,因为最终都要在x86_64架构下的CPU下运行。

支持Intel汇编风格的操作系统主要是Windows,支持AT&T汇编风格的操作系统主要是Unix以及Unix-like操作系统,这主要是因为AT&T贝尔实验室发明了Unix操作系统。

本文主要介绍AT&T汇编的基本语法。

2 AT&T 汇编

2.1 语法格式

AT&T汇编的基本语法格式如下:

c 复制代码
// AT&T 汇编风格
mnemonic	source, destination

mnemonic是助记符,比如mov

source是源操作数。

destination是目的操作数。

从语法上看,AT&T汇编是从左向右执行,比如如下汇编代码:

c 复制代码
// AT&T 汇编风格
mov %ax, %bx

就是将寄存器ax的内容移动到寄存器bx

AT&T汇编相反,大家比较熟悉的Intel汇编是从右往左执行:

c 复制代码
// Intel 汇编风格
mnemonic destination, source

上面的mov使用Intel汇编书写就是:

c 复制代码
// Intel 汇编风格
mov bx, ax

2.2 寄存器

AT&T汇编的一大特征就是寄存器前面有一个%,比如%ax%rax%bx%rbx等。

2.3 字面量

AT&T汇编中,字面量前面都有一个$,比如:

c 复制代码
mov $100, %ax

上面汇编代码将字面量100移动到寄存器ax

2.4 内存寻址方式

AT&T的内存寻址方式通用语法为:

arduino 复制代码
segment-override:signed-offset(base,index,scale)

segment-override是段地址。

signed-offset(base, index, scale)是段内偏移,计算方式为base + signed-offset + index * scale

通过段地址:段内偏移,就可以定位到具体的内存。

需要注意的是,上面内存寻址语法中的每一个部分,都可以根据实际情况省略掉。省略segment-override段地址就是当前段,省略signed-offsetbaseindex这些值就是0,省略scale就是1

下面给出一些寻址例子:

c 复制代码
100					    // 访问 当前段:100 处内存
%es:100					// 访问 es:100 处内存
(%eax)					// 访问 当前段:eax 处内存
(%eax,%ebx)				// 访问 当前段:(eax+ebx) 处内存
(%ecx,%ebx,2)		    // 访问 当前段:(ecx+ebx*2) 处内存
(,%ebx,2)				// 访问 当前段:(ebx*2) 处内存
-10(%eax)				// 访问 当前段:(eax-10) 处内存
%ds:-10(%ebp)		    // 访问 ds:(ebp-10) 处内存

2.5 操作数大小

假设有如下AT&T汇编代码:

c 复制代码
mov $100, %es:(%eax)

上面代码将字面量100移动到内存地址es:eax处,但是100到底在内存中占用多少字节呢?

如果占用1字节,那么es:eax内存地址存储的就会是0x64

如果占用2字节,那么es:eax内存地址存储的就会是0x00 0x64

为了做出区分,AT&T汇编在助记符后面添加后缀进行区分:

后缀b代表1个字节;

后缀w代表2个字节;

后缀l代表4个字节;

后缀q代表8个字节。

那么,如果上面例子100占用4个字节,那么正确的写法就是:

c 复制代码
movl $100, %es:(%eax)

2.6 控制转移指令

AT&T汇编中控制转移包括jumpcallret

如果代码的转移在相同的代码段,那么就是近(Near)转移。

如果代码转移到不同的代码段,那么就是远(Far)转移。在远转移的情况下,助记符前面需要加上前缀l,比如ljumplcalllret

转移指令中目的内存地址表示可以分为 label、寄存器、直接内存地址、段地址-段内偏移 这4种形式:

c 复制代码
// 1. label 形式
label1:
	.
	.
  jmp	label1 // near 跳转
  
// 2. 寄存器形式
jmp	*%eax			// near 跳转,跳转到 eax 指向的地址
jmp	*%ecx			// near 跳转,跳转到 ecx 指向的地址
jmp	*(%eax)			// near 跳转,先取出 eax 指向内存处的值,然后跳转到这个地址
call	*(%ebx)		// near 调用,先取出 ebx 指向内存处的值,然后转移到这个地址
ljmp	*(%eax)		// far 跳转,先取出 eax 指向内存处的值,然后跳转到这个地址
lcall	*(%ebx)		// far 调用,先取出 ebx 指向内存处的值,然后转移到这个地址

// 3. 直接内存地址
jmp	*100		   // near 跳转,跳转到内存地址 100
call	*100	  // near 调用,转移到内存地址 100
ljmp	*100	  // far 跳转,跳转到内存地址 100
lcall	*100	 // far 调用,转移到内存地址 100

// 4. 段地址-段内偏移
jmp	$0x10, $0x100000 // 跳转到地址 0x10:0x100000 出

从上面例子可以看到,除了第1种和第4种形式,其他形式地址前都需要加一个*

2.7 rip 寄存器相对寻址

rip 寄存器相对寻址是一种特殊的寻址方式。

假设有个一个全局变量global_var,它位于内存0x1000处,下面的指令:

c 复制代码
movl global_var(%rip), %eax

会是什么意思呢?

如果按照上面内存寻址的计算方式,上面代码会将rip + 0x1000内存处的值移动到寄存器eax

但是,实际上这是rip寄存器相对寻址语法。

假设此时rip寄存器存储的值是0x3000,上面语法的等价形式是:

c 复制代码
movl -0x2000(%rip), %eax

其中-0x2000rip - 0x1000计算得到,所以这条语句的实际功能是将全局变量global_var的值移动到寄存器eax

本文由mdnice多平台发布

相关推荐
顽石九变3 分钟前
【SpringBoo3】SpringBoot项目Web拦截器使用
spring boot·后端
梦兮林夕20 分钟前
从零掌握 Gin 参数解析与验证
后端·go·gin
bobz96530 分钟前
IPSec IKE PSK 与扩展支持Xauth账户密码
后端
supermodule31 分钟前
基于flask的一个数据展示网页
后端·python·flask
315356691339 分钟前
manus邀请码申请手把手教程
前端·后端·面试
青石路1 小时前
经由同个文件多次压缩的文件MD5都不一样问题排查,感慨AI的强大!
java·后端
RainbowSea1 小时前
5. MySQL 存储引擎(详解说明)
数据库·后端·mysql
RainbowSea1 小时前
130道基础OJ编程题之: 68\~77
java·后端
庄园特聘拆椅狂魔3 小时前
SpringBoot项目中注解使用规范
java·spring boot·后端
架构文摘JGWZ3 小时前
Spring40种注解(下)!!
后端·学习·spring