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

2022-01-11 00:00:00 gcc linker c++ elf

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

I want to put some user defined data into a custom section to be read by the application and an offline analyser at the same time. Assuming the following sample:

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__ 被 对应的编译指示.

The value of data and inline_data will appear in the section .custom. Clang compiles this example and produces the correct result, just as MSVC does, when the __attributes__ are replaced by corresponding pragmas.

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

Unfortunately, GCC 5.2 gives the following error:

error: inline_data causes a section type conflict with data

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

The problem boils down to the fact that the two variables have different linkage (data is in a section flagged with a, the section of inline_data is flagged with aG). GCC 4.9 fails the same way if the second function is not marked as inline but is a template (GCC 5.2 compiles that).

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

GCC 5.2 also compiles fine if one section name is temporarily changed and manually fixed in the generated assembly.

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

Is there any known workaround for this issue? I have no control over the function signature,the *data variables are produced by a macro provided by me, and they can appear anywhere.

推荐答案

为了总体利益,我将重申你已经知道的和 @Rumbaruk已经引用:gcc 的文档明确将 section 属性的应用限制为 global 变量.所以gcc 行为的所需解决方法是让 gcc 在不受支持的特定于 gcc 的语言的应用程序上不吐槽或发出损坏的代码的方法延期.我们无权期望成功或期望成功始终如一地重复.

For the general good, I'll reiterate what you already know and what @Rumbaruk has already cited: gcc's documentation explicitly restricts the application of the section attribute to global variables. So the desired workaround for gcc's behaviour is a way to get gcc not to barf or emit broken code on an unsupported application of a gcc-specific language extension. We've no right to expect success or to expect a success to be consistently repeatable.

这里有一个关于 gcc 如何以及为什么会产生节类型冲突的详细解释编译错误和clang没有.如果不耐烦,请滚动到修复,但不要期待银弹.

Here comes a long explanation of how and why gcc produces the section-type conflict compilation error and clang doesn't. Scroll to Fixes if impatient, but don't expect a silver bullet.

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

For demonstration purposes I'll work with a slightly more lifelike program than you've posted, viz:

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

ma??in.cpp

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

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

就目前而言,该程序在以下情况下重现了节类型冲突错误使用 gcc 5.2 编译:

As it stands, this program reproduces the section-type conflict error when compiled with 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 (3.6/3.7) has no complaints:

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

gcc 阻碍的根源在于 inline_get_data() 是inline 函数,带有 external links 属性的链接部分将其静态数据与 non-inline 函数放在同一翻译单元中,get_data(),将相同的链接部分归因于它自己的静态数据.

The root of gcc's obstructiveness is the fact that inline_get_data() is an inline function with external linkage that attributes a linkage section to its static data in the same translation unit as a non-inline function, get_data(), that attributes the same linkage section to its own static data.

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

The compiler adopts different rules to generate the linkage for get_data() and inline_get_data() respectively. get_data() is the simple case, inline_get_data() is tricky case.

要查看差异,让我们通过在 get_data()"custom" 替换为 "custom.a" 来暂时解除 gcc 部分冲突> 并在 inline_get_data() 中将 "custom" 替换为 "custom.b".

To see the difference, let's temporarily disarm the gcc section conflict by replacing "custom" with "custom.a" in get_data() and replacing "custom" with "custom.b" in inline_get_data().

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

Now we can compile source.cpp with gcc and inspect the relevant symbol table entries:

$ 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,虽然它在语法上是块范围静态的,已成为唯一的全局符号 (u).这是标准 ELF 的 GNU 扩展要求运行时链接器确保符号在整个系统中唯一的符号绑定运行时链接.

get_data(), of course, has been made a global symbol (g) and get_data()::data made a local symbol (l). But inline_get_data() has been made a weak, neither global nor local symbol (w) and inline_get_data()::inline_data, although it is syntactically block-scope static, has been made a unique global symbol (u). This is a GNU extension of the standard ELF symbol bindings requiring the runtime linker to ensure that the symbol is unique in the entire runtime linkage.

inline_get_data() 的这些不同链接规定中,gcc 会按照它认为合适的方式进行处理事实上,该函数与外部链接内联.函数的事实is inline 表示必须在每个翻译单元中定义它被使用,并且它具有外部链接的事实意味着所有这些定义必须处理相同的 inline_data()::get_data.因此,块范围的静态变量必须,出于链接目的,成为公共符号.

In these different linkage stipulations for inline_get_data(), gcc is coping as it deems fit with the fact that the function is inline with external linkage. The fact that the function is inline means it must be defined in each translation unit in which it is used, and the fact that it has external linkage means that all those definitions must address the same inline_data()::get_data. Thus the block-scope static variable must, for linkage purposes, become a public symbol.

出于同样的动机,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 和指示链接器仅保留该节组的一个副本.观察:

