【问题标题】:Load 64-bit address of a symbol to a register on AArch64将符号的 64 位地址加载到 AArch64 上的寄存器中
【发布时间】:2016-10-26 19:21:18
【问题描述】:

我正在将 AArch64 程序集文件链接到 C/C++ 中的项目。 C/C++ 代码包含一个存储函数指针的变量。我需要调用这个函数(如果指针不为空),所以我的计划是把变量的地址加载到X1中,然后通过LDR X2, [X1]的方式将变量的值加载到X2中,然后通过BLR X2 调用函数。但是,我想不通:如何在X1中加载变量的地址?

下面是一个代码示例。在其中我需要将变量_funcPtr的64位地址加载到X1

    .syntax unified
    .text
    .global _funcPtr
    .p2align 2
    .global _myAsmFunc
    .type _myAsmFunc, %function
_myAsmFunc:
    @ Load the address of _funcPtr to X1 here

我正在使用 GNU / Clang 汇编器。

【问题讨论】:

  • ldr x1, =_funcPtr 像往常一样吗?
  • @Jester,你的意思是汇编程序应该扩展成真实指令的宏/伪指令吗?
  • 是和否 :) 这确实是一条伪指令,但它会扩展为真正的 ldr 和文字池条目。
  • @MichaelDorgan 不,它会生成一条 PC 相关的 LDR 指令来加载文字池中的 64 位值。如果要在不使用加载指令的情况下将 64 位地址加载到寄存器中,则需要使用 MOVL 伪指令。然而,它需要 4 条指令(1 条 MOVZ 和 3 条 MOVL),因此它的总大小要长 4 个字节,而且可能要慢得多。
  • 我在想 ADRP xX, #label : ADD xY, xX, #imm。正如您所说,这当然是与 PC 相关的加载指令,并在较新的 CPU 上进一步优化。

标签: assembly arm constants arm64


【解决方案1】:

这应该可行:

adrp    x1,:got:_funcPtr
ldr     x1,[x1,:got_lo12:_funcPtr]

从全局偏移表中加载 _funcPtr 的地址。 _funcPtr 必须定义为 extern,否则 GOT 中将没有您的函数的条目。

【讨论】:

    【解决方案2】:

    使用 PC 相对寻址,您实际上可以在 2 条指令中从静态存储中加载指针 本身,或者使用 2 条纯 ALU 指令(某些 CPU 可能会将其融合并处理为单宽指令)。除非您确实需要支持符号插入,否则无需通过 GOT。

    像往常一样,请编译器为您制作一个 asm 示例:

    void (*fptr)(void);
    
    auto load_global() {
        return fptr;
    }
    

    g++ 8.2 -O3 用于 AArch64,on the Godbolt compiler explorer。您可以直接使用 fptr 而不是 .LANCHOR0 别名,尽管同一位置的第二个符号可以避免链接器抱怨没有通过 GOT 获取具有全局(而不是隐藏)ELF 可见性的符号.

    .text
    load_global:
            adrp    x0, .LANCHOR0
            ldr     x0, [x0, #:lo12:.LANCHOR0]
            ret
    
    # and later, in .bss where fptr is declared.
     .bss
     .align  3
     .set    .LANCHOR0,. + 0     # .LANCHOR0 has the same address as fptr, but isn't .global
                                 # could be .LANCHOR0: instead of .set
    .global fptr
    fptr:
       .zero   8
    

    我修剪了 asm 输出(.size.type),并将指令重新排序为更合理的顺序以对相关内容进行分组。


    实际上只是获取地址:

    auto address_of_global() {
        return &fptr;
    }
    
    address_of_global:
            adrp    x0, .LANCHOR0
            add     x0, x0, :lo12:.LANCHOR0
            ret
    

    同样,这可能直接使用fptr 而不是.LANCHOR0。如果fptrstatic(文件私有),肯定不是.global


    请注意,这与加载uintptr_t x; 全局变量没有区别;将 var 的值放入寄存器后如何处理它完全取决于您。检查它是否为非零并通过它调用不会改变你加载它的方式。我使用 C++ 函数指针只是为了解决这个问题,即使它使语法更难,特别是如果你使用 C 而auto 的工作方式与 C++11 不同。

    假设fptr 在您的代码的ADRP range (+-4GiB) 内,而不是任意 64 位距离。这是编译器通常对全局变量的假设,所以这个内存模型是完全标准的。链接器默认将.rodata.bss.data 部分放在足够接近.text 的位置,这样就可以了,除非您在静态存储中有一些巨大的数组。

    (然后您可能需要使用“中等”代码模型,其中巨大的对象放在一个特殊的部分中,该部分可能与 ADRP 工作的代码相距太远,地址的处理效率会降低,例如通过 GOT 或一个附近的文字池,就像其他答案显示的那样。)

    【讨论】:

      【解决方案3】:

      我不知道:如何在 X1 中加载变量的地址?

      ==> 我认为你可以这样做

      ldr x1, =var1  
      

      然后编译器分配一个空间来存储'var1的地址'在一个地址中(我们称这是从当前PC + 0x100,例如0x40000100),上面的代码实际翻译为

         addr            instr.      disassem.
      0x40000000    58000801        ldr x1, 0x100   <== here 0x100 is PC-relative
      ...
      0x40000100    50003400        .inst. undefined   <== 0x50003400 is actual address of var1, it's just a data
      

      如您所知,这是因为单个 ldr 指令本身不能包含 32 位立即数数据。 (所以使用 19 位的 PC 相对地址。)

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-04-07
        • 1970-01-01
        • 2016-12-25
        • 2013-01-10
        • 2020-03-27
        • 1970-01-01
        相关资源
        最近更新 更多