为什么 GNU 作为 x86 和 ARM 之间的语法不同?

Why is GNU as syntax different between x86 and ARM?

我刚开始学习 ARM 汇编,我不明白为什么 GNU as 语法与 x86* 的语法不同。

由于指令相同,我希望除了指令本身之外的一切都像 x86*,但相反,我正在努力加载字符串的地址等。我从头开始通过在线阅读一些 PDF,man 2 syscall 和反编译基本示例,因为我不确定我可以在网上找到的各种 Hello World 的价值。

我的问题:

一切都归结为相同的mov r0, #1:

        mov %r0, 
   10080:       e3a00001        mov     r0, #1
        mov r0, 
   10084:       e3a00001        mov     r0, #1
        mov %r0, #1
   10088:       e3a00001        mov     r0, #1
        mov r0, #1
   1008c:       e3a00001        mov     r0, #1

.section .text
hello:
        .asciz "Hello World\n"
        .set hello_len, .-hello

hello_addr:
        .word hello

.align 4
.global _start
_start:
        mov r0, 
        ldr r1, hello_addr
        mov r2, $hello_len
        mov r7, 
        swi [=11=]

        mov r0, [=11=]
        mov r7, 
        swi [=11=]

GNU Assembler (GAS) 使用 AT&T 语法进行 x86 汇编的原因是为了与 AT&T 的 x86 汇编程序兼容。 AT&T 没有使用基于英特尔官方 x86 汇编语法的语法,而是选择基于其早期的 68000 和 PDP-11 汇编器创建新语法。当将 x86 支持添加到 GNU 编译器 (GCC) 时,它会生成 AT&T 语法汇编,因为那是他们使用的汇编器。在此之后的某个时间创建 GAS 时,GNU 汇编程序必须使用该语法。

然而,没有用于 ARM CPUs 的 AT&T 汇编器版本。当 GNU 项目开始将 GCC 和 GAS 移植到 ARM 目标时,没有理由为 ARM 汇编创建他们自己的新的和不兼容的语法。相反,他们基于 ARM 的官方语法使用的语法。这意味着您可以在 ARM 的官方文档中查找 ARM 指令,并使用您在 GNU 汇编程序中看到的语法和操作数顺序。在使用 AT&T 语法编写 x86 程序集时,您只需了解规则和例外情况,这些规则和例外情况在任何地方都没有正式记录。

在ARM汇编中不能将地址直接加载到寄存器中的原因不是语法问题。 ARM CPU 根本没有可以做到这一点的指令。所有 ARM 指令的大小都相同,均为 32 位,因此没有空间将 32 位地址编码为立即操作数。然而,ARM 汇编程序确实提供了一个 pseudo-instruction form of LDR 可以处理自动加载 32 位地址和常量:ldr r1, =hello。这将导致汇编程序将 32 位常量存储在文字 table 中,并使用 PC 相关的 LDR 指令将其加载到内存中。如果加载的常量恰好足够小,可以直接使用 MOV 或 MVN 加载,则生成该指令。

你不能把常量放在.rodata中的原因要么是因为它太远了,无法使用PC相关的LDR指令进行寻址(它需要在+/-4KB内,因为最大的位移无法放入单个 32 位 ARM 指令)或您使用的对象格式不支持对不同部分的 PC 相对寻址。 (您的 ldr r1, hello_addr 指令使用 PC 相对寻址,因为无法在 ARM 指令中编码 32 位地址。)

汇编语言是由汇编器定义的,汇编器是解析它的程序。创建或创建汇编程序符合处理器供应商(IP 或芯片)的最大利益。记录机器语言也符合他们的最大利益,因此他们将机器语言与他们创建或签订的汇编语言相匹配,以便这些项目一起工作。汇编语言绝不是适用于所有平台的通用语言,没有理由假设对于同一目标,不同的汇编器会使用相同的汇编语言,最著名的是 AT&T 与 intel x86 的悲惨结果。英特尔本可以做得更好,但它是 CISC 并且在当时是有意义的(mov 指令如此重载,但汇编语言仍然可以更干净一些,请记住我们已经有几十年的经验了).

据我所知,GNU 在添加目标时总是会破坏目标存在的汇编语言,因此他们会为该目标创建新的汇编语言。也许故意不兼容,有时关闭,但仍然足以不兼容。同样,有一些指令适用于 gnu 汇编器汇编语言,但也存在差异。现实情况是,不是 "GNU" 而是个人或团队选择为该目标创建该端口,他们做任何他们想做的事,这就是汇编语言的本质。

