Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Branch to an address using GCC inline ARM asm

I want to branch to a particular address(NOT a label) using ARM assembly, without modifying the LR register. So I go with B instead of BL or BX. I want this to be done in GCC inline asm.

Here is the documentation, and here is what I 've tried:

#define JMP(addr) \
    __asm__("b %0" \
            : /*output*/ \
            : /*input*/ \
            "r" (addr) \
           );

It is a C macro, that can be called with an address. When I run it I get the following error:

error: undefined reference to 'r3'

The error is because of the usage of "r". I looked into it a bit, and I've found that it could be a bug on gcc 4.9.* version.

BTW, I am using Android/Linux Gcc 4.9 cross compiler, on an OSX. Also, I don't know wether I should have loaded something on Rm.

Cheers!

Edit: I changed the macro to this, and I still get undefined reference to r3 and r4:

#define JMP(addr) \
    __asm__("LDR r5,=%0\n\t" \
            "LDR r4,[r5]\n\t"\
            "ADD r4,#1\n\t" \
            "B r4" \
            : /*output*/ \
            : /*input*/ \
            "r" (addr) \
            : /*clobbered*/ \
            "r4" ,"r5" \
           );

Explanation: load the address of the variable to r5, then load the value of that address to r4. Then add 1 to LSB (emm required by ARM specification?). And finally Branch to that address.

like image 537
Paschalis Avatar asked May 20 '15 01:05

Paschalis


2 Answers

Since you are programming in C, you could just use a plain C approach without any assembly at all: just cast the variable, that holds the pointer to address to which you want to jump, to a function pointer and call it right away:

((void (*)(void)) addr)();

just an explanation to this jungle of brackets: with this code you are casting addr to a pointer (signified by the star (*)) to a function that takes no argument (the second void means there are no arguments) and that also returns nothing (first void). finally the last two brackets are the actual invocation of that function. Google for "C function pointer" for more information about that approach.

But if that doesn't work for you and you still want to go with the assembly approach, the instruction that you are in looking for is in fact BX (not sure why you excluded that initially. but I can guess that the name "Branch and Exchange" mislead you to believe that the register argument is swapped (and thereby changed) with the program counter, which is NOT the case, but it confused me in the beginning, too).

For that just a simple recap of the instructions:

  • B would take a label as an argument. Actually the jump will be encoded as an offset from the current position, which tells the processor to jump that many instruction forwards or backwards (normally the compiler, assembler or linker will take care of calculating that offset for you). During execution, control flow will simply be transferred to that position without changing any register (this means also the link register LR will stay unchanged)
  • BX R0 will take the absolute (so not an offset) address from a register, in this case R0, and continue execution at that address. This is also done without changing any other register.
  • BL and BLX R0 are the corresponding counterparts to the previous two instruction. They will do the same thing control flow wise, but on top of that save the current program counter in the link register LR. This is needed if the called function is supposed to return later on.

so in essence, what you would need to do is:

asm("BX %0" : : "r"(addr));

instructing the compiler to make sure the variable addr is in a register (r), which you are promising to only read and not to change. on top of that, upon return you won't have changed (clobbered) any other register.

See here https://gcc.gnu.org/onlinedocs/gcc/Constraints.html for more information about inline assembly constraints.

To help you understand why there are also other solutions floating around, here some things about the ARM architecture:

  • the program counter PC is for many instruction accessible as a regular register R15. It's just an alias for that exact register number.
  • this means that almost all arithmetic and register altering instructions can take it as an argument. However, for many of them it is highly deprecated.
  • if you are looking at the disassembly of a program compiled to ARM code, any function will end with one of three things:
    • BX LR which does exactly what you want to do: take the content of the link register (LR is an alias for R14) and jump to that location, effectively returning to the caller
    • POP {R4-R11, PC} restoring the caller saved register and jumping back to the caller. This will almost certainly have counterpart of PUSH {R4-R11, LR} in the beginning of the function: your are pushing the content of the link register (the return address) onto the stack but store it back into the program counter effectively returning to the caller in the end
    • B branch to a different function, if this function ends with a tail call and leaving it up to that function to return to the original caller.

Hope that helps, Martin

like image 70
Martin Keßler Avatar answered Sep 20 '22 11:09

Martin Keßler


You can't branch to a register, you can only branch to a label. If you want to jump to address in a register you need to move it into the PC register (r15).

#define JMP(addr) \
    __asm__("mov pc,%0" \
            : /*output*/ \
            : /*input*/ \
            "r" (addr) \
           );
like image 25
Ross Ridge Avatar answered Sep 20 '22 11:09

Ross Ridge