为什么在 Thumb 代码中前向引用 ADR 指令 assemble 具有偶数偏移量?
Why do forward reference ADR instructions assemble with even offsets in Thumb code?
到bx
到一个Thumb函数,需要设置地址的最低有效位。 GNU 作为文档 states 从 adr
伪指令生成地址时如何工作:
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)
有几点需要注意:
- 在
table
中,f1
、f2
、f3
的绝对地址都是奇数,符合预期。所以,很明显,汇编器和链接器知道这三个函数应该是Thumb。
- 对于向后引用,其中
adr
伪指令汇编为 subw
,偏移量是奇数,正如预期的那样。
- 但是对于前向引用,
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 中修复。
到bx
到一个Thumb函数,需要设置地址的最低有效位。 GNU 作为文档 states 从 adr
伪指令生成地址时如何工作:
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)
有几点需要注意:
- 在
table
中,f1
、f2
、f3
的绝对地址都是奇数,符合预期。所以,很明显,汇编器和链接器知道这三个函数应该是Thumb。 - 对于向后引用,其中
adr
伪指令汇编为subw
,偏移量是奇数,正如预期的那样。 - 但是对于前向引用,
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 中修复。