如何在ARM汇编中编写基于PC的相对寻址?

7

我正在使用GNU AS汇编器,试图编写一个位置无关的二进制文件,但在编写外部符号的PC相对地址时遇到了一些问题。通常情况下,可以使用以下代码加载相对地址:

adr r0, symbol

但是这仅适用于在汇编文件中定义在同一节中的符号。加载符号的另一种方法是

ldr r0, =symbol

该代码将符号的绝对地址存储在常量中,并从那里以PC相对方式加载它。 因此,您可以获得以下代码:

  48:   e59f0008        ldr     r0, [pc, #8]    ; 58 <text+0xc>
  ...
  58:   00000008        .word   0x00200018
                        58: R_ARM_ABS32 symbol

但我需要的是R_ARM_REL32链接器引用。

我想到的代码是:

tmp1:    ldr    r0, [pc, #tmp2 - tmp1 - 8]
         ldr    r0, [pc, r0]
tmp2:    .word  symbol - tmp1

这将产生以下结果:
0000003c <tmp1>:
  3c:   e59f0000        ldr     r0, [pc]        ; 44 <tmp2>
  40:   e79f0000        ldr     r0, [pc, r0]

00000044 <tmp2>:
  44:   00000008        .word   0x00000008
                        44: R_ARM_REL32 symbol

我几乎可以将它放入宏中,以便于我需要的任何符号重用。但是,我不知道如何告诉汇编程序tmp2需要进入常量块而不是出现在代码中间。
但是,在GNU AS中是否已经存在某些现有的语法或宏来处理这个问题呢?

你尝试过adrl吗?这应该允许更大的引用,并且您可以使用-fwhole-program。我认为在'C'中,如果没有某种重定位(如plt、glt等),您无法做到这一点。通常,'C'认为您可以像访问全局变量等一样做事情。对于ARM ABI,*.text.data*可能相距很远,编译器需要支持此功能。还有这个问题。话虽如此,u-boot使用'sb'来实现这一点。 - artless noise
boot.S:49: 错误:符号符号在不同的段中 - Goswin von Brederlow
一个 R_ARM_REL32 最终将被列在 .dyn.rel 部分(使用我的链接器标志)。在开始时,必须确定代码所在位置和应该在哪里之间的偏移量,然后将该偏移量添加到 .dyn.rel 中列出的所有地址中以修复问题。这个部分可能会相距很远,因此需要额外的 32 位值,并且 immediates 不起作用。 - Goswin von Brederlow
3个回答

1

ARMv8

请注意,在ARMv8中,PC不能像ARMv7那样被视为常规寄存器。

但是,对于ldr,有一种特殊的基于PC相对的编码称为“LDR(文字)”,其语法如下:

    ldr x0, pc_relative_ldr
    b 1f
pc_relative_ldr:
    .quad 0x123456789ABCDEF0
1:

GitHub upstream

尝试使用旧的“LDR(寄存器)”语法,如:

ldr x0, [pc]

失败并显示如下错误:

64-bit integer or SP register expected at operand 2 -- `ldr x0,[pc]'

由于某些原因,str 没有相应的 PC 相关编码。我认为您只需使用 adr + "STR(寄存器)",例如:

    adr x1, pc_relative_str
    ldr x0, pc_relative_ldr
    str x0, [x1]
    ldr x0, pc_relative_str
.data
pc_relative_str:
    .quad 0x0000000000000000

GitHub upstream

adr 会将 PC 相关地址加载到寄存器中。

如果您需要更长的跳转,您可能还会对 ADRP 感兴趣,请参见:ARM 汇编语言中 ADRP 和 ADRL 指令的语义是什么?


0
不要明确地定义这个词。可以考虑这样写:
  .text                                                                          
1:  ldr   r0, =symbol-1b                                                         
    ldr   r0, [pc, r0]
这会得到类似的结果:
[~] dev% ~/ellcc/bin/ecc-objdump -d test.o
test.o:     file format elf32-bigarm
Disassembly of section .text:
00000000 <.text>:
   0:   e59f0000        ldr     r0, [pc]        ; 8 <.text+0x8>
   4:   e79f0000        ldr     r0, [pc, r0]compatable
   8:   00000008        .word   0x00000008

使用正确的重定位:

RELOCATION RECORDS FOR [.text]:                                                  
OFFSET   TYPE              VALUE 
00000008 R_ARM_REL32       symbol

重新定位的单词将被放置在您的代码之后的某个位置。

注意:我使用clang的集成汇编器组装了这个。我假设GNU也会处理相同的事情,因为他们试图实现bug对bug兼容。

[编辑] 不幸的是,正如Jester在评论中提到的那样,这在GNU as中不起作用。我以前从未见过clang的集成汇编器做了GNU汇编器无法处理的事情的例子。


很不幸,GNU的as不喜欢它,它抱怨它不是一个常量。我的版本是2.18.50。我也想建议这个... - Jester
@Jester:你说得没错。我刚用我的2.25 GNU汇编器试了一下。 - Richard Pennington
这是我尝试的第一件事。让我惊讶的是它不起作用。 - Goswin von Brederlow
1
已经为binutils开了一个关于此问题的bug:https://sourceware.org/bugzilla/show_bug.cgi?id=18009 - Goswin von Brederlow

-1
你需要做的是将一个地址通过movwmovt指令加载到寄存器中,然后相对于PC使用ldr指令。挑战在于所加载的地址需要进行预校正以便进行PC相对访问。你可以通过在传递给mov指令的常量中进行一些指针运算来实现这一点,方法是从要加载的符号的绝对地址中减去ldr指令的地址(加上一个PC校正因子)。
由于我面前没有汇编器,具体实现留给读者自行练习。

那么,没有纠正术语的情况下是这样的吗?tmp3: movw r0, #((symbol - tmp3) & 0xFFFF); movt r0, #((symbol - tmp3) >> 16)。这将产生以下结果:boot.S:49: Error: constant expression expected -- 'movw r0,#((symbol-tmp3)&0xFFFF)'boot.S:50: Error: constant expression expected -- 'movt r0,#((symbol-tmp3)>>16)',“symbol”不是常量,因为它只有在后面由链接器确定。 这限制了您可以使用的数学量。 - Goswin von Brederlow

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接