From the same motivation, gcc deals differently with the attributed section custom.a in the setting of get_data() and the attributed section custom.b in inline_get_data(). Having designated inline_get_data()::inline_data a unique global symbol, it wants to ensure that multiple definitions of this symbol are not introduced by the linkage of multiple copies the section custom.b from different translation units. To that end, it applies the GROUP linker attribute to custom.b: this (skipping details) enables it to generate a .section directive that assigns custom.b to a named section-group and directs the linker to retain only one copy of that section group. Observe:

$ 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 的部分属性.

And this is the trigger of the section-type conflict error, when custom.a and custom.b are one and the same. Gcc can't make a section that both has and does not have the GROUP attribute.

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

Now if get_data() and inline_get_data were defined in different translation units the compiler could not notice the conflict. So what does it matter? What would go wrong in that case?

在这种情况下没有任何问题,因为在这种情况下没有节类型冲突.source.o 中由 gcc 生成的节 custom 是 in source.o 节.它必须有或没有 GROUP 属性,但无论哪种方式都没有冲突other_source.o 中的同名部分 custom 具有相反的状态.这些是链接器的不同输入部分.它将删除输入 custom 部分的重复数据GROUPed,每个组名只保留其中一个.它不会那样做输入的 custom 部分不是 GROUPed,最后它会合并剩下的所有输入 custom 部分都放在二进制文件中的一个输出 custom 部分中,抛弃了现在不适用的 GROUP 属性.该输出 custom 部分将包含 get_data()::data 作为本地符号和 inline_get_data()::inline_data 作为唯一的全局符号.冲突仅在于编译器遇到关于是否 source.o(custom) 节的矛盾规则是否应GROUP编辑.

Nothing goes wrong in that case, because in that case there is no section-type conflict. A section custom generated by gcc in source.o is a section in source.o. It must either have or not have the GROUP attribute, but either way there is no conflict with a section custom of the same name in other_source.o having the opposite status. These are distinct input sections for the linker. It will deduplicate the input custom sections that are GROUPed, retaining only one of them per group-name. It will not do that with the input custom sections that are not GROUPed, and finally it will merge all the input custom sections it is left with into one output custom section in the binary, with the now non-applicable GROUP attribute(s) ditched. That output custom section will contain get_data()::data as a local symbol and inline_get_data()::inline_data as a unique global symbol. The conflict consists solely in the compiler encountering contradictory rules as to whether section source.o(custom) shall be GROUPed or not.

那么,为什么clang 不会遇到同样的矛盾呢?这是因为clang需要解决带有外部链接的内联函数问题的一种更简单但不太健壮的方法包含静态数据.

Why then doesn't clang fall foul of the same contradiction? It's because clang takes a simpler but somewhat less robust approach to the problem of an inline function with external linkage containing static data.

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

Sticking with the differentiation of sections custom.a and custom.b, let's now compile source.cpp with clang and inspect the relevant symbol and section characteristics:

$ 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 没有用inline_get_data()::inline_data 的 GNU 特定符号绑定 唯一全局符号 (u).它使它成为一个弱符号,就像 inline_get_data() 本身一样.

There's one difference there from the output of gcc. As we might expect, clang does not avail of the GNU-specific symbol binding unique global symbol (u) for inline_get_data()::inline_data. It makes that a weak symbol, like inline_get_data() itself.

对于我们拥有的部分特征:

And for the section traits we have:

$ 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,每个原始文件,并成功编译.

No difference, so no conflict. That's why we can replace the section names custom.a and custom.b with custom, per original, and successfully compile.

Clang 依赖于其 inline_get_data()::inline_data 的弱绑定到回答每个实现只处理一个这样的符号的要求inline_get_data() 进入链接.这将其从部分类型中保存冲突,但放弃了 gcc 更复杂方法的连杆装甲.

Clang relies on its weak binding of inline_get_data()::inline_data to answer the requirement that just one such symbol is addressed by every implementation of inline_get_data() that gets into the linkage. This saves it from a section-type conflict but forgoes the linkage armour-plating of gcc's more complicated approach.

你能告诉 gcc 放弃这种健壮性并采用类似clang 的方式进行编译吗?inline_get_data()?你可以一点,但还不够.你可以给 gcc 选项-fno-gnu-unique 指示编译器忘记 GNU-specfic unique global符号绑定.如果你这样做,那么它将生成 inline_get_data()::inline_data一个微弱的符号,如铿锵;但这不会推动它 - 也许应该 - 放弃部分分组符号的属性部分的链接,你仍然会得到部分类型冲突.我找不到任何选项来抑制这种相当详细的代码生成您公认的臭问题代码的行为.

Can you tell gcc to forgo that robustness and take a clang-like way with compiling inline_get_data()? You can a bit, but not enough. You can give gcc the option -fno-gnu-unique to instruct the compiler to forget the GNU-specfic unique global symbol binding. If you do that, then it will make inline_get_data()::inline_data a weak symbol, like clang; but that won't nudge it - maybe it should - to drop the section-grouping linkage for the attributed section of the symbol, and you'll still get the section-type conflict. I can find no option to inhibit this rather nitty-gritty code-generation behaviour for your admittedly smelly problem code.

修复

