Search code examples
assemblygccinline-assemblyarm64gnu-assembler

ARM inline assembly: Move function address to register


I wanted to load the address of function to a register. I have written the following inline assembly. The inline assembler returns the error "Error: undefined symbol x0 used as an immediate value". What is wrong with this code?. I am using ARM GNU toolchain on ubuntu.

void MyFunction()
{
  ...
}

int main()
{
   __asm volatile("mov x2, %[FnAddr] \n\t"::[FnAddr]"m"(MyFunction));
}

Aim: In newly booted AArch64 system (which is in EL2), I wanted to go to EL1. For this ELR_EL2 register is to be loaded with the target address to continue the execution


Solution

  • There are several problems with what you have so far:

    First, as Peter Cordes pointed out in comments, m is the wrong constraint and expands to something like [x0].

    But thinking further, materializing a function's address in a register is actually somewhat complicated. A single mov instruction generally won't work, since the immediate is limited to 16 bits. So even if you can get the compiler to emit mov x2, #MyFunction, your program will compile but then fail to link. Usually you would do it with an adrp/add sequence, see Understanding ARM relocation (example: str x0, [tmp, #:lo12:zbi_paddr]).

    The best approach, though, is to have the compiler do it for you. By using an input operand with an r constraint, the compiler will precede your inline asm with a sequence of adrp, mov or whatever is appropriate for your program's code model, get the address in some general-purpose register, and expand the operand to the name of that register (x0,x1, etc). You don't care here which register is chosen, because you're just going to use it as input for a msr instruction, so you'd want to avoid hardcoding x2 anyway. (In case it did have to be x2, you could use a local register variable to tell the compiler which register to use.)

    Thus your code can simply become:

    __asm volatile ("msr ELR_EL2, %[FnAddrReg]" : : [FnAddrReg] "r" (MyFunction));
    

    Try it on godbolt (scroll down the assembly output to see the code in question).

    Technically the volatile keyword is not necessary, as an asm with no outputs is always considered volatile, but it doesn't hurt and might be a good hint to someone reading the code.