Реализация таблицы указателей функций только для чтения во время компиляции в GCC

Я хочу реализовать простой способ объявления / определения функций, которые должны быть добавлены в таблицу указателей функций в памяти R / O (программная флэш-память на устройстве AVR с использованием GCC, в частности) во время компиляции, а также иметь указатель функции по умолчанию, который помещается во все неиспользуемые записи. Например, если у меня есть 32 возможных записи, то следующее:

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

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

поместит указатель на handle_foo в элемент 28 таблицы функций, а default_handler в другой 31.

Я смотрел, как avr-libc реализует ISR() для векторов прерываний, но кажется, что это зависит от некоторого внутреннего поведения GCC, которое я еще не нашел для размещения указателя функции в сегменте .vectors. Как я могу имитировать это в коде для создания таблицы указателей функций в сегменте .{,rel{,a}.}rodata, если это необходимо?


person Ignacio Vazquez-Abrams    schedule 07.11.2013    source источник
comment
В качестве обработчика по умолчанию вы смотрели BADISR_vect? (см. nongnu.org/avr-libc/user-manual/group__avr__interrupts. html)   -  person ouah    schedule 08.11.2013
comment
@ouah: Да: # define BADISR_vect __vector_default, а потом я потеряюсь в магии GCC.   -  person Ignacio Vazquez-Abrams    schedule 08.11.2013
comment
В документе есть пример использования: ISR(BADISR_vect) { /* user code here */ }.   -  person ouah    schedule 08.11.2013
comment
Зачем повторять функции определения, а не таблицу, указывающую на них? В этом нет особого смысла. Для таблицы векторов прерываний вам нужно что-то вроде func_t interrupt_vector [] = {default, default, timer5, default, adc, default .... В этом случае default будет просто именем функции по умолчанию.   -  person Lundin    schedule 15.08.2017
comment
@Lundin: Тогда, возможно, вам стоит сказать поставщикам компиляторов, что они все это время делали это неправильно.   -  person Ignacio Vazquez-Abrams    schedule 15.08.2017
comment
@ IgnacioVazquez-Abrams О, я делаю это регулярно: большинство компиляторов встроенных систем находятся в довольно плачевном состоянии. Однако большинство из них выделяют векторную таблицу ISR как массив указателей на функции. Обычно это то, чего хотят все основные микроконтроллеры.   -  person Lundin    schedule 16.08.2017


Ответы (3)


http://gcc.gnu.org/onlinedocs/gcc/Variable-Attributes.html обсуждает размещение переменных в определенных разделах компоновщика. Они дают пример размещения структуры uart в секции, которая предположительно была сконфигурирована для размещения по адресу оборудования (названного DUART_A):

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

В случае, который вы описываете, я полагаю, вы бы просто назвали существующую секцию только для чтения, чтобы таблица размещалась там, где она находит место.

person mah    schedule 07.11.2013
comment
Дело в том, что я не только хочу поместить его в определенный раздел, но мне также нужно поместить его в произвольную позицию внутри раздела, заменяя все, что в этой позиции, а затем иметь возможность получить его во время выполнения. - person Ignacio Vazquez-Abrams; 08.11.2013

Вы, вероятно, создаете свое встроенное программное обеспечение, например, какой-нибудь ПК с Linux (в противном случае вам следует). Тогда вы, вероятно, используете make или какой-нибудь другой строитель.

Затем вы можете создать файл C (используя старый m4, GPP или ваш любимый скрипт на Python, awk или что-то в этом роде ...). Затем передайте этот сгенерированный файл (возможно, #include-добавив его где-нибудь) в компилятор C.

Этот сгенерированный код C (возможно, последовательность ваших вызовов макросов C) может статически (во время компиляции C) построить вашу таблицу функций, доступную только для чтения.

В противном случае расширите свой GCC, например, некоторые настройки закодированы с помощью MELT, чтобы волшебство произошло. В вашем конкретном случае я не думаю, что это имеет смысл (но я могу ошибаться), потому что создание некоторых частей некоторого кода C в вашем случае так просто ...

person Basile Starynkevitch    schedule 07.11.2013
comment
Я мог бы пойти по этому пути, но я хочу упростить для потребителей моего кода (который на самом деле является структурой диспетчеризации) определение обработчиков без необходимости перепрыгивать через обруч за обручем после ... - person Ignacio Vazquez-Abrams; 08.11.2013
comment
Сгенерировать очень обычный код C (например, тот, который вы хотите) настолько просто (и это очень распространенный трюк) ... - person Basile Starynkevitch; 08.11.2013

Возможно, я опоздал на несколько лет, но этот вопрос вызвал у меня интерес, поэтому я решил его с помощью небольшого количества кода C, нескольких __attribute__ и поддержки слабых символов компоновщиком (avr-libc в основном делает то же самое для таблицы векторов, но делает это в ассемблерном коде).

В этом ответе приведен простой минимальный пример, но я разместил слегка расширенную версию по адресу https://github.com/ndim/handler-function-table.

Интерфейс таблицы обработчиков:

/* 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 */

Фактическая реализация таблицы обработчика:

/* 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();
}

Основная программа тестового случая:

/* 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 определяет свои собственные обработчики:

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

Свяжите два тестовых примера:

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

    Использует только обработчики по умолчанию.

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

    Использует только обработчики, предоставленные testcase-2.c

Несколько замечаний:

  • Это реализация времени компоновки, а не реализация времени компиляции.

  • Это работает путем определения всех обработчиков со слабыми символами, так что любой другой объектный файл может реализовать символ обработчика и, таким образом, переопределить слабый символ. Для обработчика по умолчанию мы используем фиктивный обработчик, который вызывает только слабый символ для обработчика по умолчанию.

    Фрейм стека и код фиктивного обработчика, очевидно, тратятся впустую и могут быть легко заменены переходом во встроенную сборку, если вам нужно избежать циклов и занимаемого пространства.

  • Я не тестировал это на реальном микроконтроллере AVR, только изначально на моем ПК. Однако я вполне уверен, что правильно использую PROGMEM и pgm_read_word_near.

  • Если вы неправильно введете имя функции-обработчика, которую вы переопределили, она не будет введена в таблицу обработчиков без предупреждений или ошибок времени компиляции или времени компоновки.

    Я бы рекомендовал добавить уровень косвенности в макросах препроцессора для имен функций-обработчиков, чтобы вы получали ошибку времени компиляции в случае опечатки.

    Этот макроуровень, вероятно, также пригодится для кода, который должен вызывать определенный обработчик из таблицы и, следовательно, должен знать его индекс в таблице.

    С другой стороны ... в некоторых случаях, возможно, вам вообще не нужна таблица функций, и всего несколько слабых функций, которые потенциально могут быть переопределены неслабыми функциями, могут решить вашу проблему без таблицы.

person ndim    schedule 14.08.2017