为什么在 Thumb 代码中前向引用 ADR 指令 assemble 具有偶数偏移量?

Why do forward reference ADR instructions assemble with even offsets in Thumb code?

bx到一个Thumb函数,需要设置地址的最低有效位。 GNU 作为文档 statesadr 伪指令生成地址时如何工作:

adr <register> <label>

This instruction will load the address of label into the indicated register. [...]

If label is a thumb function symbol, and thumb interworking has been enabled via the -mthumb-interwork option then the bottom bit of the value stored into register will be set. This allows the following sequence to work as expected:

adr r0, thumb_function

blx r0

所以听起来一切正常。但是,查看一些反汇编代码,似乎某些地址 没有 设置了最低位。

例如汇编和链接:

.syntax unified
.thumb

.align 2
table:
    .4byte f1
    .4byte f2
    .4byte f3

.align 2
.type f1, %function
.thumb_func
f1:
    adr r1, f1
    adr r2, f2
    adr r3, f3
    bx r1

.align 2
.type f2, %function
.thumb_func
f2:
    adr r1, f1
    adr r2, f2
    adr r3, f3
    bx r2

.align 2
.type f3, %function
.thumb_func
f3:
    adr r1, f1
    adr r2, f2
    adr r3, f3
    bx r3

有:

arm-none-eabi-as adr_test.s -mthumb -mthumb-interwork -o adr_test.o
arm-none-eabi-ld adr_test.o

并检查 arm-none-eabi-objdump -D a.out,我得到:

00008000 <table>:
    8000:   0000800d    .word   0x0000800d
    8004:   00008019    .word   0x00008019
    8008:   00008025    .word   0x00008025

0000800c <f1>:
    800c:   f2af 0103   subw    r1, pc, #3
    8010:   a201        add r2, pc, #4  ; (adr r2, 8018 <f2>)
    8012:   a304        add r3, pc, #16 ; (adr r3, 8024 <f3>)
    8014:   4708        bx  r1
    8016:   46c0        nop         ; (mov r8, r8)

00008018 <f2>:
    8018:   f2af 010f   subw    r1, pc, #15
    801c:   f2af 0207   subw    r2, pc, #7
    8020:   a300        add r3, pc, #0  ; (adr r3, 8024 <f3>)
    8022:   4710        bx  r2

00008024 <f3>:
    8024:   f2af 011b   subw    r1, pc, #27
    8028:   f2af 0213   subw    r2, pc, #19
    802c:   f2af 030b   subw    r3, pc, #11
    8030:   4718        bx  r3
    8032:   46c0        nop         ; (mov r8, r8)

有几点需要注意:

  1. table中,f1f2f3的绝对地址都是奇数,符合预期。所以,很明显,汇编器和链接器知道这三个函数应该是Thumb。
  2. 对于向后引用,其中 adr 伪指令汇编为 subw,偏移量是奇数,正如预期的那样。
  3. 但是对于前向引用,adr 伪指令汇编为 add,偏移量是偶数。

我错过了什么?

您缺少的是 ADR pseudo-instruction:

的 ARM 文档中的这一行

If you use ADR to generate a target for a BX or BLX instruction, it is your responsibility to set the Thumb bit (bit 0) of the address if the target contains Thumb instructions.

前向引用 ADR 指令使用 ADD 指令的 16 位 Thumb "ADD Rd, pc, #imm" 形式。该指令的立即数在 0-1020 范围内并且必须是字对齐的(即它用 8 位字段编码并乘以 4。)使用的 PC 值也将低两位设置为 0,因此它无法生成奇数地址。

强制汇编程序始终使用带有 ADR.W 的 32 位 Thumb 指令应该会导致它在使用函数标签时始终生成奇数地址,但我不知道你是否可以依赖这个。明确设置低位可能会更好。

回到这个问题。这个错误真的很简单:

  if (inst.relocs[0].exp.X_op == O_symbol
      && inst.relocs[0].exp.X_add_symbol != NULL
      && S_IS_DEFINED (inst.relocs[0].exp.X_add_symbol)
      && THUMB_IS_FUNC (inst.relocs[0].exp.X_add_symbol))
    inst.relocs[0].exp.X_add_number += 1;

