我尝试构建一个链接到各种共享库和静态库的可执行文件。事实证明,两个静态库都定义相同的符号,这会导致多定义链接器错误。我的可执行文件不使用此符号,因此它并不是真正的问题。

我可以通过添加--allow-multiple-definitions标志来避免该错误,但这似乎是一个核选项。如果我尝试使用多次定义的符号,我希望链接器抱怨。

有没有办法告诉链接器“仅在使用符号的情况下才对多个定义进行抱怨”?或者,也可以告诉它“从lib ABC忽略符号XYZ”。我正在Linux上使用g++进行开发。

最佳答案

您可能有一个问题的变体或另一个变体,
取决于您尚未考虑的相关事实。或者,可能您将两者混合使用,所以我将逐步介绍每种变体的解决方案。

您应该熟悉静态库的性质以及如何在链接中使用它们,
as summarised here

多余的全局符号变体

这是几个源文件和头文件:

one.cpp

#include <onetwo.h>

int clash = 1;

int get_one()
{
    return clash;
}

two.cpp
#include <onetwo.h>

int get_two()
{
    return 2;
}

onetwo.h
#pragma once

extern int get_one();
extern int get_two();

这些已内置到静态库libonetwo.a
$ g++ -Wall -Wextra -pedantic -I. -c one.cpp two.cpp
$ ar rcs libonetwo.a one.o two.o

其预期的API在onetwo.h中定义

类似地,其他一些源文件和 header
内置到静态API的libfourfive.a中,该API的预期API
fourfive.h中定义

four.cpp
#include <fourfive.h>

int clash = 4;

int get_four()
{
    return clash;
}

五.cpp
#include <fourfive.h>

int get_five()
{
    return 5;
}

fourfive.h
#pragma once

extern int get_four();
extern int get_five();

这是依赖于两个库的程序的源代码:

prog.cpp
#include <onetwo.h>
#include <fourfive.h>

int main()
{
    return get_one() + get_four();
}

