首页 > 解决方案 > 如何使用 gcc 编译器 __attributes__ 在 ELF 部分中收集结构?

问题描述

这是对其他 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

标签: carrayslinuxgccstruct

解决方案


根本问题是 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,它将被解释为 C 编译器定义它。但是,如果我们有

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=32gcc 选项)。在头文件中,如果没有定义,它应该默认为已知值(我相信 8 代表 32 位拱门,16 代表 Linux 中的 64 位拱门):

#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 计算器,使用这种确切的机制来“注册”计算器支持的运算符。如果对此答案的内容有任何异议,我将不胜感激,如果有人能先测试这个真实世界的例子。


推荐阅读