如果你在 ARM 之前学习 x86 我真的很同情你,我真的希望 x86 不是你的第一个汇编语言。从历史上看,百分号寄存器不是 x86 的东西,有人觉得他们需要添加它真的有点可悲,而当时许多汇编程序已经编写表明不需要这样的东西。 ARM 汇编语言,无论是 GNU 还是众多 ARM 汇编程序中的一种,都是目前最干净的汇编语言之一,最有意义,最不模糊。

重要的是机器代码,机器代码是您必须符合该目标的标准,而不是汇编语言。你能不能把机器码写出来,汇编语言能不能做的各不相同,这就是汇编语言的本质。与 AT&T 和完成单个 GNU 目标端口的人们一样,当然欢迎您编写自己的汇编器和汇编语言,如果您使用通用文件格式作为对象输出(在 ARM 的情况下为 elf),那么您可以使用汇编器编写汇编语言,然后 link 使用 C 或其他使用 GNU 工具的语言。没有人会阻止你这样做,这是学习指令集的一个很好的方法,我更喜欢写一个反汇编程序或一个指令集模拟器,而不是写一个汇编程序(大约是一个周末任务,也许还有几个工作日晚上进行微调)也会做得很好

人们可以很容易地抱怨 x86 GNU 汇编语言看起来不像 arm 或 mips,填空。不是很相关,原因很明显。 Ssemi-portable 与 gnu 端口之前的文档或工具。这本身就是为什么甚至完全使用 gnu 汇编程序的原因......如果 arm 后端是按照其他一些处理器常见的语法设计的,那么有人会制作一个备用端口。另请注意,在 gnu 世界中发生了令人不安的武器组装混乱,也许您应该赶上这股潮流……

回答你的实际问题,因为你确实有实际问题。这些是 x86 和 arm 完全不同的指令集。 CISC 与 RISC,您不能拥有固定大小的指令并适合您想要的任何大小。立即数有规则(请阅读 ARM 文档以了解您尝试使用的说明)否则您必须执行 pc 相对负载,并且 pc 相对负载可以达到的距离是有限的,您可能从一些 x86 指令中理解影响范围有限。至此各种汇编器给了我们一个伪代码的解决方案:

ldr r0,=0x00110000
ldr r0,=0x12345678
ldr r0,=mylabel
ldr r0,mylabeladd
ldr r0,myvalue
b .

mylabeladd: .word mylabel
mylabel: .word 1,2,3,4
myvalue: .word 0x11223344

给予

