4

我刚刚阅读了https://www.keil.com/support/man/docs/armlink/armlink_pge1406301797482.htm。但无法理解 arm 链接器在函数调用之间插入的单板是什么。

在“ARM 体系结构的过程调用标准”文档中,它说,

5.3.1.1 链接器对 IP 的使用 ARM 和 Thumb 状态的 BL 指令都无法寻址完整的 32 位地址空间,因此链接器可能需要在调用例程和被调用例程之间插入胶合代码子程序。可能还需要单板来支持 ARM-Thumb 互通或动态链接。插入的任何单板都必须保留除 IP (r12) 和条件代码标志之外的所有寄存器的内容;符合要求的程序必须假定可以在任何暴露于支持互通或长分支的重定位的分支指令中插入更改 IP 的胶合代码。注意 R_ARM_CALL、R_ARM_JUMP24、R_ARM_PC24、R_ARM_THM_CALL、R_ARM_THM_JUMP24 和 R_ARM_THM_JUMP19 是具有此属性的 ELF 重定位类型的示例。详情请参阅 [AAELF]

这是我的猜测,是这样的吗?:当函数 A 调用函数 B 时,当这两个函数相距太远而无法用bl命令表达时,链接器会在函数 A 和 B 之间插入函数 C,使函数 C 接近函数 B。现在函数 A 使用b指令转到函数 C(在函数调用之间复制所有寄存器),函数 C 使用bl指令(也复制所有寄存器)。当然r12寄存器是用来保存剩余的长跳转地址位的。这就是贴面的意思吗?(我不知道为什么 arm 不解释单板是什么,而只解释单板提供什么..)

4

2 回答 2

3

这只是一个蹦床。交互工作更容易演示,在这里使用 gnu,但这意味着 Kiel 也有一个解决方案。

.globl even_more
.type eve_more,%function
even_more:
    bx lr

.thumb

.globl more_fun
.thumb_func
more_fun:
    bx lr



extern unsigned int more_fun ( unsigned int x );
extern unsigned int even_more ( unsigned int x );
unsigned int fun ( unsigned int a )
{
    return(more_fun(a)+even_more(a));
}
    
Unlinked object:

Disassembly of section .text:

00000000 <fun>:
   0:   e92d4070    push    {r4, r5, r6, lr}
   4:   e1a05000    mov r5, r0
   8:   ebfffffe    bl  0 <more_fun>
   c:   e1a04000    mov r4, r0
  10:   e1a00005    mov r0, r5
  14:   ebfffffe    bl  0 <even_more>
  18:   e0840000    add r0, r4, r0
  1c:   e8bd4070    pop {r4, r5, r6, lr}
  20:   e12fff1e    bx  lr

Linked binary (yes completely unusable, but demonstrates what the tool does)

Disassembly of section .text:

00001000 <fun>:
    1000:   e92d4070    push    {r4, r5, r6, lr}
    1004:   e1a05000    mov r5, r0
    1008:   eb000008    bl  1030 <__more_fun_from_arm>
    100c:   e1a04000    mov r4, r0
    1010:   e1a00005    mov r0, r5
    1014:   eb000002    bl  1024 <even_more>
    1018:   e0840000    add r0, r4, r0
    101c:   e8bd4070    pop {r4, r5, r6, lr}
    1020:   e12fff1e    bx  lr

00001024 <even_more>:
    1024:   e12fff1e    bx  lr

00001028 <more_fun>:
    1028:   4770        bx  lr
    102a:   46c0        nop         ; (mov r8, r8)
    102c:   0000        movs    r0, r0
    ...

00001030 <__more_fun_from_arm>:
    1030:   e59fc000    ldr r12, [pc]   ; 1038 <__more_fun_from_arm+0x8>
    1034:   e12fff1c    bx  r12
    1038:   00001029    .word   0x00001029
    103c:   00000000    .word   0x00000000

您不能使用 bl 在 arm 和 thumb 之间切换模式,因此链接器添加了一个蹦床,正如我所说的那样,或者听到它叫你跳上跳下到达目的地。在这种情况下,本质上是将 bl 的分支部分转换为 bx,他们利用的链接部分仅使用 bl。您可以看到拇指到手臂或手臂到拇指的情况。

