101010
101010

Reputation: 15746

How to collect structs in an ELF section using gcc compiler __attributes__?

This is a follow-up question to the accepted answer to this other SO article. I think this stands alone in its own right, which is why I posted it.

I'm trying to "collect" structs defined in different modules into an ELF section. I'm doing this by way of GCC compiler __attributes__. I'm not sure what's preventing this from working.

There are a number of related questions on SO and I've tried some of their ideas with the idea that something small in my code is the issue. For example, this one.

Update (I simplified the code some more)

#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;
}

What I am seeing:

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

What I expected to see:

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

Upvotes: 1

Views: 2083

Answers (2)

Nominal Animal
Nominal Animal

Reputation: 39386

The underlying problem is that the C compiler and the linker do not agree on the alignment of the structures.

For the contents of a section, say foo, to be treated as a single array by the C compiler, both the linker and the C compiler must agree on the size and alignment of each structure. The issue is that the linker typically uses much larger alignment than the C compiler, so successive symbols placed in the section have higher alignment than the C compiler expects.

The solution is to ensure both the C compiler and the linker agree on the alignment of the symbols placed in the section.


For example, if you have e.g.

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 }
};

the symbol placed by the linker is foo, and it will be interpreted as the C compiler defined it. However, if we have

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
};

then foo1 and foo2 are placed by the linker, using whatever alignment it chooses; and to treat the entire foo section as an array, our C definition of the structures must have a size or alignment that matches the linker alignment.


The solution is not to pack the structures, but to pad or align them to the alignment the linker actually uses; or to tell the linker to use the same alignment for the foo section as the C compiler uses for the structures.

There are many ways this can be achieved. Some suggest using a linker script, but I disagree: I prefer to align (using __attribute__((__aligned__(size)))) or pad (using e.g. a trailing unsigned char padding[bytes];) the structure instead, because it makes the code more portable across architectures and compilers (and most importantly, compiler versions) in my experience. Others probably disagree, but I can only comment on my experience, and what I have found to work best.

Because the linker alignment for the section may change, we'll certainly want it to be easily defined at compile time. The simplest option is to define a macro, say SECTION_ALIGNMENT, that one can override at compile time (using e.g. -DSECTION_ALIGNMENT=32 gcc option). In the header file, if it is not defined, it should default to known values (8 for 32-bit arches, 16 for 64-bit arches in Linux, I believe):

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

and the C compiler is told that each such structure has that alignment,

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

so that both the C compiler and the linker agree on the size and alignment of each such structure placed in the foo section.

Note that my related answer has a working example RPN calculator, using this exact mechanism to "register" operators supported by the calculator. If there are any objections to the content of this answer, I would greatly appreciate if one would test that real-world example first.

Upvotes: 1

KamilCuk
KamilCuk

Reputation: 141483

Padding. We all love padding so much.
Consider the following:

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;
}

Do you think that &INFO_1 == &__start_info[1] ? Maybe. Maybe not. The output on my ArchLinux 4.16.8 gcc8.1 is:

288
272

Whoa. &__start_info[1] - &__start_info[0] is equal to sizeof(struct mystruct) = 272. Variables INFO_1 and INFO_0 reside in section named info. But there is padding between them. Exactly 288 - 272 = 16 bytes of padding were added between the ending of INFO_1 and the beginning of INFO_2 variable.
Why? Because we can. I mean, the C compiler can. The C compiler may put any number of padding between any variables. Probably the 16 bytes of padding are because of some memory granulation or optimization.
Adding __attribute__((__aligned__(1))) seems to fix the problem, at least on my PC. I don't think aligned(1) was designed to remove padding between variables.
Maybe a better way (and still not reliable) is to store only pointers to structs in section (added VLA initialization, and ups, i reformatted a little):

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

This works (I use that on at least 3 platforms with gcc), but this is still not reliable. Notice, that in this example only the pointers to variables will be stored in info section, the variables will be stored in other section. This is not how C was designed to be used.
We all know, that the only "good" way is to declare an array in this section, as the C compiler cannot put padding bytes between array members:

__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
    }
};

, but that removes the fun of declaring a variable in one file, and then iterating over it in another... ; )

Upvotes: 0

Related Questions