00000000 <mylabeladd-0x18>:
   0:   e3a00811    mov r0, #1114112    ; 0x110000
   4:   e59f0024    ldr r0, [pc, #36]   ; 30 <myvalue+0x4>
   8:   e59f0024    ldr r0, [pc, #36]   ; 34 <myvalue+0x8>
   c:   e59f0004    ldr r0, [pc, #4]    ; 18 <mylabeladd>
  10:   e59f0014    ldr r0, [pc, #20]   ; 2c <myvalue>
  14:   eafffffe    b   14 <mylabeladd-0x4>

00000018 <mylabeladd>:
  18:   0000001c    andeq   r0, r0, r12, lsl r0

0000001c <mylabel>:
  1c:   00000001    andeq   r0, r0, r1
  20:   00000002    andeq   r0, r0, r2
  24:   00000003    andeq   r0, r0, r3
  28:   00000004    andeq   r0, r0, r4

0000002c <myvalue>:
  2c:   11223344            ; <UNDEFINED> instruction: 0x11223344
  30:   12345678    eorsne  r5, r4, #120, 12    ; 0x7800000
  34:   0000001c    andeq   r0, r0, r12, lsl r0

如果他们不适合它或者如果它是一个标签,他们会为您创造价值(在 .text 中,因为您不能假设您可以到达任何其他部分)。如果他们可以为您创建一个 mov(至少 GAS 可以)。

或者您可以像在 mylabeladd

中那样自己制作 pc 相对负载

如果您想访问任何其他部分,则必须正确执行:

.globl _start
_start:

mov r3,#1
ldr r0,=mydata
str r3,[r0]
ldr r1,mydataadd
str r3,[r1]
b .
mydataadd: .word mydata
.data
mydata: .word 0

在 linked

时给予
00001000 <_start>:
    1000:   e3a03001    mov r3, #1
    1004:   e59f0010    ldr r0, [pc, #16]   ; 101c <mydataadd+0x4>
    1008:   e5803000    str r3, [r0]
    100c:   e59f1004    ldr r1, [pc, #4]    ; 1018 <mydataadd>
    1010:   e5813000    str r3, [r1]
    1014:   eafffffe    b   1014 <_start+0x14>

00001018 <mydataadd>:
    1018:   80000000    andhi   r0, r0, r0
    101c:   80000000    andhi   r0, r0, r0

Disassembly of section .data:

80000000 <__data_start>:
80000000:   00000000    andeq   r0, r0, r0

你必须为外部标签做同样的事情,但对于分支等,它在同一个 .text 部分,linker 会尽力帮助你。

.globl _start
_start:

b fun

在另一个文件中

.globl fun
fun:
    b .

不足为奇...

00000000 <_开始>: 0: eaffffff b 4

00000004 : 4: eafffffe b 4

但是如果

.thumb
.thumb_func
.globl fun
fun:
    b .

谢谢 gnu!

00000000 <_start>:
   0:   ea000000    b   8 <__fun_from_arm>

00000004 <fun>:
   4:   e7fe        b.n 4 <fun>
    ...

00000008 <__fun_from_arm>:
   8:   e59fc000    ldr r12, [pc]   ; 10 <__fun_from_arm+0x8>
   c:   e12fff1c    bx  r12
  10:   00000005    andeq   r0, r0, r5
  14:   00000000    andeq   r0, r0, r0

或者模拟一个非常大的程序

.globl _start
_start:

b fun

.space 0x10000000

感叹:

arm-none-eabi-ld -Ttext=0 so.o x.o -o so.elf
so.o: In function `_start':
(.text+0x0): relocation truncated to fit: R_ARM_JUMP24 against symbol `fun' defined in .text section in x.o

那么就像跨栏一样

.globl _start
_start:

ldr r0,=fun
bx fun
.ltorg
.space 0x10000000

那行得通...

00000000 <_start>:
       0:   e51f0000    ldr r0, [pc, #-0]   ; 8 <_start+0x8>
       4:   e12fff10    bx  r0
       8:   1000000d    andne   r0, r0, sp
    ...

1000000c <fun>:
1000000c:   e7fe        b.n 1000000c <fun>

但你必须确保 link 人正在帮助你,因为它可能不会帮助你,而且从手臂到拇指的蹦床也不总是在那里......

.globl _start
_start:

    b fun

.globl more_fun
more_fun:
    b .

其他文件

.thumb
.thumb_func
.globl fun
fun:
    b more_fun

生成完全损坏的代码。

00000000 <_start>:
   0:   ea000002    b   10 <__fun_from_arm>

00000004 <more_fun>:
   4:   eafffffe    b   4 <more_fun>

00000008 <fun>:
   8:   e7fc        b.n 4 <more_fun>
   a:   0000        movs    r0, r0
   c:   0000        movs    r0, r0
    ...

00000010 <__fun_from_arm>:
  10:   e59fc000    ldr r12, [pc]   ; 18 <__fun_from_arm+0x8>
  14:   e12fff1c    bx  r12
  18:   00000009    andeq   r0, r0, r9
  1c:   00000000    andeq   r0, r0, r0

现在如果我使用了更多可能有效的 gnu 特定语法...

.globl _start
_start:

    b fun

void more_fun ( void )
{
    return;
}

不,猜不到

00000000 <_start>:
   0:   ea000002    b   10 <__fun_from_arm>

00000004 <more_fun>:
   4:   e12fff1e    bx  lr

00000008 <fun>:
   8:   e7fc        b.n 4 <more_fun>
   a:   0000        movs    r0, r0
   c:   0000        movs    r0, r0
    ...

00000010 <__fun_from_arm>:
  10:   e59fc000    ldr r12, [pc]   ; 18 <__fun_from_arm+0x8>
  14:   e12fff1c    bx  r12
  18:   00000009    andeq   r0, r0, r9
  1c:   00000000    andeq   r0, r0, r0

虽然所有的乐趣都...很明显,您正在处理不同的指令集 x86、arm、mips、avr、msp430、pdp11、xtensa、risc-v 和其他 gnu 支持的目标。一旦你学会了一种或两种或三种汇编语言,其余的相似多于不同,语法就是语法,很容易超越,真正的问题是你能用那个指令集做什么或不能做什么。答案通常在该供应商的文档中(而不仅仅是您搜索的一些指令集参考)