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

时间:2016-01-29 18:48:01

标签: c++ gcc linker elf

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

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

问题归结为这两个变量具有不同的链接(data位于flaggeda部分,inline_data的部分标记为aG)。如果第二个函数没有标记为内联但是模板(GCC 5.2编译它),则GCC 4.9会以相同的方式失败。

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

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

4 个答案:

答案 0 :(得分:13)

为了一般的利益,我会重申你已经知道的和@Rumbaruk的内容 已经引用:gcc的文档明确限制section属性应用于全局变量。所以 gcc行为的理想解决方法是让gcc不受barf限制或在不受支持的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扩展 符号绑定,要求运行时链接程序确保符号在整个符号中是唯一的 运行时链接。

inline_get_data()的这些不同的链接规定中,gcc正在应对它认为合适的情况 事实上该函数与外部链接内联。这个功能的事实 是内联意味着它必须在每个翻译单元中定义 使用它,以及它具有外部链接的事实意味着所有这些定义 必须解决相同的inline_data()::get_data。因此块范围的静态变量必须, 为了联系目的,成为公共符号。

根据同样的动机,gcc与归属部分custom.a的处理方式不同 在get_data()中设置custom.b和归因部分inline_get_data()。 指定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
                              ^^^^^
 ...
 ...

custom.acustom.b时,这是部分类型冲突错误的触发器 是一回事。 Gcc无法创建一个既有又有GROUP的部分 属性。

现在,如果get_data()inline_get_data在不同的翻译单元中定义了 编译器无法注意到冲突。那有什么关系呢?怎么会出错? 那个案子?

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

为什么然后没有因为同样的矛盾而犯规?这是因为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无济于事 u的GNU特定符号绑定唯一全局符号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.a的原因 和原始custom.b custom,并成功编译。

Clang依赖于inline_get_data()::inline_datainline_get_data()的弱约束 回答每个实现仅解决一个这样的符号的要求 进入联系的inline_get_data()。这样可以将其从节类型中保存下来 冲突,但放弃了gcc更复杂方法的联动装甲。

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

<强>修复

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

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

简单安全的

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

  1. 它们都在同一来源(static)文件中定义。
  2. 非内联函数在包含标题的源文件中定义 内联函数的定义。
  3. 如果你有类型1的情况,那么对于任何编码的人来说,这只是一个蠢事 源文件使用外部链接对其中的内联函数进行编码。在这 如果内联函数是 local 到它的翻译单元,应该是static。如果是 制作.section然后gcc的外部连接消耗消失,而节类型消失 与他们发生冲突。你说你无法控制你的代码 属性部分是宏注入的,但它的作者应该是接受的 在源文件中编写内联外部函数而不是 标题是一个大错,并愿意纠正它。

    极度绝望的人

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

    显然,这样的解决方案仅适用于您知道的某些gcc版本 生成错误custom指令的正确模式&#34;在哪个目标 你的纠正黑客,以及使用它的构建系统应该进行健全性检查 提前运行gcc版本。

    必要的初步是修改生成.custom部分的宏 属性,以便它不是统一生成节名.custom.1而是生成 序列custom.2custom.N,...,__COUNTER__在a中的连续调用 翻译单位。使用内置#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__))))) 的预处理器来执行此操作,例如

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

    这一点只是让gcc预处理代码如:

    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

    不会引发部分类型冲突。

    有了这个并应用于g++ -S source.cpp ,您可以使用gcc:

    组装文件
    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是部门组名称和.section .custom.0,"a",@progbits .section .custom.1,"a",@progbits 告诉链接器对此节组进行重复数据删除。

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

    .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 替换表示:

    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)
    

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

    ./prog

    可以使用gcc构建246,以满足打印期望%.o: %.cpp 在stdout上。

    请注意makefile的三个细节: -

    • 我们需要编写像sed这样的空模式规则来删除内置的make 这些规则的食谱。
    • *$$命令中,我们需要$作为eol标记来逃避扩展 -fno-gnu-unique
    • {{1}}在编译器标志中传递以填写clang模仿。

    这不是一个我想要暴露给开放用户群的解决方案,除非是止损。我没赢 如果从所有这些中获取的话是异议的话:是不是有更好的方法可以解决潜在的问题?

答案 1 :(得分:4)

与自定义部分混淆是一个肮脏的主题,因为gcc根据init值决定数据或bss,并且不希望你在那个级别搞乱它。

我建议用户数据是在正常使用数据时使用它 - 将其放入数据文件中。如果你坚持使用库,你至少可以让它使用自己的库,然后数据可以在正常的地方。

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

答案 2 :(得分: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;
}

答案 3 :(得分:0)

gcc文档(例如5.3)说:

  

将section属性与全局变量一起使用,而不是局部变量[...]

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

$jsonRequestURL = 'http://uat.xendpay.com/partner/api/1.0/{partner_key}/quote?countryFrom=PL&countryTo=GB&currencyFrom=PLN&currencyTo=GBP&deliveryType=BANK_TRANSFER&paymentMethod=banktransferlocal&amount=1000.00&amountCurrency=PLN&indent=true';
json_decode ( file_get_contents ( $jsonRequestURL ) );

这与gcc-5.2和clang-3.5.1

编译得很好