【问题标题】:How to collect structs in an ELF section using gcc compiler __attributes__?如何使用 gcc 编译器 __attribute__ 在 ELF 部分中收集结构?
【发布时间】:2018-11-07 10:46:05
【问题描述】:

这是对this other SO article 已接受答案的后续问题。我认为这本身就是独立的,这就是我发布它的原因。

我正在尝试将不同模块中定义的结构“收集”到 ELF 部分中。我通过 GCC 编译器 __attributes__ 来做这件事。我不确定是什么阻止了它的工作。

有许多关于 SO 的相关问题,我尝试了他们的一些想法,认为我的代码中的一些小问题就是问题所在。例如,this one

更新(我进一步简化了代码)

#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

【问题讨论】:

  • “收集”这个词是什么意思?如果这意味着:将变量放在给定部分 - 您还需要在链接描述文件中声明此问题。
  • 这意味着将变量放入给定的部分,并使其出现在以__start_开头的特定数组中。链接描述文件应该是什么样的?
  • 这是一个非常广泛的主题,您应该阅读:sourceware.org/binutils/docs/ld/Scripts.html BTW 您的宏很难阅读,不需要 IMO。部分的对齐也应该在链接描述文件中完成。宏容易出错,应不惜一切代价避免使用。
  • C 没有任何机制来表示单独声明的对象应该重叠,或者应该像数组的元素一样立即相互跟随。 GCC __attribute__s 已经在深入研究扩展领域,但是这些对存储布局的期望超出了标准 C 语言的领域。无论您真正想做的是什么,我建议您找到一种不依赖语言扩展的方法。
  • ::sigh:: 如果我们知道,我们如何评论。这不是一个疯狂的想法。该问题被标记为 gcc 问题。相关链接:mgalgs.github.io/2013/05/10/…

标签: c arrays linux gcc struct


【解决方案1】:

根本问题是 C 编译器和链接器在结构的对齐方式上不一致。

对于要被 C 编译器视为单个数组的节的内容,例如 foo,链接器和 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
};

然后foo1foo2 由链接器放置,使用它选择的任何对齐方式;并且要将整个 foo 部分视为一个数组,我们对结构的 C 定义必须具有与链接器对齐匹配的大小或对齐方式。


解决方案不是打包结构,而是将它们填充或对齐到链接器实际使用的对齐方式;或者告诉链接器对 foo 部分使用与 C 编译器对结构使用相同的对齐方式。

有很多方法可以实现这一点。有些人建议使用链接器脚本,但我不同意:我更喜欢对齐(使用__attribute__((__aligned__(size))))或填充(使用例如尾随unsigned char padding[bytes];)结构,因为它使代码在体系结构和编译器之间更具可移植性(以及大多数重要的是,编译器版本)以我的经验。其他人可能不同意,但我只能评论我的经验,以及我发现最有效的方法。

因为节的链接器对齐可能会改变,我们当然希望它在编译时容易定义。最简单的选择是定义一个宏,比如SECTION_ALIGNMENT,它可以在编译时覆盖(使用例如-DSECTION_ALIGNMENT=32 gcc 选项)。在头文件中,如果没有定义,它应该默认为已知值(我相信 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 部分中放置的每个此类结构的大小和对齐方式上达成一致。

请注意,我的相关@​​987654321@ 有一个工作示例 RPN 计算器,使用这种精确机制来“注册”计算器支持的运算符。如果对这个答案的内容有任何异议,如果有人能先测试这个真实世界的例子,我将不胜感激。

【讨论】:

    【解决方案2】:

    填充。我们都非常喜欢填充。
    考虑以下几点:

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

    你认为&amp;INFO_1 == &amp;__start_info[1] 吗?可能是。也许不吧。我的 ArchLinux 4.16.8 gcc8.1 上的输出是:

    288
    272
    

    哇哦。 &amp;__start_info[1] - &amp;__start_info[0] 等于 sizeof(struct mystruct) = 272。变量 INFO_1 和 INFO_0 位于名为 info 的部分中。但是它们之间有填充。恰好在 INFO_1 的结尾和 INFO_2 变量的开头之间添加了 288 - 272 = 16 字节的填充。
    为什么?因为我们可以。我的意思是,C 编译器可以。 C 编译器可以在任何变量之间放置任意数量的填充。可能 16 字节的填充是因为一些内存粒度或优化。
    添加__attribute__((__aligned__(1))) 似乎可以解决问题,至少在我的电脑上是这样。我不认为 aligned(1) 旨在删除变量之间的填充。
    也许更好的方法(但仍然不可靠)是在节中仅存储指向结构的指针(添加了 VLA 初始化和 ups,我重新格式化了一点):

    #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 的平台上使用它),但这仍然不可靠。注意,在这个例子中,只有指向变量的指针将存储在 info 部分,变量将存储在其他部分。这不是 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
        }
    };
    

    ,但这消除了在一个文件中声明一个变量,然后在另一个文件中迭代它的乐趣......; )

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-02-06
      • 1970-01-01
      • 2018-04-18
      • 1970-01-01
      • 2011-09-20
      • 2019-07-18
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多