设计AT&T汇编语法的最初原因是什么?

ant*_*one 7 x86 assembly intel att

在x86或amd64上使用汇编指令时,程序员可以使用"Intel"(即nasm编译器)或"AT&T"(即gas编译器)汇编语法."Intel"语法在Windows上更受欢迎,但"AT&T"在UNIX(类似)系统上更受欢迎.

但是英特尔和AMD手册,以及芯片创建者创建的手册都使用"英特尔"语法.

我想知道,"AT&T"语法设计背后的原始想法是什么?浮动处理器创建者使用的符号有什么好处?

fuz*_*fuz 14

UNIX很长一段时间是在PDP-11上开发的,这是一台来自DEC的16位计算机,它具有相当简单的指令集.几乎每条指令都有两个操作数,每个操作数可以有以下八种寻址模式之一,这里用MACRO 16汇编语言显示:

0n  Rn        register
1n  (Rn)      deferred
2n  (Rn)+     autoincrement
3n  @(Rn)+    autoincrement deferred
4n  -(Rn)     autodecrement
5n  @-(Rn)    autodecrement deferred
6n  X(Rn)     index
7n  @X(Rn)    index deferred
Run Code Online (Sandbox Code Playgroud)

可以通过巧妙地重用R7(程序计数器)上的一些寻址模式来编码中间地址和直接地址:

27  #imm      immediate
37  @#imm     absolute
67  addr      relative
77  @addr     relative deferred
Run Code Online (Sandbox Code Playgroud)

作为使用UNIX tty驱动@#控制字符,$代替#*@.

PDP11指令字中的第一个操作数是指源操作数,而第二个操作数是指目的地.这反映在汇编语言的操作数顺序中,即源,然后是目标.例如,操作码

011273
Run Code Online (Sandbox Code Playgroud)

指的是指令

mov (R2),R3
Run Code Online (Sandbox Code Playgroud)

它将指向的单词移动R2R3.

此语法适用于8086 CPU及其寻址模式:

mr0 X(bx,si)  bx + si indexed
mr1 X(bx,di)  bx + di indexed
mr2 X(bp,si)  bp + si indexed
mr3 X(bp,di)  bp + di indexed
mr4 X(si)     si indexed
mr5 X(di)     di indexed
mr6 X(bp)     bp indexed
mr7 X(bx)     bx indexed
3rR R         register
0r6 addr      direct
Run Code Online (Sandbox Code Playgroud)

其中m是0,如果没有索引,m是1,如果有一个字节的索引,m是2,如果有一个两字节索引和m为3,如果代替存储器操作数,使用一个寄存器.如果存在两个操作数,则另一个操作数始终是寄存器并以r数字编码.否则,r编码操作码的另外三位.

在该寻址方案中不可能使用中间体,所有采用立即数的指令都在其操作码中编码该事实.Immediates拼写$imm就像在PDP-11语法中一样.

虽然英特尔总是使用dst, src其汇编程序的操作数排序,但没有特别令人信服的理由来适应这种约定,并且编写UNIX汇编程序以使用src, dstPDP11中已知的操作数排序.

他们在实现8087浮点指令时与这种排序有一些不一致,可能是因为英特尔给出了非交换浮点指令的两个可能方向,这些指令与AT&T语法使用的操作数排序不匹配.

PDP11指令jmp(跳转)和jsr(跳转到子程序)跳转到其操作数的地址.因此,jmp foo将跳转到foojmp *foo会跳转到存储在变量的地址foo,类似于如何lea在8086的作品.

x86 jmpcall指令的语法被设计为好像这些指令在PDP11上工作一样,这就是jmp foo跳转到foojmp *foo跳转到地址值的原因foo,即使8086实际上没有延迟寻址.这具有在语法上区分直接跳转与间接跳转的优点和便利,而不需要$每个直接跳转目标的前缀,但逻辑上没有很多意义.

扩展语法以使用冒号指定段前缀:

seg:addr
Run Code Online (Sandbox Code Playgroud)

当引入80386时,该方案使用四部分通用寻址模式适应其新的SIB寻址模式:

disp(base,index,scale)
Run Code Online (Sandbox Code Playgroud)

其中disp是位移,base是基址寄存器,index索引寄存器scale是1,2,4或8,用于按索引寄存器中的一个来缩放索引寄存器.这等于Intel语法:

[disp+base+index*scale]
Run Code Online (Sandbox Code Playgroud)

PDP-11的另一个显着特点是大多数指令都有字节和字变体.您使用哪一个由操作码的后缀bw后缀表示,它直接切换操作码的第一位:

 010001   movw r0,r1
 110001   movb r0,r1
Run Code Online (Sandbox Code Playgroud)

这也适用于AT&T语法,因为大多数8086指令确实也可用于字节模式和字模式.后来80386和AMD K6引入了32位指令(后缀llong)和64位指令(后缀q为quad).

最后但并非最不重要的是,最初的惯例是使用下划线为C语言符号添加前缀(在Windows上仍然如此),因此您可以区分ax从寄存器命名的C函数ax.当Unix系统实验室开发出ELF二进制格式时,他们决定摆脱这种装饰.由于无法区分直接地址和寄存器,否则会%在每个寄存器中添加前缀:

mov direct,%eax # move memory at direct to %eax
Run Code Online (Sandbox Code Playgroud)

这就是我们今天获得AT&T语法的方式.

  • NASM 使用不同的设计:它防止您声明与寄存器名称冲突的符号,并要求使用 `extern foobar`、`foobar equ 123` 或作为标签声明所有符号。解析器可以使用表格来区分寄存器和符号(在某些情况下还可以使用助记符)。例如,`mov rdi, rxd` 将 rxd 视为一个符号,因为没有该名称的寄存器,并组合成一个 `mov r64, imm32`(或 `imm64`)。AT&T 语法使得拥有像 `eax` 这样的全局符号成为可能。AC 编译器很难将 `int eax = 0;` 编译为 NASM 语法。 (3认同)
  • 是的,这似乎不是一个好的设计。它从来没有被设计成一个好的编译器输出格式(因为你不能编译 `int eax=0;`),尽管我认为 NASM 语法是在 `a.out` 天设计的,在 Linux 切换到 ELF 之前,所以也许问题没有预见到。我问了 /sf/ask/3212345181/ 因为我对答案很好奇。我没有想到 ABI 兼容性使它比搜索/替换符号名称更难,但这是一个很好的观点。 (2认同)
  • @BeeOnRope:哦,原来NASM让你写`$ eax`来引用符号`eax`.罗斯回答了我的问题:) (2认同)
  • 我想NASM至少在这方面是无辜的"可怕的设计".现在,如果他们只修复他们的DWARF信息生成. (2认同)