Загрузить 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 не будет записи для вашей функции.

Другие вопросы по тегам