内联静态数据导致段类型冲突

Inline static data causes a section type conflict

我想将一些用户定义的数据放入自定义部分,以供应用程序和离线分析器同时读取。假设以下示例:

const int* get_data()
{
  __attribute__((section(".custom")))
  static const int data = 123;

  return & data;
}

inline const int* inline_get_data()
{
  __attribute__((section(".custom")))
  static const int inline_data = 123;

  return & inline_data;
}

int main()
{
  (void) get_data();
  (void) inline_get_data();
  return 0;
}

datainline_data的值将出现在.custom部分。当 __attributes__corresponding pragmas.

替换时,Clang 编译此示例并产生正确的结果,就像 MSVC 所做的那样

不幸的是,GCC 5.2 给出了以下错误:

error: inline_data causes a section type conflict with data

问题归结为两个变量有不同的联系(dataflagged部分和a部分,inline_data部分是用 aG 标记)。如果第二个函数没有被标记为内联而是一个模板(GCC 5.2 编译它),GCC 4.9 也会以同样的方式失败。

如果在生成的程序集中临时更改并手动修复了一个节名称,GCC 5.2 也可以正常编译。

这个问题有什么已知的解决方法吗?我无法控制函数签名,*data 变量是由我提供的宏生成的,它们可以出现在任何地方。

为了大家好,我会重申你已经知道的和@Rumbaruk 已经引用:gcc的文档明确限制section属性的应用为global变量。所以 gcc 行为的理想解决方法是让 gcc 在不受支持的 gcc-specific 语言应用程序上不呕吐或发出损坏的代码 延期。我们没有权利期望成功或期望成功一再重复table.

这里有关于 gcc 如何以及为什么产生 section-type 冲突的长篇解释 编译错误和 clang 没有。如果不耐烦,请滚动到 Fixes,但不要 期待银弹。

出于演示目的,我将使用比 您已发布,即:

source.cpp

const int* get_data()
{
    __attribute__((section(".custom")))
    static const int data = 123;

    return & data;
}

inline const int* inline_get_data()
{
    __attribute__((section(".custom")))
    static const int inline_data = 123;

    return & inline_data;
}

const int* other_get_data()
{
    return inline_get_data();
}

header.h

#ifndef HEADER_H
#define HEADER_H
extern const int* get_data();
extern const int* other_get_data();
#endif

main.cpp

#include "header.h"
#include <iostream>

int main()
{
    std::cout << (*get_data() + *other_get_data()) << std::endl;
    return 0;
}

就目前而言,此程序重现了 section-type 冲突错误 用 gcc 5.2 编译:

$ g++-5 -Wall -pedantic -c source.cpp
source.cpp:12:22: error: inline_data causes a section type conflict with data
     static const int inline_data = 123;
                      ^

Clang (3.6/3.7) 无投诉:

$ clang++ -Wall -pedantic -I. -o prog main.cpp source.cpp 
$ ./prog
246

gcc 阻塞的根源在于 inline_get_data()内联函数,带有外部链接,属于链接部分 到与 non-inline 函数相同的翻译单元中的静态数据, get_data(),将相同的链接部分归于它自己的静态数据。

编译器采用不同的规则为get_data()生成链接 和 inline_get_data() 分别。 get_data() 是简单的情况,inline_get_data() 是棘手的情况。

为了看到区别,让我们通过在 get_data() 中将 "custom" 替换为 "custom.a" 并将 "custom" 替换为 "custom.b" 来暂时消除 gcc 部分冲突inline_get_data().

现在我们可以用 gcc 编译 source.cpp 并检查相关符号 table 条目:

$ objdump -C -t source.o | grep get_data
0000000000000000 l     O .custom.a  0000000000000004 get_data()::data
0000000000000000 l    d  .text._Z15inline_get_datav 0000000000000000 .text._Z15inline_get_datav
0000000000000000 g     F .text  000000000000000b get_data()
0000000000000000 u     O .custom.b  0000000000000004 inline_get_data()::inline_data
0000000000000000  w    F .text._Z15inline_get_datav 000000000000000b inline_get_data()
000000000000000b g     F .text  000000000000000b other_get_data()

