拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Intel 语法与 ATT 语法的故事:汇编语言的两种方言

如果你曾经在 Linux 下写过内联汇编或者在 Windows 下用 MASM 写过汇编程序你一定遇到过这样的困惑为什么同样的指令在不同地方写法完全不同为什么有人写mov eax, 1有人却写movl $1, %eax这不是谁对谁错而是汇编语言的两种“方言”——Intel 语法与ATT 语法——在历史长河中各自生长出的模样。今天我们就来一场深度探索把这两种语法的来龙去脉、脾气秉性、使用场景彻底讲透。我会像老朋友聊天一样给你讲它们背后的故事并用大量代码和图解让你从此不再被它们迷惑。一、前置代码展示同一个程序两种写法按照我们的约定先把一个完整的示例程序完整列出。这个程序的功能很简单计算 1 2然后返回结果。我们将分别用 Intel 语法和 ATT 语法写出 32 位 Linux 下的汇编程序。Intel 语法版本使用 NASM; intel_add.asm ; 编译nasm -f elf32 intel_add.asm -o intel_add.o ; 链接ld -m elf_i386 intel_add.o -o intel_add section .data a dd 1 b dd 2 result dd 0 section .text global _start _start: mov eax, [a] ; 将 a 的值加载到 eax add eax, [b] ; 加上 b 的值 mov [result], eax ; 结果存入 result ; 退出程序返回码为 eax 的值 mov ebx, eax ; 返回码放入 ebx mov eax, 1 ; sys_exit 系统调用号 int 0x80ATT 语法版本使用 GAS# att_add.s # 编译as --32 att_add.s -o att_add.o # 链接ld -m elf_i386 att_add.o -o att_add .section .data a: .long 1 b: .long 2 result: .long 0 .section .text .global _start _start: movl a, %eax # 将 a 的值加载到 eax addl b, %eax # 加上 b 的值 movl %eax, result # 结果存入 result # 退出程序返回码为 eax 的值 movl %eax, %ebx # 返回码放入 ebx movl $1, %eax # sys_exit 系统调用号 int $0x80这两段程序做的事情完全一样但写法差异巨大。接下来我们会逐条拆解这些差异并告诉你为什么它们会变成这样。二、故事的开始两种语法的诞生2.1 Intel 语法官方嫡长子1978 年Intel 发布了 8086 微处理器。为了帮助程序员使用这款芯片Intel 发布了官方的汇编语言文档定义了指令的标准书写格式。这种格式就是后来的Intel 语法。它的特点很直接操作数顺序是目的在前源在后就像赋值语句dest src寄存器直接写名字如ax、bx、eax立即数不带任何前缀如mov eax, 1内存操作数用方括号表示如[eax]、[ebp 8]。因为它是 Intel 官方定义的所以 Intel 语法在 DOS、Windows 世界以及许多早期汇编器如 MASM、TASM、NASM中占据主导地位。2.2 ATT 语法Unix 世界的反叛者与此同时在另一条时间线上Unix 正在贝尔实验室孕育。Unix 最初用汇编编写后来用 C 重写。但 C 编译器仍然需要生成汇编代码这就需要一个汇编器。当时的 Unix 团队选择了ATT 贝尔实验室开发的汇编器而这个汇编器使用的是ATT 语法。ATT 语法最初用于 PDP-11 和 VAX 等机器后来被移植到 x86。它沿用了 ATT 一贯的风格操作数顺序是源在前目的在后更像数学函数src - dest寄存器名前加%如%eax立即数前加$如$1内存操作数用圆括号如(%eax)、8(%ebp)指令后缀表明操作数大小如movl、movw、movb。由于 GNU 工具链GCC、GAS默认使用 ATT 语法所以 Linux、Unix、BSD 等开源世界几乎全都使用 ATT 语法。2.3 为什么会有两种语法根本原因在于Unix 和 Intel 是两个独立的生态。Intel 语法是 Intel 公司为了自己的处理器定义的主要用于 Intel 的开发工具和文档。而 ATT 语法是贝尔实验室为了 Unix 的移植而定义的主要用于 GNU 工具链。两种语法没有谁更好只是习惯不同。就像英式英语和美式英语都表达同样的意思但拼写和发音有差异。三、核心差异一操作数顺序这是最明显、也最容易让人搞混的差异。Intel 语法目的在前源在后mov eax, 1 ; 把 1 赋给 eaxeax 1 add eax, ebx ; eax eax ebx sub eax, 1 ; eax eax - 1可以理解为目的操作数 源操作数ATT 语法源在前目的在后movl $1, %eax # 把 1 赋给 eaxeax 1 addl %ebx, %eax # eax eax ebx subl $1, %eax # eax eax - 1可以理解为源操作数 - 目的操作数这个差异在阅读时非常容易出错。比如mov %eax, %ebx在 ATT 中是把eax的值复制到ebx但在 Intel 中mov eax, ebx是把ebx的值复制到eax。记忆技巧Intelmov dest, src像 C 语言的dest srcATTmov src, dest像 Unix 的管道src - dest。四、核心差异二寄存器前缀在 Intel 语法中寄存器直接写名字mov eax, ebx add ecx, 1在 ATT 语法中寄存器名前必须加%movl %ebx, %eax addl $1, %ecx%前缀的作用是让汇编器区分寄存器名和符号名。例如eax可能是一个变量名而%eax明确表示寄存器。在早期的 Unix 汇编器中%前缀被广泛使用后来延续到了 x86。五、核心差异三立即数前缀立即数就是直接写在指令中的常数。在 Intel 语法中立即数不带前缀mov eax, 1 add eax, 0x10在 ATT 语法中立即数前必须加$movl $1, %eax addl $0x10, %eax$前缀用于区分立即数和内存地址。例如movl $100, %eax是把立即数 100 放入eax而movl 100, %eax是把内存地址 100 处的内容放入eax。这个差异在 C 语言层面也有体现在 GCC 内联汇编中如果你写movl $100, %eax就是立即数如果写movl 100, %eax就是内存操作数。六、核心差异四指令后缀在 ATT 语法中指令后面通常会跟一个后缀字母表示操作数的大小。常见的后缀后缀大小对应 Intel 指令b1 字节byte ptrw2 字节word ptrl4 字节dword ptrq8 字节qword ptr例如movb $1, %al ; 1 字节 movw $1, %ax ; 2 字节 movl $1, %eax ; 4 字节 movq $1, %rax ; 8 字节在 Intel 语法中操作数大小通常由寄存器名决定mov al, 1 ; 1 字节 mov ax, 1 ; 2 字节 mov eax, 1 ; 4 字节 mov rax, 1 ; 8 字节但如果操作数是内存Intel 语法需要使用byte ptr、word ptr、dword ptr、qword ptr来明确大小mov byte ptr [eax], 1 mov word ptr [eax], 1 mov dword ptr [eax], 1 mov qword ptr [rax], 1ATT 语法通过后缀避免了这种歧义。七、核心差异五内存操作数表示这是两种语法最复杂的部分。Intel 语法方括号Intel 语法使用方括号[]表示内存操作数。常见形式mov eax, [ebx] ; 从 ebx 指向的地址读取 4 字节 mov eax, [ebx 4] ; 从 ebx4 读取 mov eax, [ebx ecx*4] ; 从 ebx ecx*4 读取 mov eax, [ebx ecx*4 8] ; 从 ebx ecx*4 8 读取基址寄存器和变址寄存器可以是任意通用寄存器。ATT 语法圆括号ATT 语法使用圆括号()表示内存操作数并且偏移量写在括号外。对应上面的例子movl (%ebx), %eax # 从 ebx 指向的地址读取 movl 4(%ebx), %eax # 从 ebx4 读取 movl (%ebx, %ecx, 4), %eax # 从 ebx ecx*4 读取 movl 8(%ebx, %ecx, 4), %eax # 从 ebx ecx*4 8 读取ATT 的内存寻址格式为偏移量(基址寄存器, 变址寄存器, 比例因子)其中偏移量、变址寄存器、比例因子可以省略。比例因子只能是 1、2、4、8。例如movl (%eax), %ebx ; 基址寻址 movl 4(%eax), %ebx ; 基址 偏移 movl (%eax, %ecx), %ebx ; 基址 变址 movl (%eax, %ecx, 4), %ebx ; 基址 变址*比例 movl 8(%eax, %ecx, 4), %ebx ; 基址 变址*比例 偏移对比表格寻址方式Intel 语法ATT 语法寄存器间接[eax](%eax)基址偏移[eax4]4(%eax)基址变址[eaxecx](%eax,%ecx)基址变址*比例[eaxecx*4](%eax,%ecx,4)基址变址*比例偏移[eaxecx*48]8(%eax,%ecx,4)八、核心差异六段寄存器与远指针在 16 位实模式下段寄存器非常重要。Intel 语法mov ax, ds mov es, ax mov ax, es:[bx] mov ds:[si], axATT 语法movw %ds, %ax movw %ax, %es movw %es:(%bx), %ax movw %ax, %ds:(%si)在 ATT 中段寄存器写在括号外用冒号分隔。九、核心差异七跳转指令和符号跳转指令在两种语法中也有所不同。Intel 语法jmp label jmp 0x1000 call functionATT 语法jmp label jmp *%eax ; 间接跳转需要加 * call function对于直接跳转两种语法相同。但对于间接跳转ATT 需要在操作数前加*以表示间接跳转。十、核心差异八字符串指令字符串指令在两种语法中也有差异。Intel 语法movsb movsw movsd rep movsbATT 语法movsb movsw movsl ; 注意ATT 中 32 位是 movsl 而不是 movsd rep movsb特别注意ATT 语法中movsl表示 32 位字符串传送而movsd在 ATT 中可能表示“移动标量双精度浮点”指令与 Intel 的movsd含义不同。这是两种语法之间的一个著名陷阱。此外ATT 的字符串指令操作数通常可以省略但也可以显式写出movsb %ds:(%esi), %es:(%edi)十一、核心差异九指令后缀与浮点指令浮点指令在两种语法中差异也很大。Intel 语法使用st(0)、st(1)等表示浮点寄存器fld dword ptr [eax] fadd st(0), st(1)ATT 语法使用%st(0)、%st(1)flds (%eax) fadd %st(1), %st(0)ATT 的后缀s、l表示单精度、双精度等。SSE 指令同样有后缀Intelmovaps xmm0, xmm1 addps xmm0, xmm1ATTmovaps %xmm1, %xmm0 addps %xmm1, %xmm0注意操作数顺序反转。十二、核心差异十注释风格注释风格不同Intel 语法通常使用;作为注释开始ATT 语法使用#作为注释开始也支持/* */块注释。示例; Intel 注释 mov eax, 1 # ATT 注释 movl $1, %eax十三、历史原因与生态分布为什么会有两种语法并存这要从两家公司的历史说起。Intel 语法Intel 作为 CPU 制造商定义了汇编语言的官方格式。DOS 和 Windows 世界主要使用 MASM、TASM、NASM 等汇编器它们默认使用 Intel 语法。Intel 的官方文档、SDMSoftware Developer Manual也都使用 Intel 语法。ATT 语法ATT 的贝尔实验室是 Unix 的发源地。Unix 的汇编器asGNU Assembler 的前身使用 ATT 语法。GCC 编译器输出 ATT 语法汇编因此整个 GNU/Linux、BSD、macOS早期等开源生态系统都使用 ATT 语法。生态分布总结平台/工具默认语法Windows MASMIntelWindows NASMIntelLinux GASATTLinux NASMIntel需指定GCC 内联汇编ATT默认Clang 内联汇编ATT默认Intel 官方文档Intel反汇编工具objdump默认 ATT可切换 Intel十四、切换语法同一个工具两种视角大多数 GNU 工具链的汇编器和反汇编器都支持切换语法。14.1 GAS 切换语法GASGNU Assembler默认使用 ATT 语法但可以通过.intel_syntax noprefix伪指令切换到 Intel 语法。例如.intel_syntax noprefix mov eax, 1 add eax, ebx使用noprefix可以省略寄存器前的%。如果需要前缀使用.intel_syntax prefix。14.2 objdump 切换语法objdump -d默认输出 ATT 语法。使用-M intel可以输出 Intel 语法objdump-d-Mintel program14.3 GCC 内联汇编切换语法GCC 内联汇编默认使用 ATT 语法但可以在内联汇编块中使用.intel_syntax noprefix临时切换。十五、真实案例反汇编同一个程序观察两种语法让我们用一个真实的 C 程序看看编译器生成的汇编代码在两种语法下的样子。C 程序// add.cintadd(inta,intb){returnab;}intmain(){intx10;inty20;intzadd(x,y);returnz;}编译并反汇编ATT 语法gcc-Sadd.c-oadd_att.s生成的add_att.s可能包含add: pushl %ebp movl %esp, %ebp movl 8(%ebp), %eax addl 12(%ebp), %eax popl %ebp ret反汇编Intel 语法gcc-S-masmintel add.c-oadd_intel.s生成的add_intel.s可能包含add: push ebp mov ebp, esp mov eax, DWORD PTR [ebp8] add eax, DWORD PTR [ebp12] pop ebp ret对比非常清晰ATT 的操作数顺序是源在前Intel 是目的在前ATT 有%和$前缀Intel 没有ATT 用8(%ebp)Intel 用[ebp8]。十六、用 Mermaid 画出两种语法的结构对比下面这张图直观展示了同一指令在两种语法下的元素对应关系ATT语法Intel语法对应对应对应指令: mov eax, 1操作数1: eax 目的操作数2: 1 源指令: movl $1, %eax操作数1: $1 源操作数2: %eax 目的这张图揭示了核心差异操作数顺序反转前缀不同指令后缀不同。十七、深入理解为什么 ATT 语法使用源在前ATT 语法源自 PDP-11 和 VAX 汇编器。在这些机器上指令的自然顺序是“从源到目的”类似于数据流的方向。例如在 VAX 汇编中movl source, destination这种风格被移植到 Unix 的 x86 汇编器中形成了 ATT 语法。而 Intel 语法则更贴近机器指令的微操作mov指令的机器码通常将目的操作数放在前面因此 Intel 语法选择目的在前。两种风格都有道理只是习惯不同。十八、实际开发中如何选择18.1 如果你是 Windows 开发者你会自然而然地使用 Intel 语法因为 MASM、NASM、FASM 等主流汇编器都默认 Intel 语法而且 Windows 下的调试器如 WinDbg、OllyDbg也默认 Intel 语法。18.2 如果你是 Linux/Unix 开发者你可能会使用 ATT 语法因为 GCC、GAS、objdump 等工具默认 ATT 语法。但很多 Linux 开发者也会手动切换到 Intel 语法因为 Intel 语法更接近 Intel 官方文档阅读起来更直观。18.3 内联汇编在 GCC 内联汇编中默认是 ATT 语法。但你可以使用.intel_syntax noprefix切换到 Intel 语法。示例intmain(){inta1,b2,result;asmvolatile(.intel_syntax noprefix\n\tmov eax, %1\n\tadd eax, %2\n\tmov %0, eax\n\t.att_syntax prefix\n\t:r(result):r(a),r(b):eax);returnresult;}注意使用 Intel 语法后寄存器不需要%前缀立即数也不需要$。最后需要切换回 ATT 语法以避免影响后续代码。十九、常见陷阱与注意事项19.1movsd的歧义在 Intel 语法中movsd是一条字符串指令复制一个双字4 字节。但在 ATT 语法中movsd是一条 SSE 指令移动一个标量双精度浮点8 字节。ATT 中字符串双字传送应使用movsl。这是新手最容易踩的坑。19.2 操作数顺序很多人在两种语法间切换时会忘记反转操作数顺序导致程序逻辑完全错误。19.3 立即数与内存地址在 ATT 中忘记$前缀会把立即数当作内存地址导致段错误。例如movl $100, %eax ; 正确eax 100 movl 100, %eax ; 错误eax *(int*)100很可能段错误19.4 指令后缀在 ATT 中如果忘记后缀汇编器会报错。例如mov %eax, %ebx是错误的必须写成movl %eax, %ebx。二十、总结对比表下面是一个全面的对比表方便随时查阅特性Intel 语法ATT 语法操作数顺序目的, 源源, 目的寄存器前缀无%立即数前缀无$指令大小后缀无由操作数决定b/w/l/q内存寻址[baseindex*scaledisp]disp(base,index,scale)字符串双字指令movsdmovsl间接跳转jmp eaxjmp *%eax注释;#段寄存器es:[bx]%es:(%bx)默认工具MASM、NASM、WindowsGAS、GCC、Linux二十一、结语两种语法一个世界汇编语言是计算机世界最底层的语言之一而 Intel 语法和 ATT 语法就像两种不同的方言各自承载着不同的历史和生态。理解它们不仅能让你在 Windows 和 Linux 之间自由切换更能加深你对 x86 架构、操作系统和编译原理的理解。下次当你看到movl $1, %eax时你会会心一笑这是 ATT 语法意思是把立即数 1 放入eax寄存器。而当你看到mov eax, 1时你也会知道这是 Intel 语法做的是同一件事。汇编语言并不神秘它只是机器指令的人类可读形式。而这两种语法只是让我们以不同的方式阅读同一种机器指令罢了。希望这个故事能让你在汇编的世界里走得更远、更轻松。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门