我们尝试这样构建:
$ g++ -Wall -Wextra -pedantic -I. -c prog.cpp
$ g++ -o prog prog.o -L. -lonetwo -lfourfive
/usr/bin/ld: ./libfourfive.a(four.o):(.data+0x0): multiple definition of `clash'; ./libonetwo.a(one.o):(.data+0x0): first defined here
collect2: error: ld returned 1 exit status

遇到符号clash的名称冲突,因为它是在两个
链接所需的目标文件one.ofour.o:
$ readelf -s libonetwo.a libfourfive.a | egrep '(File|Symbol|OBJECT|FUNC)'
File: libonetwo.a(one.o)
Symbol table '.symtab' contains 11 entries:
     9: 0000000000000000     4 OBJECT  GLOBAL DEFAULT    3 clash
    10: 0000000000000000    16 FUNC    GLOBAL DEFAULT    1 _Z7get_onev
File: libonetwo.a(two.o)
Symbol table '.symtab' contains 10 entries:
     9: 0000000000000000    15 FUNC    GLOBAL DEFAULT    1 _Z7get_twov
File: libfourfive.a(four.o)
Symbol table '.symtab' contains 11 entries:
     9: 0000000000000000     4 OBJECT  GLOBAL DEFAULT    3 clash
    10: 0000000000000000    16 FUNC    GLOBAL DEFAULT    1 _Z8get_fourv
File: libfourfive.a(five.o)
Symbol table '.symtab' contains 10 entries:
     9: 0000000000000000    15 FUNC    GLOBAL DEFAULT    1 _Z8get_fivev

我们自己的代码clash中未引用问题符号prog.(cpp|o)。您想知道:



不,没有,但这并不重要。不会从one.o中提取libonetwo.a并链接到程序(如果链接程序不需要它来解析某些符号)。需要它
解决get_one。同样,它仅链接了four.o,因为解析get_four时需要使用它。
因此clash的冲突定义在链接中。尽管prog.o不使用clash
它的确使用了get_one,后者使用clash,并且打算在clash中使用one.o的定义。
同样,prog.o使用get_four,后者使用clash并打算在four.o中使用不同的定义。

即使每个库和程序都未使用clash,它的定义事实
必须链接到程序中的多个目标文件中,意味着该程序将包含
它有多种定义,只有--allow-multiple-definitions允许。

鉴于此,您还将看到:



通常不会飞。如果我们可以告诉链接器忽略(说)clash的定义
four.o中并将符号解析为one.o(唯一的其他候选者)中的定义,然后get_four()将在我们的程序中返回1而不是4。那
实际上是--allow-multiple-definitions的效果,因为它导致了第一个定义
在要使用的链接中。

通过检查libonetwo.a(或libfourfive.a)的源代码,我们可以相当自信地发现根
问题的原因。符号clash已留有外部链接,其中
它只需要内部链接,因为它没有在关联中声明
头文件,在库中无处引用,除了
在定义它的文件中。有问题的源文件应该写为:

one_good.cpp
#include <onetwo.h>

namespace {
    int clash = 1;
}

int get_one()
{
    return clash;
}

four_good.cpp
#include <fourfive.h>

namespace {
    int clash = 4;
}

int get_four()
{
    return clash;
}

一切都会很好:
$ g++ -Wall -Wextra -pedantic -I. -c one_good.cpp four_good.cpp
$ readelf -s one_good.o four_good.o | egrep '(File|Symbol|OBJECT|FUNC)'
File: one_good.o
Symbol table '.symtab' contains 11 entries:
     5: 0000000000000000     4 OBJECT  LOCAL  DEFAULT    3 _ZN12_GLOBAL__N_15clashE
    10: 0000000000000000    16 FUNC    GLOBAL DEFAULT    1 _Z7get_onev
File: four_good.o
Symbol table '.symtab' contains 11 entries:
     5: 0000000000000000     4 OBJECT  LOCAL  DEFAULT    3 _ZN12_GLOBAL__N_15clashE
    10: 0000000000000000    16 FUNC    GLOBAL DEFAULT    1 _Z8get_fourv

$ g++ -o prog prog.o one_good.o four_good.o
$./prog; echo $?
5

由于这样无法重写源代码,因此我们必须将目标文件修改为
同样的效果。该工具是 objcopy
$ objcopy --localize-symbol=clash libonetwo.a libonetwo_good.a

此命令与运行具有相同的效果:
$ objcopy --localize-symbol=clash orig.o fixed.o

在每个目标文件libonetwo(orig.o)上输出固定的目标文件fixed.o
并将所有fixed.o文件归档到新的静态库libonetwo_good.a中。和--localize-symbol=clash对每个目标文件的作用是更改
符号clash(如果已定义),从外部(GLOBAL)到内部(LOCAL):
$ readelf -s libonetwo_good.a | egrep '(File|Symbol|OBJECT|FUNC)'
File: libonetwo_good.a(one.o)
Symbol table '.symtab' contains 11 entries:
     9: 0000000000000000     4 OBJECT  LOCAL  DEFAULT    3 clash
    10: 0000000000000000    16 FUNC    GLOBAL DEFAULT    1 _Z7get_onev
File: libonetwo_good.a(two.o)
Symbol table '.symtab' contains 10 entries:

现在,链接器无法在LOCAL中看到clashlibonetwo_good.a(one.o)定义。

这足以避免多重定义错误,但是由于libfourfive.a具有
同样的缺陷,我们也会修复:
$ objcopy --localize-symbol=clash libfourfive.a libfourfive_good.a

然后,我们可以使用固定库成功重新链接prog
$ g++ -o prog prog.o -L. -lonetwo_good -lfourfive_good
$ ./prog; echo $?
5

全局符号死锁变体

在这种情况下,libonetwo.a的源和 header 是:

one.cpp
#include <onetwo.h>
#include "priv_onetwo.h"

int inc_one()
{
    return inc(clash);
}

two.cpp
#include <onetwo.h>
#include "priv_onetwo.h"

int inc_two()
{
    return inc(clash + 1);
}

priv_onetwo.cpp
#include "priv_onetwo.h"

int clash = 1;

int inc(int i)
{
    return i + 1;
}

priv_onetwo.h
#pragma once

extern int clash;
extern int inc(int);

onetwo.h
#pragma once

extern int inc_one();
extern int inc_two();

对于libfourfive.a,它们是:

four.cpp
#include <fourfive.h>
#include "priv_fourfive.h"

int dec_four()
{
    return dec(clash);
}

五.cpp
#include <fourfive.h>
#include "priv_fourfive.h"

int dec_five()
{
    return dec(clash + 1);
}

priv_fourfive.cpp
#include "priv_fourfive.h"

int clash = 4;

int dec(int i)
{
    return i - 1;
}

priv_fourfive.h
#pragma once

extern int clash;
extern int dec(int);

fourfive.h
#pragma once

extern int dec_four();
extern int dec_five();

这些库中的每个库都定义了一些常见的内部构造
在源文件中-(priv_onetwo.cpp | priv_fourfive.cpp)
-这些内部信息是通过私有(private) header 全局声明用于构建库的
-(priv_onetwo.h | priv_fourfive.h)-不随库一起分发。
它们是未记录的符号,但是仍然暴露给链接器。

现在每个库中有两个文件使未定义(UND)引用
到在另一个文件中定义的全局符号clash:
$ readelf -s libonetwo.a libfourfive.a | egrep '(File|Symbol|OBJECT|FUNC|clash)'
File: libonetwo.a(one.o)
Symbol table '.symtab' contains 13 entries:
     9: 0000000000000000    23 FUNC    GLOBAL DEFAULT    1 _Z7inc_onev
    10: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND clash
File: libonetwo.a(two.o)
Symbol table '.symtab' contains 13 entries:
     9: 0000000000000000    26 FUNC    GLOBAL DEFAULT    1 _Z7inc_twov
    10: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND clash
File: libonetwo.a(priv_onetwo.o)
Symbol table '.symtab' contains 11 entries:
     9: 0000000000000000     4 OBJECT  GLOBAL DEFAULT    2 clash
    10: 0000000000000000    19 FUNC    GLOBAL DEFAULT    1 _Z3inci
File: libfourfive.a(four.o)
Symbol table '.symtab' contains 13 entries:
     9: 0000000000000000    23 FUNC    GLOBAL DEFAULT    1 _Z8dec_fourv
    10: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND clash
File: libfourfive.a(five.o)
Symbol table '.symtab' contains 13 entries:
     9: 0000000000000000    26 FUNC    GLOBAL DEFAULT    1 _Z8dec_fivev
    10: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND clash
File: libfourfive.a(priv_fourfive.o)
Symbol table '.symtab' contains 11 entries:
     9: 0000000000000000     4 OBJECT  GLOBAL DEFAULT    2 clash
    10: 0000000000000000    19 FUNC    GLOBAL DEFAULT    1 _Z3deci

这次我们的程序来源是:

prog.cpp
#include <onetwo.h>
#include <fourfive.h>

int main()
{
    return inc_one() + dec_four();
}

和:
$ g++ -Wall -Wextra -pedantic -I. -c prog.cpp
$ g++ -o prog prog.o -L. -lonetwo -lfourfive
/usr/bin/ld: ./libfourfive.a(priv_fourfive.o):(.data+0x0): multiple definition of `clash'; ./libonetwo.a(priv_onetwo.o):(.data+0x0): first defined here
collect2: error: ld returned 1 exit status
clash再次被乘法定义。要在inc_one中解析main
链接器需要one.o,这使其不得不解析inc,这使得它需要priv_onetwo.o,其中包含clash的第一个定义。要在dec_four中解析main
链接器需要four.o,这使其不得不解析dec,这使得它需要priv_fourfive.o,其中包含clash的竞争定义。