get_data()当然已经做了全局符号(g)和get_data()::data做了 本地符号 (l)。但是 inline_get_data() 已经变成了 weak,既不是全局的也不是本地的 符号 (w) 和 inline_get_data()::inline_data,虽然它在句法上是 block-scope 静态的, 已成为 唯一的全局符号 (u)。这是标准 ELF 的 GNU 扩展 符号绑定要求运行时链接器确保符号在整个过程中是唯一的 运行时链接。

inline_get_data() 的这些不同链接规定中,gcc 正在处理它认为合适的情况 事实上,该函数 与外部链接 内联。函数的事实 is inline 表示必须定义在每个翻译单元中 它被使用,并且它具有 external linkage 的事实意味着所有这些定义 必须地址相同 inline_data()::get_data。因此 block-scope 静态变量必须, 出于链接目的,成为 public 符号。

出于同样的动机,gcc 对属性部分的处理方式不同 custom.a get_data() 的设置和 inline_get_data() 中的属性部分 custom.b。 指定 inline_get_data()::inline_data 一个唯一的全局符号后,它想要 确保该符号的多个定义不会被链接引入 从不同的翻译单元多次复制 custom.b 部分。为此,它 将 GROUP 链接器属性应用于 custom.b:这(跳过详细信息)启用它 生成一个 .section 指令,将 custom.b 分配给一个命名的 section-group 和 指示链接器只保留该节组的一个副本。观察:

$ readelf -t source.o
...
...
[ 7] .custom.a
   PROGBITS               PROGBITS         0000000000000000  0000000000000068  0
   0000000000000004 0000000000000000  0                 4
   [0000000000000002]: ALLOC
[ 8] .custom.b
   PROGBITS               PROGBITS         0000000000000000  000000000000006c  0
   0000000000000004 0000000000000000  0                 4
   [0000000000000202]: ALLOC, GROUP
                              ^^^^^
 ...
 ...

而这是section-type冲突错误的触发器,当custom.acustom.b 是一样的。 Gcc 无法制作既有又没有 GROUP 的部分 属性。

现在如果 get_data()inline_get_data 在不同的翻译单元中定义 编译器无法注意到冲突。那有什么关系呢?会出现什么问题 那种情况?

在那种情况下没有问题,因为在那种情况下 没有 section-type 冲突。 gcc 在 source.o 中生成的一个部分 custom source.o 中的一个部分。它必须 有或没有 GROUP 属性,但无论哪种方式都不会与 other_source.o 中的同名部分 custom 具有相反的状态。这些 是链接器的不同输入部分。它将对输入 custom 部分进行重复数据删除 已 GROUPed,每个 group-name 仅保留其中一个。它不会那样做 输入 custom 部分不是 GROUPed,最后它会合并 它留下的所有输入 custom 部分进入二进制文件中的一个输出 custom 部分, 现在 non-applicable GROUP 属性被丢弃。该输出 custom 部分将 包含 get_data()::data 作为局部符号和 inline_get_data()::inline_data 作为唯一的全局符号。 冲突仅在于编译器遇到关于 source.o(custom) 节是否存在的矛盾规则 应 GROUPed 与否。

为什么clang不符合同样的矛盾呢?这是因为 clang 需要 一个更简单但有点具有外部链接的 内联函数问题的鲁棒性较低的方法 包含静态数据.

坚持 custom.acustom.b 部分的区别,现在让我们用 clang 编译 source.cpp 并检查相关符号和部分特征:

$ objdump -C -t source.o | grep get_data
0000000000000000 l     O .custom.a  0000000000000004 get_data()::data
0000000000000000 l    d  .text._Z15inline_get_datav 0000000000000000 .text._Z15inline_get_datav
0000000000000010 g     F .text  000000000000000b other_get_data()
0000000000000000  w    F .text._Z15inline_get_datav 0000000000000010 inline_get_data()
0000000000000000 g     F .text  0000000000000010 get_data()
0000000000000000  w    O .custom.b  0000000000000004 inline_get_data()::inline_data

这里与 gcc 的输出有一处不同。正如我们所料,clang 没有用 GNU-specific 符号绑定 唯一全局符号 (u) 用于 inline_get_data()::inline_data。 它使它成为一个弱符号,就像 inline_get_data() 本身一样。

对于部分特征,我们有:

$ readelf -t source.o
...
...
[ 8] .custom.a
   PROGBITS               PROGBITS         0000000000000000  0000000000000080  0
   0000000000000004 0000000000000000  0                 4
   [0000000000000002]: ALLOC
