Search code examples
cassemblygccinline-assemblycortex-m

gcc, arm, inline assembly, BX produce unexpected results trying to jump over a goto section


I am using Cortex M0+;

I am trying to preserve a piece of code inside the regular code flow, don't ask me why :)
Basically later on, I'd like to jump to MYCODE.

This is the code

    bool x = false; // defined somewhere globally 

    if (x) goto MYCODE; // prevents removal of MYCODE

    //goto MYJUMP; // this is working as expected
    __asm volatile("BX %0" : : "r" (&&MYJUMP) : ); // this crashes !!!

MYCODE:

    __asm volatile ("nop");
    __asm volatile ("nop");
    __asm volatile ("nop");

MYJUMP:
    __asm volatile ("nop"); // first NOP will crash !!!
    __asm volatile ("nop");
    __asm volatile ("nop");

Now in the first place, GCC removes anything from MYCODE, no matter -Ox I use.
One way to convince to keep the code, was to declare 'x' as global and make a fake if() before.

Another way was to use jump in assembly with this

__asm volatile("BX %0" : : "r" (&&MYJUMP) : );

The generated code is

 96           __asm volatile("BX %0" : : "r" (&&MYJUMP) : );
0800285e:   ldr     r3, [pc, #96]  
08002860:   bx      r3                    // value is 0800286c

Using inline jump, always crashes into DefaultHandler! This is very curious, the NOPs appears to be fine

08002864:   nop     ; (mov r8, r8)
100         __asm volatile ("nop");
08002866:   nop     ; (mov r8, r8)
101         __asm volatile ("nop");
08002868:   nop     ; (mov r8, r8)
102         __asm volatile ("nop");
0800286a:   nop     ; (mov r8, r8)
105         __asm volatile ("nop");
0800286c:   nop     ; (mov r8, r8)       // <-- the code reaches here just fine as expected
106         __asm volatile ("nop");      // but when executed this NOP will jump to DefaultHandler
0800286e:   nop     ; (mov r8, r8)
107         __asm volatile ("nop");
08002870:   nop     ; (mov r8, r8)  

Any idea what's happening?
I am debugging for few hours and I just don't get it.

I'd like to use assembly jump, in this case, GCC will not optimize anymore.

Thanks a lot in advance!


Solution

  • Jumping out of an inline asm needs special handling. There is extra syntax for it: asm goto.

    Briefly, your jump should look like this:

    __asm goto("B %l0" : : : : MYJUMP);
    

    Try on godbolt

    This compiles to a simple b label. I didn't test it but the generated asm looks "obviously correct".

    As a bonus you do not need to load the label address into a scratch register. It also prevents optimizing out MYCODE (without the need for a dummy variable) because the compiler assumes that the jump may or may not be taken. (The docs mention that if you want to inform the compiler that the asm will always jump, you can follow it with __builtin_unreachable().)