Загрузить 64-битный адрес символа в регистр на AArch64
Я связываю файл сборки AArch64 с проектом на C/C++. Код C / C++ содержит переменную, в которой хранится указатель на функцию. Мне нужно вызвать эту функцию (если указатель не является нулевым), поэтому я планирую загрузить адрес переменной в X1
, а затем загрузить значение переменной в X2
посредством LDR X2, [X1]
затем вызовите функцию с помощью BLR X2
, Тем не менее, я не могу понять: как загрузить в X1
адрес переменной?
Пример кода приведен ниже. В нем мне нужно загрузить в X1
64-битный адрес переменной _funcPtr
,
.syntax unified
.text
.global _funcPtr
.p2align 2
.global _myAsmFunc
.type _myAsmFunc, %function
_myAsmFunc:
@ Load the address of _funcPtr to X1 here
Я использую GNU / Clang ассемблер.
3 ответа
Не могу понять: как загрузить в X1 адрес переменной?
==> Я думаю, вы можете сделать это
ldr x1, =var1
Затем компилятор выделяет пространство для хранения «адреса переменной 1» в адресе (назовем это с текущего ПК + 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 бит.)
Используя относительную адресацию ПК, вы можете фактически загрузить само значение указателя из статического хранилища в 2 инструкции или только его адрес с помощью 2 чистых инструкций ALU (которые некоторые процессоры могут объединять и обрабатывать как одну широкую инструкцию). Нет необходимости проходить через GOT, если вам действительно не нужно поддерживать вставку символов.
Как обычно, попросите компилятор сделать для вас пример asm:
void (*fptr)(void);
auto load_global() {
return fptr;
}
g ++ 8.2 -O3 для AArch64 в проводнике компилятора Godbolt. Вы можете использовать его напрямую вместо псевдонима, хотя второй символ в том же месте может избежать жалоб компоновщика на то, что он не проходит через 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
Опять же, это, вероятно, работает напрямую, а не с
.LANCHOR0
. И определенно если есть
static
(частный файл), а не
.global
.
Обратите внимание, что это не отличается от загрузки
uintptr_t x;
глобальная переменная; что вы будете делать со значением var, когда оно будет у вас в регистре, полностью зависит от вас. Проверка на ненулевое значение и вызов через него не меняют способ его загрузки. Я использовал указатель на функцию C ++, чтобы следить за вопросом, хотя это усложняет синтаксис, особенно если вы используете C, где
auto
не работает так же, как C ++ 11.
Это предполагает, что
fptr
находится в пределах диапазона ADRP (+-4 ГБ) вашего кода , а не на произвольном 64-битном расстоянии. Это то, что обычно принимают компиляторы для глобальных переменных, поэтому эта модель памяти полностью стандартна. По умолчанию компоновщик помещает
.rodata
,
.bss
и
.data
раздел достаточно близко к
.text
чтобы это было нормально, если у вас нет огромных массивов в статическом хранилище.
(Тогда вам может потребоваться «средняя» модель кода, в которой огромные объекты помещаются в специальный раздел, который может быть слишком далеко от кода для работы ADRP, где адреса будут обрабатываться менее эффективно, например, через GOT или ближайший литерал бассейн, как и другие ответы.)
Это должно работать:
adrp x1,:got:_funcPtr
ldr x1,[x1,:got_lo12:_funcPtr]
загрузка адреса _funcPtr из глобальной таблицы смещений. _funcPtr должен быть определен как extern, иначе в GOT не будет записи для вашей функции.