【问题标题】:Why is GNU as syntax different between x86 and ARM?为什么 GNU 作为 x86 和 ARM 的语法不同?
【发布时间】:2017-09-20 07:30:39
【问题描述】:

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

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

我的问题:

  • 寄存器不需要% sigil
  • 整数常量可以有#$ 符号。事实上,如果我编译 mov r0, $0objdump -D 会给我一个 mov r0, #1

一切都归结为同一个mov r0, #1

        mov %r0, $1
   10080:       e3a00001        mov     r0, #1
        mov r0, $1
   10084:       e3a00001        mov     r0, #1
        mov %r0, #1
   10088:       e3a00001        mov     r0, #1
        mov r0, #1
   1008c:       e3a00001        mov     r0, #1
  • 我无法直接使用标签的地址来加载字符串地址,所以我需要为此使用一个变量。 mov r1, $helloldr r1, $hello 不起作用。在 x86_64 中,我会写成 mov $hello, %rsi。所以我正在做 gcc 所做的事情,我正在使用另一个标签的地址创建一个单词。

  • 我无法输入我的常量.rodata 或者我得到一个Error: internal_relocation (type: OFFSET_IMM) not fixed up,但将所有内容都放入.text 有效(这部分与语法无关)


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

hello_addr:
        .word hello

.align 4
.global _start
_start:
        mov r0, $1
        ldr r1, hello_addr
        mov r2, $hello_len
        mov r7, $4
        swi $0

        mov r0, $0
        mov r7, $1
        swi $0

【问题讨论】:

  • @CodyGray 或者 AT&T 语法对于 x86 来说可能很棒,由于寄存器内存操作的歧义,它 应该 有明确的长度后缀,而 ARM 不需要它?另外,对于 OP:查看gas 文档。
  • @EOF 我正在努力处理气体文档,因为它不能作为单页提供。所以搜索非常繁琐。 sourceware.org/binutils/docs-2.28/as/index.html 。但实际上,我可以看到某些语法是 ARM 特定的,我不明白为什么 sourceware.org/binutils/docs-2.28/as/…
  • 而且文档是空的sourceware.org/binutils/docs-2.28/as/… "TODO解释一下ARM寄存器命名,以及预定义的名字。"
  • 我知道我的问题可能很糟糕,但我发现一个工具有这么多语法sourceware.org/binutils/docs-2.28/as/…
  • @Benoît 在 ARM 上,寄存器和符号可以与立即操作数混淆的唯一地方,使用 $ 前缀区分。因此不需要寄存器前缀。

标签: assembly arm gnu-assembler att


【解决方案1】:

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

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

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

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

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

回答您的实际问题,因为您确实有实际问题。这些是完全不同的指令集 x86 和 arm。 CISC vs 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

链接时给予

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 部分中的分支等,链接器会尝试帮助您。

.globl _start
_start:

b fun

在另一个文件中

.globl fun
fun:
    b .

并不奇怪......

00000000 <_>: 0: eaffffff b 4

00000004: 4: eaffffe b 4

如果

.thumb
.thumb_func
.globl fun
fun:
    b .

谢谢你!

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>

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

.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 支持的目标。一旦您学习了一种或两种或三种汇编语言,其余的则相似多于不同,语法就是语法,易于超越,真正的问题是您可以使用该指令集做什么或不做什么。答案通常在该供应商的文档中(不仅仅是您搜索的一些指令集参考)

【讨论】:

    【解决方案2】:

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

    但是,没有用于 ARM CPU 的 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 位常量存储在文字表中,并使用 PC 相关 LDR 指令将其加载到内存中。如果加载的常量恰好足够小,可以直接使用 MOV 或 MVN 加载,则生成该指令。

    您不能将常量放在.rodata 中的原因要么是因为它太远而无法使用 PC 相关 LDR 指令来解决(它需要在 +/-4KB 中,因为最大的位移超出了可以容纳的范围一条 32 位 ARM 指令)或您使用的对象格式不支持 PC 相对寻址到不同的部分。 (您的 ldr r1, hello_addr 指令使用 PC 相对寻址,因为无法在 ARM 指令中编码 32 位地址。)

    【讨论】:

      猜你喜欢
      • 2013-11-21
      • 1970-01-01
      • 1970-01-01
      • 2018-01-05
      • 1970-01-01
      • 2014-04-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多