一文搞懂 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多平台发布

相关推荐
计算机毕设VX:Fegn08955 小时前
计算机毕业设计|基于springboot + vue蛋糕店管理系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计
没差c5 小时前
springboot集成flyway
java·spring boot·后端
三水不滴5 小时前
Redis 过期删除与内存淘汰机制
数据库·经验分享·redis·笔记·后端·缓存
笨蛋不要掉眼泪6 小时前
Spring Boot集成LangChain4j:与大模型对话的极速入门
java·人工智能·后端·spring·langchain
sheji34169 小时前
【开题答辩全过程】以 基于SpringBoot的疗养院管理系统的设计与实现为例,包含答辩的问题和答案
java·spring boot·后端
短剑重铸之日9 小时前
《设计模式》第六篇:装饰器模式
java·后端·设计模式·装饰器模式
码界奇点10 小时前
基于Flask与OpenSSL的自签证书管理系统设计与实现
后端·python·flask·毕业设计·飞书·源代码管理
代码匠心11 小时前
从零开始学Flink:状态管理与容错机制
java·大数据·后端·flink·大数据处理
分享牛11 小时前
LangChain4j从入门到精通-11-结构化输出
后端·python·flask
知识即是力量ol13 小时前
在客户端直接上传文件到OSS
java·后端·客户端·阿里云oss·客户端直传