在这种情况下,clash具有外部链接不是两个库中的编码错误。
它需要具有外部链接。在任一版本中使用clash本地化objcopy的定义libonetwo.a(priv_onetwo.o)libfourfive.a(priv_fourfive.o)将不起作用。如果我们这样做
链接将成功,但会输出错误的程序,因为链接器将解析clash到另一个目标文件中存在的一个GLOBAL定义:然后是dec_four()将在程序中返回0而不是3,dec_five()将返回1而不是4;否则inc_one()将返回5,而inc_two()将返回6。
如果我们将这两个定义都本地化,那么在列表中将找不到clash的定义。
链接prog以满足one.ofour.o中的引用,并且对clash的 undefined reference 将失败

这次,objcopy再次得到救援,但是具有不同的选项1:
$ objcopy --redefine-sym clash=clash_onetwo libonetwo.a libonetwo_good.a

该命令的作用是创建一个新的静态库libonetwo_good.a
包含与libonetwo.a中成对相同的新对象文件,
除了符号clash已经到处都被clash_onetwo代替:
$ readelf -s libonetwo_good.a | egrep '(File|Symbol|clash)'
File: libonetwo_good.a(one.o)
Symbol table '.symtab' contains 13 entries:
    10: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND clash_onetwo
File: libonetwo_good.a(two.o)
Symbol table '.symtab' contains 13 entries:
    10: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND clash_onetwo
File: libonetwo_good.a(priv_onetwo.o)
Symbol table '.symtab' contains 11 entries:
     9: 0000000000000000     4 OBJECT  GLOBAL DEFAULT    2 clash_onetwo

我们将使用libfourfive.a进行相应的操作:
$ objcopy --redefine-sym clash=clash_fourfive libfourfive.a libfourfive_good.a

现在,我们可以再次进行以下操作:
$ g++ -o prog prog.o -L. -lonetwo_good -lfourfive_good
$ ./prog; echo $?
5

在这两种解决方案中,如果存在,请使用的修复程序The Superflous Globals Symbols Variant
尽管已修复全局符号死锁变体,但您已经拥有了多余的全局变量
也可以。篡改目标文件永远是不可取的
在编译和链接之间;它只能是不可避免的,也可以是邪恶的一种。但
如果要篡改它们,请定位一个本不应该是全局的全局符号
与将符号名称更改为
没有源代码。

[1]如果您想将objcopy与任何选项参数一起使用,请不要忘记
是C++目标文件中的符号,您必须使用
C++标识符然后映射到该符号。在此演示代码中,碰巧的是
C++标识符clash也是clash。但是如果完全合格的识别者
onetwo::clash所示,其错误名称为_ZN6onetwo5clashE,由nmreadelf。相反,如果您希望使用objcopy来更改_ZN6onetwo5clashE
一个目标文件到一个符号,它将作为onetwo::klash分解,然后该符号将
_ZN6onetwo5klashE

10-05 20:44
查看更多