arm 链接器在函数调用中使用的 'veneer' 是什么?

What is 'veneer' that arm linker uses in function call?

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

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

5.3.1.1 Use of IP by the linker Both the ARM- and Thumb-state BL instructions are unable to address the full 32-bit address space, so it may be necessary for the linker to insert a veneer between the calling routine and the called subroutine. Veneers may also be needed to support ARM-Thumb inter-working or dynamic linking. Any veneer inserted must preserve the contents of all registers except IP (r12) and the condition code flags; a conforming program must assume that a veneer that alters IP may be inserted at any branch instruction that is exposed to a relocation that supports inter-working or long branches. Note R_ARM_CALL, R_ARM_JUMP24, R_ARM_PC24, R_ARM_THM_CALL, R_ARM_THM_JUMP24 and R_ARM_THM_JUMP19 are examples of the ELF relocation types with this property. See [AAELF] for full details

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

这是陈峰的评论:

The veneer has to be close to A because B is too far away. A does a bl to the veneer, and the veneer sets r12 to the final destination(B) and does a bx r12. bx can reach the entire address space.

这足以回答我的问题,但他不想写完整的答案(可能是因为时间不够..)我把它作为答案放在这里 select。如果有人发布更好、更详细的答案,我会改用它。

这只是一个蹦床。 Interworking 是比较容易演示的,这里使用 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 在手臂和拇指之间切换模式,因此 linker 添加了一个蹦床,我称之为蹦床,或者听说它叫您跳上跳下到达目的地。在这种情况下,基本上将 bl 的分支部分转换为 bx,link 部分他们利用了 bl。你可以看到这是为拇指到手臂或手臂到拇指完成的。

even_more 函数处于相同模式 (ARM),因此不需要 trampoline/veneer。

bl的距离限制让我看看。哇,这很简单,gnu 也称它为单板:

.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 offset/immediate 是在 link 时计算的,您可以在 link 时将 trampoline/veneer 放入以改变模式或覆盖距离。

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

编辑

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

ARM就不用解释了,还是说吧,在Architectural Reference Manual里面解释的很清楚(看bl指令,bx指令找interworking等字眼,都解释的很清楚了)对于特定的架构。所以没有理由再解释一遍。特别是对于 bl 的范围各不相同且每个体系结构具有不同的互通功能的通用声明,这将是一长段段落或一小章来解释已经明确记录的内容。

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

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