我将AArch64程序集文件链接到C / C ++中的项目。 C / C ++代码包含一个存储函数指针的变量。我需要调用此函数(如果指针不为null),所以我的计划是在X1
中加载变量的地址,然后通过{加载变量的值到X2
{1}},然后通过LDR X2, [X1]
调用该函数。但是,我无法弄清楚:如何加载BLR X2
变量的地址?
下面是一个代码示例。在其中我需要加载X1
变量X1
的64位地址。
_funcPtr
我正在使用GNU / Clang汇编程序。
答案 0 :(得分:1)
使用相对于 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 获取带有 global(而不是隐藏)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
。如果 fptr
是 static
(文件私有),而不是 .global
。
请注意,这与加载 uintptr_t x;
全局变量没有区别;一旦将 var 的值放入寄存器中,您对它的处理完全取决于您。检查它是否为非零并通过它调用不会改变你加载它的方式。我使用 C++ 函数指针只是为了解决这个问题,即使它使语法更难,特别是如果您使用 C,其中 auto
的工作方式与 C++11 不同。
这假设 fptr
在您的代码的 ADRP range (+-4GiB) 范围内,而不是任意的 64 位距离。这是编译器通常对全局变量的假设,因此该内存模型是完全标准的。链接器默认将 .rodata
、.bss
和 .data
部分放置在足够接近 .text
的位置,这样就可以了,除非您在静态存储中有一些巨大的数组。
(然后您可能需要使用“中等”代码模型,其中巨大的对象位于一个特殊的部分,该部分可能与 ADRP 的代码相距太远而无法工作,在那里地址的处理效率会降低,例如通过 GOT 或附近的文字池,如其他答案所示。)
答案 1 :(得分:0)
这应该有效:
adrp x1,:got:_funcPtr
ldr x1,[x1,:got_lo12:_funcPtr]
从全局偏移量表中加载 _funcPtr 的地址。必须将 _funcPtr 定义为 extern ,否则GOT中将没有用于您的功能的条目。
答案 2 :(得分:0)
我想不通:如何在 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 相对地址。)