even_more 函数处于相同模式 (ARM),因此不需要蹦床/单板。

有关 bl lemme 的距离限制,请参阅。哇,这很容易,gnu 也称它为 veneer:

.globl more_fun
.type more_fun,%function
more_fun:
    bx lr

extern unsigned int more_fun ( unsigned int x );
unsigned int fun ( unsigned int a )
{
    return(more_fun(a)+1);
}

MEMORY
{
    bob : ORIGIN = 0x00000000, LENGTH = 0x1000
    ted : ORIGIN = 0x20000000, LENGTH = 0x1000
}
SECTIONS
{
    .some   : { so.o(.text*)       } > bob
    .more   : { more.o(.text*)      } > ted
}

Disassembly of section .some:

00000000 <fun>:
   0:   e92d4010    push    {r4, lr}
   4:   eb000003    bl  18 <__more_fun_veneer>
   8:   e8bd4010    pop {r4, lr}
   c:   e2800001    add r0, r0, #1
  10:   e12fff1e    bx  lr
  14:   00000000    andeq   r0, r0, r0

00000018 <__more_fun_veneer>:
  18:   e51ff004    ldr pc, [pc, #-4]   ; 1c <__more_fun_veneer+0x4>
  1c:   20000000    .word   0x20000000

Disassembly of section .more:

20000000 <more_fun>:
20000000:   e12fff1e    bx  lr

保持相同的模式不需要 bx。

另一种方法是在编译时用更复杂的解决方案替换每条 bl 指令,以防万一需要进行远调用。或者由于 bl 偏移量/立即数是在链接时计算的,您可以在链接时将蹦床/单板放入以更改模式或覆盖距离。

您应该可以使用 Kiel 工具自己重复此操作,您需要做的就是在外部函数调用上切换模式或超出 bl 指令的范围。

编辑

了解工具链各不相同,甚至在工具链中,gcc 3.xx 是第一个支持 thumb 的,我不知道我当时看到了这个。请注意,链接器是 binutils 的一部分,它与 gcc 是分开开发的。你提到“arm linker”,arm有自己的工具链,然后他们买了Kiel,也许用他们自己的替换了Kiel的。然后是 gnu 和 clang/llvm 等。因此,这不是“arm链接器”这样做或那样的情况,而是工具链链接器这样做或那样的情况,并且每个工具链首先可以自由使用他们想要的任何调用约定,没有强制要求他们必须使用ARM 的建议,其次他们可以选择实现或不实现,或者只是给你一个警告,你必须处理它(可能用汇编语言或通过函数指针)。

ARM 不需要解释它,或者说,它在架构参考手册中有明确的解释(查看 bl 指令,bx 指令查找单词 interworking 等。所有解释都非常清楚)针对特定架构。所以没有理由再解释一遍。尤其是对于 bl 的范围各不相同且每种架构具有不同的互通特性的通用声明,它可能是一长串的段落或一小章来解释已经清楚记录的内容。

任何实现编译器和链接器的人都会事先精通指令集,并了解指令集的 bl 和条件分支以及其他限制。一些指令集提供近跳转和远跳转,其中一些近和远的汇编语言可能是相同的助记符,因此汇编器通常会决定是否在同一文件中看不到标签来实现远跳转/调用而不是而不是一个近的,以便可以链接对象。

无论如何,在链接之前,您必须编译和组装,工具链人员将完全理解架构的规则。ARM在这里并不特别。

于 2020-11-19T10:38:35.177 回答
1

这是 Raymond Chen 的评论:

单板必须靠近 A,因为 B 太远。A 对单板执行 bl,单板将 r12 设置为最终目的地(B)并执行 bx r12。bx 可以到达整个地址空间。

这对我的问题的回答足够了,但他不想写一个完整的答案(可能是因为时间不够……)我把它放在这里作为答案并选择它。如果有人发布更好,更详细的答案,我会切换到它。

于 2020-11-19T02:04:58.470 回答