Search code examples
pointersgccarmcortex-mthumb

UsageFault when branching to a function pointer on Cortex-M0


I'm running code on an STM32F0 (ARM Cortex-M0). I define a function pointer to a nested function:

void My_Async_Func(void *handle, void (*complete)(bool success)) {
    /* 
     *  \/ A nested function \/
     */
    void receiveHandler(void) {
        // This function lies at an even-numbered address
    }
    /* ... */
    uart->rxDoneHandler = &receiveHandler;
    Go(uart);
}

The nested function appears to be screwing things up. Later when I call that rxDoneHandler pointer, it tries to branch to 0x8c0c6c0c and I get a UsageFault. According to the ARM docs, this is because I'm branching to an even-numbered address on a processor that only supports the Thumb instruction set, which requires you to only branch to odd-numbered addresses.

I'm using GCC arm-none-eabi 4.9.3. My CFLAGS are:

arm-none-eabi-gcc -mcpu=cortex-m0 -mthumb -mfloat-abi=soft -O0 -g3 -Wall -fmessage-length=0 -ffunction-sections -c -MMD -MP 

Things I tried

I tried calling gcc with -mthumb-interwork as suggested here. Same result.

I tried manually setting the alignment of the function with an attribute like

__attribute__((aligned(16))) void receiveHandler(void) {

}

Same result.

I tried manually adding 1 to the pointer when I call it like

(uart->rxDoneHandler + 1)();

Same result.

Am I doing something wrong, or is this a bug in the compiler?


Solution

  • A nested function must not be called from the outside of the enclosing function unless you're still "logically" inside that function; from the GCC page on nested functions (emphasis mine):

    It is possible to call the nested function from outside the scope of its name by storing its address or passing the address to another function:

     hack (int *array, int size)
     {
       void store (int index, int value)
         { array[index] = value; }
    
       intermediate (store, size);
     }
    

    Here, the function intermediate receives the address of store as an argument. If intermediate calls store, the arguments given to store are used to store into array. But this technique works only so long as the containing function (hack, in this example) does not exit.

    My guess is that you register uart->rxDoneHandler as a Interrupt Service Routine (or something that is called from within an ISR). You can't do that.