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

What was the original reason for the design of AT&T assembly syntax?

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

但是 Intel 和 AMD 的手册,即芯片的创建者创建的手册,都使用 "Intel" 语法。

我想知道 "AT&T" 语法设计背后的最初想法是什么?摆脱处理器创建者使用的符号有什么好处?

UNIX 长期以来是在 PDP-11 上开发的,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

立即数和直接地址可以巧妙地编码re-using R7上的一些寻址方式,程序计数器:

27  #imm      immediate
37  @#imm     absolute
67  addr      relative
77  @addr     relative deferred

由于 UNIX tty 驱动程序使用 @# 作为控制字符,$ 被替换为 # 并且 * 被替换为 @.

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

011273

指的是指令

mov (R2),R3

R2 指向的单词移动到 R3.

此语法适用于 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

其中m如果没有索引则为0,如果有one-byte索引则m为1,如果有[=]则m为2 104=] 索引和 m 是 3 如果使用寄存器而不是内存操作数。如果存在两个操作数,则另一个操作数始终是寄存器并以 r 数字编码。否则,r 对操作码的另外三位进行编码。

立即数在此寻址方案中是不可能的,所有采用立即数的指令都在其操作码中对该事实进行编码。立即数的拼写 $imm 就像在 PDP-11 语法中一样。

虽然 Intel 总是为其汇编程序使用 dst, src 操作数排序,但没有特别令人信服的理由来适应这一约定,并且 UNIX 汇编程序被编写为使用已知的 src, dst 操作数排序PDP11.

他们在 8087 浮点指令的实现中与此顺序有些不一致,可能是因为 Intel 给了 non-commutative 浮点指令的两个可能方向不同的助记符,这与使用的操作数顺序不匹配AT&T 的语法。

PDP11 指令jmp(跳转)和jsr(跳转到子程序)跳转到其操作数的地址。因此,jmp foo 会跳转到 foojmp *foo 会跳转到存储在变量 foo 中的地址,类似于 lea 在 8086 中的工作方式。

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

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

seg:addr

引入 80386 时,此方案使用 four-part 通用寻址模式适应其新的 SIB 寻址模式:

disp(base,index,scale)

其中 disp 是位移,base 是基址寄存器,index 是变址寄存器,scale 是 1、2、4 或 8 以缩放变址寄存器这些金额之一。这等于英特尔语法:

[disp+base+index*scale]

PDP-11 的另一个显着特点是大多数指令都以字节和字的形式提供。您使用哪个由操作码的 bw 后缀指示,它直接切换操作码的第一位:

 010001   movw r0,r1
 110001   movb r0,r1

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

最后但同样重要的是,最初的约定是在 C 语言符号前加上下划线(在 Windows 上仍然如此),这样您就可以将名为 ax 的 C 函数与寄存器区分开来ax。当 Unix 系统实验室开发 ELF 二进制格式时,他们决定去掉这个装饰。由于无法区分直接地址和寄存器,因此每个寄存器都添加了 % 前缀:

mov direct,%eax # move memory at direct to %eax

这就是我们今天的 AT&T 语法的来源。

汇编语言是由汇编程序定义的,汇编程序是对汇编语言进行解析的软件。唯一的 "standard" 是机器代码,它必须与处理器相匹配,但是如果你让 100 个程序员给他们机器代码标准(没有任何汇编语言提示),你最终会得到 1 到 100 个不同的地方汇编语言。对于该处理器的所有用例(裸机、操作系统、应用程序工作),只要它们制作出适合工具链的完整工具,它们都将完美运行。

创建描述指令集的文档和汇编程序(您需要的第一个工具)符合指令集(机器代码)的创建者的最大利益。他们可以将它外包或自己制作,无论哪种方式都没有关系,但是拥有一个汇编器、一个语法和一个机器代码文档,它使用汇编器的语法来连接两者之间的点,这将给任何人可能对该处理器感兴趣的一个起点。就像英特尔和 8086/88 的情况一样。但这并不意味着 masm 和 tasm 与 intels 汇编程序完全兼容。即使每个指令的语法匹配,每个指令的语法也只是汇编语言的一部分,还有很多非指令类型的语法、指令、宏语言等。那是来自世界末日的 DOS,有UNIX 端以及 AT&T。当时的 gnu 人是 unix 的世界末日,所以他们使用 AT&T 语法或派生语法是完全合理的,因为他们通常在移植过程中搞乱汇编语言。也许有例外。

nasm 和其他类似的人试图继续使用 masm 语法,因为 masm 是一个封闭源代码的 Microsoft 工具(tasm 也是如此,如果 Borland C 也不是 tasm 的话)。这些现在可能是开源的,但不需要,从头开始编写一个比尝试移植该代码更容易,我假设是用现代编译器构建的,并且 nasm 已经存在。

为什么问题就像问我为什么选择今天早上或任何特定日子选择的那双袜子。你的袜子可能不会对世界其他地方产生那么大的影响,但这个问题同样无关紧要 and/or 无法回答。答案部分可以追溯到要求 100 名程序员为相同的机器代码定义制作一个汇编程序。这些程序员中的一些人可能对汇编语言很有经验,并且可能会选择按照他们以前使用过的语言的形象来创建一种汇编语言,这意味着他们中的一些人会做出一个看起来非常相似的语言。但是他们之前使用的一个或多个可能不同,因此会有这些相似但仍然不同的组。然后假设 30 年问这 100 个人中的每一个人为什么问题......如果他们还活着......就像问我为什么你选择在 30 年前以你所做的方式编写的程序中声明变量它。