4.3.6.3 Function Pointers

The MPLAB XC8 compiler fully supports pointers to functions. These are often used to call one of several function addresses stored in a user-defined C array, which acts like a lookup table.

Function pointers are 2 bytes in size. As the address is word aligned, such pointers can reach program memory addresses up to 128 KB. If the device you are using supports more than this amount of program memory and you wish to indirectly access routines above this address, then you need to use the -mrelax option (see 3.6.1.11 Relax Option), which maintains the size of the pointer,but will instruct the linker to have calls reach their final destination via lookups.

In order to facilitate indirect jump on devices with more than 128 KB of program memory space, there is a special function register called EIND that serves as most significant part of the target address when eicall or eijmp instructions are executed. The compiler might also use this register in order to emulate an indirect call or jump by means of a ret instruction.

The compiler never sets the EIND register and assumes that it never changes during the startup code or program execution, and this implies that the EIND register is not saved or restored in function or interrupt service routine prologues or epilogues.

To accommodate indirect calls to functions and computed gotos, the linker generates function stubs, or trampolines, that contain direct jumps to the desired addresses. Indirect calls and jumps are made to the stub, which then redirects execution to the desired function or location.

For the stubs to work correctly, the -mrelax option must be used. This option ensures that the linker will use a 16-bit function pointer and stub combination, even though the destination address might be above 128 KB.

The default linker script assumes code requires the EIND register contain zero. If this is not the case, a customized linker script must be used in order to place the sections whose name begin with .trampolines into the segment appropriate to the value held by the EIND register.

The startup code from the libgcc.a library never sets the EIND register.

It is legitimate for user-specific startup code to set up EIND early, for example by means of initialization code located in section .init3. Such code runs prior to general startup code that initializes RAM and calls constructors, but after the AVR-LibC startup code that sets the EIND register to a value appropriate for the location of the vector table.

#include <avr/io.h>

static void
__attribute__((section(".init3"),naked,used,no_instrument_function))
init3_set_eind (void)
{
  __asm volatile ("ldi r24,pm_hh8(__trampolines_start)\n\t"
  "out %i0,r24" :: "n" (&EIND) : "r24","memory");
}

The __trampolines_start symbol is defined in the linker script.

Stubs are generated automatically by the linker, if the following two conditions are met:

  • The address of a label is taken by means of the gs assembler modifier (short for generate stubs) like so:
    LDI r24, lo8(gs(func))
    LDI r25, hi8(gs(func))
  • The final location of that label is in a code segment outside the segment where the stubs are located.

The compiler emits gs modifiers for code labels in the following situations:

  • When taking the address of a function or code label
  • When generating a computed goto
  • If the prologue-save function is used (see 3.6.1.3 Call-prologues Option)
  • When generating switch/case dispatch tables (these can be inhibited by specifying the -fno-jump-tables option, 3.6.1.10 Jump-tables Option)
  • C and C++ constructors/destructors called during startup/shutdown

Jumping to absolute addresses is not supported, as shown in the following example:

int main (void)
{
  /* Call function at word address 0x2 */
  return ((int(*)(void)) 0x2)();
}

Instead, the function has to be called through a symbol (func_4 in the following example) so that a stub can be set up:

int main (void)
{
  extern int func_4 (void);
  /* Call function at byte address 0x4 */
  return func_4();
}

The project should be linked with -Wl,--defsym,func_4=0x4. Alternatively, func_4 can be defined in the linker script.