在 do_t_adr() 函数中。

S_IS_DEFINED检查符号是否定义,此时做前向引用时符号未定义,所以那一行不通过,不加一个对于清洁度来说非常令人不安,它应该 ORR 一个,但无论如何。对于向后引用,定义符号以便进行调整。 (当然 THUMB_IS_FUNC 也不会在没有定义符号的情况下工作)

ADR 转换为BFD_RELOC_ARM_THUMB_ADD。这把我们带到了这里

case BFD_RELOC_ARM_THUMB_ADD:
  /* This is a complicated relocation, since we use it for all of
 the following immediate relocations:

    3bit ADD/SUB
    8bit ADD/SUB
    9bit ADD/SUB SP word-aligned
   10bit ADD PC/SP word-aligned

 The type of instruction being processed is encoded in the
 instruction field:

   0x8000  SUB
   0x00F0  Rd
   0x000F  Rs
  */

并在此处

else if (rs == REG_PC || rs == REG_SP)
  {
    /* PR gas/18541.  If the addition is for a defined symbol
       within range of an ADR instruction then accept it.  */

并且在稍后的传递中发生的代码(在定义符号并可以找到之后)不会修补 immediate/offset。

我更发现 disturbing/buggy 如果没有 .syntax 统一,它无法处理这个问题。

.thumb
.thumb_func
zero:
    adr r0,zero

即使使用统一的 .syntax,他们也没有完成 T16 的 ADR 实施。只需在其中输入一个错误即可完成。 (当然可以在 T16 中实现 add rx,pc,#0, sub rx,#offset 例如。)

即使他们修复了它,我也会避免使用 ADR 指令。但很明显,他们并没有真正完成这个伪指令的实现。

注意在 arm 模式下它们有相同的错误,在错误的时间检查符号。

  if (support_interwork
      && inst.relocs[0].exp.X_op == O_symbol
      && inst.relocs[0].exp.X_add_symbol != NULL
      && S_IS_DEFINED (inst.relocs[0].exp.X_add_symbol)
      && THUMB_IS_FUNC (inst.relocs[0].exp.X_add_symbol))
    inst.relocs[0].exp.X_add_number |= 1;

注意 better/different 作者的 ORR 而不是 ADD,但并没有完全考虑这个解决方案。

如果我删除 S_IS_DEFINED 和 THUMB_IS_FUNC 检查

.arm
zero:
    adr r0,two
.thumb
.thumb_func
two:
    nop

来自

00000000 <zero>:
   0:   e24f0004    sub r0, pc, #4

00000004 <two>:
   4:   46c0        nop         ; (mov r8, r8)
   6:   46c0        nop         ; (mov r8, r8)

00000000 <zero>:
   0:   e24f0003    sub r0, pc, #3

00000004 <two>:
   4:   46c0        nop         ; (mov r8, r8)
   6:   46c0        nop         ; (mov r8, r8)

同样

.syntax unified

.thumb
    adr r0,two
    nop
    nop
.thumb_func
two:
    nop

给予

00000000 <two-0x8>:
   0:   f20f 0005   addw    r0, pc, #5
   4:   46c0        nop         ; (mov r8, r8)
   6:   46c0        nop         ; (mov r8, r8)

00000008 <two>:
   8:   46c0        nop         ; (mov r8, r8)

请注意,这可以使用 T16 指令轻松实现(使用 4 个字节,就像 T32 解决方案一样),但正如提到的另一个错误:

.syntax unified
.cpu cortex-m0
.thumb
    adr r0,two
    nop
    nop
.thumb_func
two:
    nop

/path/so.s: Assembler messages:
/path/so.s:5: Error: invalid immediate for address calculation (value = 0x00000003)

(该错误与您指出的错误在同一段代码中)

首先看看其他汇编器的文档关于 ADR 和 thumb 的说明,然后看看他们是否根据该文档实际实现它会很有趣 and/or 出现错误或警告时退出。

This was a bug in the GNU Assembler (gas)。它应该在 v2.37 中修复。