1. AT&T与Intel汇编语法概述
第一次接触汇编语言时,很多人会被两种截然不同的语法风格搞懵。比如在Linux系统用GCC反汇编看到的movl $1, %eax,和在Windows用Visual Studio看到的mov eax, 1其实是同一件事——把数字1放进eax寄存器。这就是AT&T和Intel两大汇编语法流派最直观的区别。
我在调试跨平台项目时就踩过坑:在Windows下写好的汇编代码放到Linux编译,结果报了一堆语法错误。后来才发现GCC默认使用AT&T语法,而微软工具链用Intel语法。这两种语法就像中文里的简体字和繁体字,表达相同的意思但写法不同。理解它们的差异,就像掌握双语切换能力,能让你在不同平台游刃有余。
2. 寄存器操作差异
2.1 寄存器命名规则
在AT&T语法中,寄存器名前必须加%前缀,比如%eax、%ebx。这就像给寄存器穿上了制服,一眼就能识别身份。而Intel语法更简洁,直接写eax、ebx就行。
我刚开始用GDB调试时,经常忘记写%导致报错。有次调试一个段错误,花了半小时才发现是漏写了%符号。这种细节差异看似微小,却可能浪费大量时间。
2.2 操作数顺序
这是最容易混淆的差异之一。AT&T语法采用"源操作数在前,目标操作数在后"的顺序,就像说"把水倒进杯子";而Intel语法相反,是"把杯子接水"的逻辑。
看个具体例子:
# AT&T movl %ebx, %eax # 把ebx的值复制到eax ; Intel mov eax, ebx # 把ebx的值复制到eax这种差异在数据移动指令中特别明显。我建议新手可以这样记忆:AT&T像"从...到...",Intel像"目标=来源"。
3. 指令格式详解
3.1 立即数表示法
AT&T语法中,立即数要加$前缀,就像给数字贴标签。比如movl $42, %eax表示把数字42存入eax。而Intel语法直接写数字:mov eax, 42。
不加$的后果很严重。有次我写movl 42, %eax,结果程序崩溃。因为这里42被当成了内存地址,试图读取地址42的内容。这种错误在编译时不会报错,但运行时必然崩溃。
3.2 内存寻址方式
内存访问的语法差异最大。Intel用方括号[]表示内存引用,AT&T用圆括号()。比如访问eax指向的内存:
# AT&T movl (%eax), %ebx # 从eax指向的内存读取到ebx ; Intel mov ebx, [eax] # 从eax指向的内存读取到ebx复杂寻址时差异更明显。假设要访问[eax + ecx*4 + 8]这个地址:
# AT&T movl 8(%eax, %ecx, 4), %ebx ; Intel mov ebx, [eax + ecx*4 + 8]AT&T的格式是offset(base, index, scale),这种结构化的表达其实更清晰,只是需要时间适应。
4. 操作数大小标识
4.1 后缀表示法
AT&T语法在指令后加后缀表示操作数大小:
b:字节(8位)w:字(16位)l:长字(32位)q:四字(64位)
例如movb移动字节,movw移动字。Intel语法则通过寄存器名或前缀表示:
# AT&T movb %al, %bl # 8位 movw %ax, %bx # 16位 movl %eax, %ebx # 32位 ; Intel mov bl, al # 8位 mov bx, ax # 16位 mov ebx, eax # 32位4.2 显式指定大小
在访问不确定大小的内存时,Intel需要用byte ptr等前缀明确大小:
; Intel mov al, byte ptr [ebx] # 读取1字节 mov ax, word ptr [ebx] # 读取2字节而AT&T通过指令后缀就能表达,不需要额外前缀。这种设计让代码更紧凑,但也增加了记忆负担。
5. 跳转与调用指令
5.1 跳转目标表示
远程跳转(跨段跳转)在AT&T中要用l前缀,比如ljmp、lcall。Intel则用far关键字:
# AT&T ljmp $0x8, $0x1234 # 跳转到段0x8偏移0x1234 ; Intel jmp far 08:00001234 # 同上间接跳转时,AT&T要在操作数前加*:
# AT&T jmp *%eax # 跳转到eax中的地址 jmp *(%eax) # 跳转到eax指向的内存中的地址 ; Intel jmp eax # 跳转到eax中的地址 jmp [eax] # 跳转到eax指向的内存中的地址5.2 条件跳转
条件跳转的助记符两者基本相同,但AT&T的条件码通常更直观。比如"大于"在AT&T是jg(jump if greater),Intel也是jg;"小于等于"都是jle。
我在移植代码时发现,条件跳转是少数几乎完全相同的部分,这大大减少了移植工作量。
6. 实际应用对比
6.1 函数调用示例
下面是一个简单的函数调用在两种语法下的对比:
# AT&T pushl %ebp movl %esp, %ebp subl $8, %esp movl 8(%ebp), %eax addl $5, %eax leave ret ; Intel push ebp mov ebp, esp sub esp, 8 mov eax, [ebp+8] add eax, 5 leave ret可以看到栈操作几乎一一对应,只是语法格式不同。leave指令(相当于mov esp,ebp加pop ebp)在两种语法中完全相同。
6.2 循环结构实现
实现一个累加1到100的循环:
# AT&T movl $0, %eax movl $1, %ecx loop_start: addl %ecx, %eax incl %ecx cmpl $101, %ecx jne loop_start ; Intel xor eax, eax mov ecx, 1 loop_start: add eax, ecx inc ecx cmp ecx, 101 jne loop_start两种语法下,循环逻辑完全一致。AT&T的cmpl对应Intel的cmp,incl对应inc。
7. 工具链选择建议
7.1 常见汇编器
- AT&T语法:GNU Assembler(gas)、LLVM
- Intel语法:NASM、MASM、FASM、YASM
我个人的经验是:Linux环境下多用GAS(AT&T),Windows下多用MASM/NASM(Intel)。但现代工具如GCC其实也支持-masm=intel选项输出Intel语法。
7.2 反汇编工具
objdump默认输出AT&T语法,加-M intel可切换:
objdump -d -M intel a.out在GDB中切换语法:
(gdb) set disassembly-flavor intel # 切换为Intel (gdb) set disassembly-flavor att # 切换回AT&T8. 学习建议与资源
刚开始建议先精通一种语法,再学习另一种。我的学习路径是:
- 通过NASM掌握Intel语法基础
- 用GDB调试Linux程序时学习AT&T
- 制作对照表帮助记忆差异点
推荐几个实用资源:
- 《Professional Assembly Language》:同时讲解两种语法
- GCC官方文档:包含AT&T语法详解
- Intel官方手册:最权威的Intel语法参考
记住,语法差异只是表象,底层机器码其实完全相同。就像用不同口音说同一句话,重要的是理解背后的计算机原理。