[ 9] .custom.b
   PROGBITS               PROGBITS         0000000000000000  0000000000000084  0
   0000000000000004 0000000000000000  0                 4
   [0000000000000002]: ALLOC
...
...

没有区别,所以不冲突。这就是为什么我们可以替换部分名称 custom.acustom.bcustom,每个原始文件,并成功编译。

Clang 依赖于 inline_get_data()::inline_data 的弱绑定 回答每个实现只处理一个这样的符号的要求 进入链接的 inline_get_data()。这将它从 section-type 中保存下来 冲突,但放弃了 gcc 更复杂方法的链接 armour-plating。

你能告诉 gcc 放弃这种健壮性并采用 clang-like 编译方式吗 inline_get_data()?您可以 一点 ,但还不够。你可以给 gcc 选项 -fno-gnu-unique 指示编译器忘记 GNU-specfic unique global 符号绑定。如果你这样做,那么它将 inline_get_data()::inline_data 弱符号,如 clang;但这不会推动它 - 也许它应该 - 放弃 section-grouping 链接符号的属性部分,你仍然会得到 section-type 冲突。我找不到抑制这个的选项 nitty-gritty code-generation 您公认的臭问题代码的行为。

修复

我们已经了解了 gcc section-type 冲突是如何以及为什么由 存在于两个函数定义的同一翻译单元中,一个 与外部内联 linkage,另一个不是inline,每个都属于同一个linkage section 到它的静态数据。

我可以建议两种补救措施,其中一种简单且安全但仅适用于一种变体 的问题,另一个适用总是,但激烈和绝望。

简单安全的

冲突的函数定义可以通过两种方式进入相同的 翻译单位:-

  1. 它们都在同一个源 (.cpp) 文件中定义。
  2. non-inline 函数在源文件中定义,其中包含 header 其中定义了内联函数。

如果你有类型 1 的情况,那么这只是代码编写者的一个错误 源文件以使用外部链接在其中编写内联函数。在这个 如果内联函数是 local 到它的翻译单元,应该是 static。如果是 使 static 然后 gcc 的外部链接作用消失并且 section-type 与他们发生冲突。你说过你无法控制你的代码 属性部分内容是 macro-injected,但其作者应该可以接受 事实上,在源文件中编写内联外部函数而不是 aheader是个错误,愿意改正。

绝命毒师

第 2 类情况的可能性更大。对于这些,据我所知,你的唯一希望 是将程序集 hack 注入到你的 gcc 构建中,以便 gcc 的 .section 指令 关于内联外部函数定义中的属性部分是 在生成 object 代码之前以编程方式编辑为 clang-like。

显然,这样的解决方案仅适用于您知道的某些 gcc 版本集 生成目标 "right pattern of wrong .section directives" 你的纠正技巧,以及使用它的构建系统应该 sanity-check 提前运行gcc版本。

必要的准备工作是修改生成 custom 部分的宏 属性,而不是统一生成节名 .custom 而是生成 序列 .custom.1, custom.2,...,custom.N 在连续调用中 翻译单位。使用内置预处理器 __COUNTER__ 来执行此操作,例如

#define CAT2(x,y) x##y
#define CONCAT(x,y) CAT2(x,y)
#define QUOT(x) #x
#define QUOTE(x) QUOT(x) 
#define SET_SECT() __attribute__((section(QUOTE(CONCAT(.custom.,__COUNTER__)))))

这样做的目的只是让 gcc 像这样预处理代码:

const int* get_data()
{
    SET_SECT()
    static const int data = 123;

    return & data;
}

inline const int* inline_get_data()
{
    SET_SECT()
    static const int inline_data = 123;

    return & inline_data;
}

进入如下代码:

const int* get_data()
{

    __attribute__((section(".custom.0")))
    static const int data = 123;

    return & data;
}

inline const int* inline_get_data()
{

    __attribute__((section(".custom.1")))
    static const int inline_data = 123;

    return & inline_data;
}

不会引起 section-type 冲突。

有了这个并应用到 source.cpp,您可以 assemble 使用 gcc 的文件:

g++ -S source.cpp

并在输出 source.s 中观察到没有问题的部分 custom.0 获取 .section 指令:

.section    .custom.0,"a",@progbits

而有问题的部分 custom.1 得到:

.section    .custom.1,"aG",@progbits,_ZZ15inline_get_datavE11inline_data,comdat

其中 _ZZ15inline_get_datavE11inline_data 是 section-group 名称,comdat 告诉链接器删除这个 section-group.

用 clang 重复此操作并观察相应的指令是:

.section    .custom.0,"a",@progbits
.section    .custom.1,"a",@progbits

除了部分名称外没有区别。

所以驴bly hack 你需要的是一个可以变成以下任何一个的东西:

.section    .custom.0,"a",@progbits
.section    .custom.1,"aG",@progbits,_ZZ15inline_get_datavE11inline_data,comdat

进入:

.section    .custom,"a",@progbits

这可以用sed替换来表示:

s|^\t\.section\t\.custom\.[0-9]\{1,\},"a\(G\)*",@progbits.*$|\t\.section\t\.custom,"a",@progbits|g

对于演示程序,假设对宏设备进行必要的改动, 可以在 makefile 中制定激进的解决方案,如下所示:

CXX ?= g++
SRCS = main.cpp source.cpp
ASMS = $(SRCS:.cpp=.s)
OBJS = $(SRCS:.cpp=.o)
CPPFLAGS = -I.
CXXFLAGS = -fno-gnu-unique

%.o: %.cpp

%.s: %.cpp
%.s: %.cpp
    $(CXX) $(CPPFLAGS) $(CXXFLAGS) -S -o $@ $<

%.o: %.s    
%.o: %.s
    sed -i 's|^\t\.section\t\.custom\.[0-9]\{1,\},"a\(G\)*",@progbits.*$$|\t\.section\t\.custom,"a",@progbits|g' $<
    $(CXX) $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $<

.PHONY: all clean
.INTERMEDIATE: $(ASMS)

all: prog

prog: $(OBJS)
    $(CXX) -o $@ $^

clean:
    rm -f prog $(OBJS) $(ASMS)

从中可以用 gcc 构建一个 ./prog 来满足打印的期望 246 在标准输出上。

注意makefile的三个细节:-

  • 我们需要编写像%.o: %.cpp这样的空模式规则来删除make的内置 这些规则的食谱。
  • sed命令中我们需要*$$作为eol-marker来转义make-expansion的 $.
  • -fno-gnu-unique 在编译器标志中传递给 fill-out clang 模仿。

除了 stop-gap 之外,这不是我想要暴露给开放 user-base 的解决方案。我不会 异议,如果 take-away 来自所有的是:难道没有更好的方法来解决潜在的问题吗?

弄乱自定义部分是一个有点肮脏的话题,因为 gcc 根据 init 值决定数据或 bss,并且不希望您在那个级别弄乱它。

我对 用户数据 的建议是像通常使用数据一样使用它 - 将它放在数据文件中。如果非要用library的话,至少可以让它用自己的library,这样数据就可以在正常的地方了。

一个很小的用户库,可以用-fno-zero-initialized-in-bss构建,将所有用户数据放在数据部分以便于解析。但是不要在你的二进制文件上这样做。

gcc 文档(例如 5.3)说:

Use the section attribute with global variables and not local variables [...]

因此,您需要从函数中提取这些变量:

__attribute__((section(".custom"))) static const int data = 123;
__attribute__((section(".custom"))) static const int inline_data = 123;

const int* get_data()
{
  return &data;
}

inline const int* inline_get_data()
{
  return &inline_data;
}

int main()
{
  (void)get_data();
  (void)inline_get_data();
} 

这与 gcc-5.2 和 clang-3.5.1 编译得很好

我终于找到了满意的解决方案。它实际上只是已知技术的组合。运行时使用一个普通的静态变量,其地址通过内联汇编放入自定义段。在不同的平台(clang、MSVC)上,__attribute__#pragma 可以使用相同的结果,没有 ASM。这个解决方案可以很容易地包装成一个通用的、平台不可知的宏。

const int* get_data()
{
  static const int data = 123;
  __asm__(
    ".pushsection .custom, \"?\", @progbits" "\n"
    ".quad %c0" "\n"
    ".popsection" "\n"
    : : "i"(&data)
  );

  return & data;
}

inline const int* inline_get_data()
{
  static const int inline_data = 123;
  __asm__(
    ".pushsection .custom, \"?\", @progbits" "\n"
    ".quad %c0" "\n"
    ".popsection" "\n"
    : : "i"(&inline_data)
  );

  return & inline_data;
}

int main()
{
  (void) get_data();
  (void) inline_get_data();
  return 0;
}