gpt4 book ai didi

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

转载 作者:行者123 更新时间:2023-12-02 02:06:42 24 4
gpt4 key购买 nike

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

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 4.9失败的方式相同(GCC 5.2对其进行编译)。

如果暂时更改了一个节名称并在生成的程序集中手动进行了固定,则GCC 5.2也可以正常编译。

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

最佳答案

为了整体利益,我将重申您已经知道的内容和@Rumbaruk的内容。
已经引用:gcc的文档明确将section属性的应用限制为全局变量。所以
gcc行为的理想解决方法是一种使gcc不会在不受支持的gcc特定语言的应用程序上驳倒或发出破坏代码的方法
延期。我们无权期望成功或期望成功能够持续重复。

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

出于演示目的,我将使用比实际更为逼真的程序
您已发布,即:

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

就目前而言,该程序在以下情况下会重现节类型冲突错误:
用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()
具有外部链接的内联函数,该函数将链接节赋予属性
在与非内联函数相同的转换单元中转换为静态数据, get_data(),将相同的链接节归于其自己的静态数据。

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

要了解区别,让我们暂时消除gcc节冲突,方法是用 "custom"中的 "custom.a"替换 get_data(),并用 "custom"中的 "custom.b"替换 inline_get_data()

现在,我们可以使用gcc编译 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
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扩展
符号绑定(bind),要求运行时链接程序确保符号在整个过程中都是唯一的
运行时链接。

inline_get_data()的这些不同的链接规定中,gcc认为合适
该功能与外部链接内联。功能的事实
内联表示必须在以下每个翻译单元中定义
使用它,以及它具有外部链接这一事实意味着所有这些定义
必须解决相同的 inline_data()::get_data。因此,大范围静态变量必须
出于链接目的,成为公共(public)符号。

基于相同的动机,gcc处理属性部分中的 custom.a的方式有所不同 get_data()的设置以及 custom.b中的属性部分 inline_get_data()
inline_get_data()::inline_data指定为唯一的全局符号后,它想
确保以下符号的链接不引入该符号的多个定义
多份复制来自不同翻译单元的 custom.b节。为此,它
GROUP链接器属性应用于 custom.b:这(跳过详细信息)启用它
生成 .section指令,该指令将 custom.b分配给命名的节组,并
指示链接器仅保留该节组的一个副本。观察:
$ 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
^^^^^
...
...

custom.acustom.b时,这是段类型冲突错误的触发器
是相同的。 Gcc无法创建既有也没有 GROUP的部分
属性。

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

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

那么为什么c不会陷入同样的​​矛盾呢?这是因为c
一种简单但不那么健壮的方法来解决带有外部链接的内联函数问题
包含静态数据。

继续区分 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特定符号绑定(bind) 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的弱绑定(bind)
回答每个实现只解决一个这样的符号的要求
进入链接的 inline_get_data()的数量。这样可以将其保存为节类型
冲突,但放弃了gcc更为复杂的方法的联系方式。

您能告诉gcc放弃这种健壮性,并采取类似于clang的方式进行编译 inline_get_data()?您可以,但是还不够。您可以给gcc选项 -fno-gnu-unique指示编译器忘记特定于GNU的唯一全局变量
符号绑定(bind)。如果这样做,它将生成 inline_get_data()::inline_data较弱的符号,例如c声;但这并不会插入-也许应该-删除部分分组
符号的属性部分的链接,您仍然会获得部分类型
冲突。我找不到任何办法来抑制这种相当具体的代码生成
您公认的臭味问题代码的行为。

修复

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

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

简单安全一号

冲突的函数定义可以通过两种方式进入同一个
翻译单位:-
  • 两者都在同一源(.cpp)文件中定义。
  • 非内联函数是在源文件中定义的,该文件中包含 header
    定义内联函数。

  • 如果您有类型1的案例,那么对于任何代码而言,这只是愚蠢的事情
    源文件以使用外部链接在其中编码内联函数。在这个
    如果内联函数位于其翻译单元的本地,则应为 static。如果是
    制作 static,然后gcc的外部链接作用消失并且该段类型
    与他们发生冲突。您说过您无法控制其中的代码
    归因于节的内容是宏注入(inject)的,但其作者应易于接受
    在源文件中而不是在源文件中编写内联外部函数的事实
    header 是一个错误,并且愿意纠正它。

    绝望的巨人一首

    2型病例更有可能。就我所知,您的希望是
    是向您的gcc构建中注入(inject)程序集黑客,以便gcc的 .section指令
    关于内联外部函数定义中的属性部分,
    在生成目标代码之前以编程方式将其编辑为类似clang的样式。

    显然,这样的解决方案仅适用于您知道的某些gcc版本
    生成要定位的“错误的 .section指令的正确模式”
    您的纠正性技巧,以及使用该技巧的构建系统应进行的完整性检查
    提前执行gcc版本。

    必要的准备工作是修改生成 custom部分的宏
    属性,以便代替统一生成段名称 .custom,而生成
    顺序调用中的序列 .custom.1custom.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;
    }

    不会引发节类型冲突。

    将其放置到位并应用于 source.cpp,您可以使用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是部分组名称和 comdat告诉链接器对该节组进行重复数据删除。

    用clang重复此操作,观察相应的指令为:
    .section    .custom.0,"a",@progbits
    .section .custom.1,"a",@progbits

    除节名称外没有其他区别。

    因此,您需要的汇编技巧将变成以下两者之一:
    .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的扩展$
  • -fno-gnu-unique在编译器标志中传递,以填充clang模拟。

  • 除了作为权宜之计之外,我不希望向开放的用户群公开此解决方案。我不会
    如果所有这些建议的结果都是反对,那就是:不是有更好的方法来解决潜在的问题吗?

    关于c++ - 内联静态数据导致节类型冲突,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35091862/

    24 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com