我们已经了解了 gcc 节类型冲突是如何以及为什么是由两个函数的定义存在于同一个翻译单元中,一个与外部函数内联链接,另一个不内联,每个都属性相同的链接部分到它的静态数据.

We've seen how and why the gcc section-type conflict is provoked specifically by the presence in the same translation unit of definitions of two functions, one inline with external linkage, the other not inline, each of which attributes the same linkage section to its static data.

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

I can suggest two remedies, one of them simple and safe but applicable only to one variation of the problem, the other applicable always, but drastic and desperate.

简单安全的

有两种方法可以使相互冲突的函数定义进入相同的翻译单位:-

There are two ways in which the conflicting function definitions can get into the same translation unit:-

  1. 它们都在同一个源 (.cpp) 文件中定义.
  2. 非内联函数是在一个源文件中定义的,该文件包含一个头文件定义了内联函数.

如果您有类型 1 的案例,那么这只是编码者的一个愚蠢行为使用外部链接在其中编写内联函数的源文件.在这个如果内联函数对其翻译单元是 local 并且应该是 static.如果是使 static 然后 gcc 的外部链接作用消失并且节类型与他们发生冲突.你说过你无法控制你的代码属性部分的东西是宏注入的,但它的作者应该接受事实上,在源文件中编写内联外部函数而不是标题是一个错误,愿意纠正它.

If you have cases of type 1, then it is just a goof on the part of whoever codes the source file to code an inline function in it with external linkage. In this case the inline function is local to its translation unit and should be static. If it is made static then gcc's external linkage exertions disappear and the section-type conflict with them. You have said you have no control over the code in which your attributed section stuff is macro-injected, but its authors should be receptive of the fact that writing inline external functions in a source file rather than a header is a blunder and be willing to correct it.

极度绝望的人

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

Cases of type 2 are more likely. For these, as far as I can see, your one hope is to inject an assembly hack into your gcc build so that gcc's .section directives regarding the attributed section in the inline external function definitions are programmatically edited to be clang-like before the object code is generated.

显然,这样的解决方案仅适用于您知道的某些 gcc 版本生成目标的错误 .section 指令的正确模式"您的纠正黑客,以及使用它的构建系统应该对提前运行 gcc 版本.

Clearly, such a solution will be viable only for some set of gcc versions that you know to generate the "right pattern of wrong .section directives" on which to target your corrective hack, and a build system that uses it should sanity-check the operative gcc version in advance.

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

A necessary preliminary is to modify your macro that generates the custom section attributes so that instead of uniformly generating the section name .custom it instead generates the sequence .custom.1, custom.2,...,custom.N at successive invocations in a translation unit. Use the preprocessor builtin __COUNTER__ to do this, e.g.

#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 预处理代码,例如:

The point of this is just to let gcc preprocess code like:

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

不会引起节类型冲突.

有了这个并应用到 source.cpp,你可以用 gcc 组装文件:

With this in place and applied to source.cpp, you can assemble the file with gcc:

g++ -S source.cpp

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

and observe in the output source.s that the unproblematic section custom.0 get the .section directive:

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

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

whereas the problematic section custom.1 gets:

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

其中 _ZZ15inline_get_datavE11inline_data 是节组名称,comdat告诉链接器去重复这个节组.

where _ZZ15inline_get_datavE11inline_data is the section-group name and comdat tells the linker to deduplicate this section-group.

用 clang 重复这个,观察对应的指令是:

Repeat this with clang and observe that the corresponding directives are:

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

除了部分名称没有区别.

with no difference other than the section name.

因此,您需要的组装技巧可以变成以下任何一种:

So the assembly hack you require is one that will turn the like of either:

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

进入:

.section    .custom,"a",@progbits

这可以通过 sed 替换来表示:

This can be expressed by a sed substitution:

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

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

For the demo program, assuming the necessary changes to the macro apparatus, the Drastic solution can be formulated in a makefile like so:

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|^	.section	.custom.[0-9]{1,},"a(G)*",@progbits.*$$|	.section	.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)

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

from which a ./prog can be built with gcc that fulfils the expectation of printing 246 on stdout.

注意makefile的三个细节:-

Notice three details of the makefile:-

  • 我们需要编写像 %.o: %.cpp 这样的空模式规则来删除 make 的内置这些规则的食谱.
  • sed 命令中,我们需要 *$$ 作为 eol-marker 来逃避 make-expansion$.
  • -fno-gnu-unique 在编译器标志中传递以填充 clang 模仿.
  • We need to write empty pattern rules like %.o: %.cpp to delete make's inbuilt recipes for those rules.
  • In the sed command we need *$$ as the eol-marker to escape make-expansion of $.
  • -fno-gnu-unique is passed in the compiler flags to fill-out the clang mimicry.

除了权宜之计之外,这不是我希望向开放的用户群公开的解决方案.我不会如果从所有问题中得出的结论是:难道没有更好的方法来解决问题的根本吗?

This is not a solution I'd want expose to an open user-base except as stop-gap. I won't demur if the take-away from all of it is: Isn't there's a better way of attacking the underlying the problem?

相关文章