简体   繁体   中英

Implementing a compile-time read-only function pointer table in GCC

I want to implement a simple way to declare/define functions that should be added to a function pointer table in R/O memory (program flash on an AVR device using GCC, specifically) at compile-time while also having a default function pointer that gets placed in all unused entries. For example, if I have 32 possible entries, then the following:

DEFAULTFUNC
void default_handler(...)
{
   ...
}

FUNC(28)
void handle_foo(...)
{
   ...
}

will put a pointer to handle_foo in element 28 of the function table while putting default_handler into the other 31.

I've looked at how avr-libc implements ISR() for interrupt vectors, but it seems that it depends on some internal GCC behavior that I haven't found yet for placing the function pointer within the .vectors segment. How can I mimic it in code for creating a function pointer table in the .{,rel{,a}.}rodata segment as appropriate?

http://gcc.gnu.org/onlinedocs/gcc/Variable-Attributes.html discusses placing variables into particular linker sections. They give an example for placing a uart structure at a section which presumably has been configured to be at the address of the hardware (named DUART_A):

struct duart a __attribute__ ((section ("DUART_A"))) = { 0 };

In the case you describe, I believe you would simply name the existing read-only section to cause the table to be placed wherever it finds room there.

You probably are building your embedded software on eg some Linux PC (otherwise you should). Then you probably use make or some other builder.

Then you can generate a C file (using old m4 , GPP or your favorite script in Python, awk or whatever...). Then feed that generated file (perhaps by #include -ing it somewhere) to the C compiler.

That generated C code (perhaps a sequence of your C macro invocations) could construct statically (at C compile time) your read-only table of functions.

Otherwise, extend your GCC with eg some customization coded with MELT to make the magic happen. In your particular case, I don't think it is worthwhile (but I could be wrong), because generating some parts of some C code is so simple in your case....

I may be a few years late, but this question piqued my interest, and so I solved it with a bit of C code, a few __attribute__ s and the linker's support for weak symbols ( avr-libc basically does the same for the vector table, but does it in assembly code).

The simple minimal example is in this answer, but I have put up the slightly extended version at https://github.com/ndim/handler-function-table .

The handler table interface:

/* handler_table.h */
#ifndef HANDLER_TABLE_H
#define HANDLER_TABLE_H
#include <avr/pgmspace.h>
#define HANDLER_MAX 2
typedef void (*handler_func)(void);
extern const handler_func handler_table_P[HANDLER_MAX] PROGMEM;
#endif /* HANDLER_TABLE_H */

The actual implementation of the handler table:

/* handler_table.c */
#include "handler_table.h"
void handler_foo(void) __attribute__((weak, alias("__handler_default")));
void handler_bar(void) __attribute__((weak, alias("__handler_default")));

const handler_func handler_talbe_P[HANDLER_MAX] PROGMEM = {
  handler_foo,
  handler_bar
};

__attribute__((weak))
void handler_default(void)
{
}

void __handler_default(void)
{
  handler_default();
}

Test case main program:

/* testcase-main.c */
#include "handler-table.h"

int main()
{
  for (unsigned int i=0; i<HANDLER_MAX; ++i) {
    const uint16_t func_addr = pgm_read_word_near(handler_table_P[i]);
    const handler_func func = (handler_func) addr;
    func();
  }
}

Testcase 2 defines its own handlers:

/* testcase-2.c */
void handler_default(void)
{ ... }
void handler_foo(void)
{ ... }

Link two test cases:

  1. testcase-1: testcase-main.o handler-table.o

    Uses only the default handlers.

  2. testcase-2: testcase-main.o handler-table.o testcase-2.o

    Uses only the handlers supplied by testcase-2.c

A few remarks:

  • This is a link time implementation, not a compile time implementation.

  • This works by defining all handlers with weak symbols, so that any other object file may implement the handler symbol and thus override the weak symbol. For the default handler, we use a dummy handler which only calls the weak symbol for the default handler.

    The stack frame and code of the dummy handler is obviously wasted, and can be easily replaced by a jump in inline assembly if you need avoid the cycles and the space it takes.

  • I have not tested this on an actual AVR MCU, only natively on my PC. I am quite confident that I am correctly using PROGMEM and pgm_read_word_near , though.

  • If you mistype the name of a handler function you have overridden, it will not be entered into the handler table without any compile time or link time warnings or errors.

    I would recommend adding a layer of indirection in preprocessor macros for the names of the handler functions, so that you get a compile time error in case of a typo.

    This macro layer will probably come in handy as well for the code which needs to call a specific handler from the table, and thus needs to know its index in the table.

    On the other hand... in some cases maybe you do not need the table of functions in the first place and just a few weak functions being potentially overridden by non-weak functions can solve your problem without the table.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM