Как собрать структуры в разделе ELF с помощью компилятора gcc __attributes__? - PullRequest
0 голосов
/ 28 мая 2018

Это дополнительный вопрос к принятому ответу этой другой статье SO .Я думаю, что это само по себе само по себе, поэтому я и опубликовал его.

Я пытаюсь «собрать» структуры, определенные в разных модулях, в раздел ELF.Я делаю это с помощью компилятора GCC __attributes__.Я не уверен, что мешает этому работать.

Есть несколько связанных с этим вопросов о SO, и я попробовал некоторые из их идей, полагая, что проблема заключается в небольшом коде.Например, это .

Обновление (я немного упростил код)

#include <stdio.h>

#define  INFO_NAME(counter)  INFO_CAT(INFO_, counter)
#define  INFO_CAT(a, b)      INFO_DUMMY() a ## b
#define  INFO_DUMMY()

#define  DEFINE_INFO(data...) \
         const static struct mystruct INFO_NAME(__COUNTER__)    \
         __attribute((__section__("info")))         \
     __attribute((__used__)) = { data }         \


struct mystruct
{
    char name[255];
    int (*on_init) (int num1);
    int (*on_do_something) (int num1);
};

extern struct mystruct  __start_info[];
extern struct mystruct  __stop_info[];

static int _print_number(int x)
{
    printf("%d\n", x);
}

DEFINE_INFO(
    .name = "mary",
    .on_init = _print_number,
    .on_do_something = _print_number
);

DEFINE_INFO(
    .name = "joe",
    .on_do_something = _print_number
);

DEFINE_INFO(
    .name = "bob",
    .on_do_something = _print_number
);

int main(void)
{
    struct mystruct *iter = &__start_info;

    for ( ; iter < &__stop_info; ++iter)
    {
        printf("element name: %s\n", iter->name);
        if (iter->on_init != NULL)
        {
            iter->on_init(1);
        }
        if (iter->on_do_something != NULL)
        {
            iter->on_do_something(2);
        }
    }
    return 0;
}

Что я вижу:

$ ./a.out 
element name: mary
1
2
element name: 
element name: 
element name: 
Segmentation fault (core dumped)

Что я ожидал увидеть:

$ ./a.out 
element name: mary
1
2
element name: joe
2
element name: bob
2

Ответы [ 2 ]

0 голосов
/ 29 мая 2018

Основная проблема заключается в том, что компилятор C и компоновщик не согласовывают выравнивание структур.

Для содержимого раздела, скажем foo, которое должно рассматриваться как один массивкомпилятор C, и компоновщик, и компилятор C, должны согласовать размер и выравнивание каждой структуры.Проблема заключается в том, что компоновщик обычно использует гораздо большее выравнивание, чем компилятор C, поэтому последовательные символы, размещенные в разделе, имеют более высокое выравнивание, чем ожидает компилятор C.

Решение состоит в том, чтобы обеспечитькомпилятор C и компоновщик согласовывают выравнивание символов, размещенных в разделе.


Например, если у вас есть, например,

static struct {
    int     i;
    double  d;
    char    c;
    float   f;
} foo[] __attribute__((__used__, __section__("foo"))) = {
    { 1, 1.0, '1', 1.0f },
    { 2, 2.0, '2', 2.0f }
};

, то символ, размещенный компоновщиком:foo, и он будет интерпретироваться как определенный им компилятор Си.Однако, если у нас есть

static struct {
    int     i;
    double  d;
    char    c;
    float   f;
} foo1 __attribute__((__used__, __section__("foo"))) = {
    1, 1.0, '1', 1.0f
};

static struct {
    int     i;
    double  d;
    char    c;
    float   f;
} foo2 __attribute__((__used__, __section__("foo"))) = {
    2, 2.0, '2', 2.0f
};

, тогда компоновщик размещает foo1 и foo2, используя любое выравнивание, которое он выберет;и чтобы рассматривать весь раздел foo как массив, наше определение структур C должно иметь размер или выравнивание, совпадающее с выравниванием компоновщика.


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

Существует множество способов достижения этого.Некоторые предлагают использовать скрипт компоновщика, но я не согласен: я предпочитаю выравнивать (используя __attribute__((__aligned__(size)))) или дополнять (используя, например, конечный unsigned char padding[bytes];) структуру, потому что это делает код более переносимым между архитектурами и компиляторами (и большинствоглавное, версии компилятора) по моему опыту.Другие, возможно, не согласны, но я могу только прокомментировать мой опыт и то, что я считаю наиболее подходящим.

Поскольку выравнивание компоновщика для раздела может измениться, мы, безусловно, хотим, чтобы его было легко определить при компиляции.время.Самый простой вариант - определить макрос, скажем, SECTION_ALIGNMENT, который можно переопределить во время компиляции (например, с помощью опции -DSECTION_ALIGNMENT=32 gcc).В заголовочном файле, если он не определен, он должен по умолчанию использовать известные значения (8 для 32-битных арок, 16 для 64-битных арок в Linux, я считаю):

#ifndef  SECTION_ALIGNMENT
#if defined(__LP64__)
#define  SECTION_ALIGNMENT  16
#else
#define  SECTION_ALIGNMENT  8
#endif
#endif

и CКомпилятору сообщают, что каждая такая структура имеет это выравнивание

struct foo {
    /* ... Fields ... */
} __attribute__((__section__("foo"), __aligned__(SECTION_ALIGNMENT)));

, так что и компилятор C, и компоновщик согласовывают размер и выравнивание каждой такой структуры, размещенной в разделе foo.

Обратите внимание, что мой связанный ответ имеет рабочий пример RPN калькулятора, использующего этот точный механизм для "регистрации" операторов, поддерживаемых калькулятором.Если есть какие-либо возражения против содержания этого ответа, я был бы очень признателен, если бы кто-нибудь сначала проверил этот реальный пример.

0 голосов
/ 29 мая 2018

Перетяжка.Мы все так любим прокладки.
Примите во внимание следующее:

int main(void)
{
    printf("%" PRIdPTR "\n",
                 (uintptr_t)&INFO_1 - (uintptr_t)&INFO_0);
    printf("%" PRIdPTR "\n",
                 (uintptr_t)&__start_info[1] - (uintptr_t)&__start_info[0]);
    return 0;
}

Как вы думаете, &INFO_1 == &__start_info[1]?Может быть.Возможно, нет.Выходные данные на моем ArchLinux 4.16.8 gcc8.1:

288
272

Вау.&__start_info[1] - &__start_info[0] равно sizeof(struct mystruct) = 272.Переменные INFO_1 и INFO_0 находятся в разделе с именем info.Но между ними есть отступы.Точно 288 - 272 = 16 байтов заполнения были добавлены между окончанием INFO_1 и началом переменной INFO_2.
Почему?Потому что мы можем.Я имею в виду, компилятор C может.Компилятор C может поместить любое количество отступов между любыми переменными.Вероятно, заполнение 16 байтов происходит из-за некоторой грануляции или оптимизации памяти.
Добавление __attribute__((__aligned__(1))), похоже, решает проблему, по крайней мере, на моем ПК.Я не думаю, что aligned(1) был разработан для удаления заполнения между переменными.
Может быть, лучший способ (и все еще не надежный) - хранить только указатели на структуры в разделе (добавлена ​​инициализация VLA и взлеты, я переформатировал немного):

#define  DEFINE_INFO(...) \
     __attribute__((__used__,__section__("info") /* maybe aligned(1) too? */ )) \
     static const struct mystruct * const   \
     INFO_NAME(__COUNTER__) = &(const struct mystruct){ __VA_ARGS__ }

Это работает (я использую это по крайней мере на 3 платформах с gcc), но это все еще не надежно.Обратите внимание, что в этом примере в разделе информации будут храниться только указатели на переменные, а в другом разделе - переменные.Это не то, как C был разработан для использования.
Мы все знаем, что единственный «хороший» способ - объявить массив в этом разделе, так как компилятор C не может помещать байты заполнения между членами массива:

__attribute__((__used__,__section__("info")))
static const struct mystruct INFO[] = {
    {
        .name = "mary",
        .on_init = _print_number,
        .on_do_something = _print_number
    },{
        .name = "joe",
        .on_do_something = _print_number
    },{ 
        .name = "bob",
        .on_do_something = _print_number
    }
};

, но это убирает удовольствие от объявления переменной в одном файле, а затем итерации по ней в другом ...;)

...