C++ 共享库有重复的符号

C++ shared libraries have duplicate symbols

我是 c++ 符号 table 和库的新手,想了解符号 table 的行为。 我们有一个 android 应用程序,它有本地支持。在分析共享库的符号 table 的过程中,我注意到 .so 文件中存在重复的符号。请找到交易品种 table.

的示例列表
0162502c  w   DO .data  00000004  Base        boost::asio::error::get_addrinfo_category()::instance

00aaa4f4  w   DF .text  0000009c  Base        boost::asio::error::get_misc_category()

01626334  w   DO .bss   00000004  Base        guard variable for boost::asio::error::get_misc_category()::instance

00aab4d0  w   DF .text  0000003c  Base        boost::asio::error::detail::misc_category::~misc_category()

00aab368  w   DF .text  0000003c  Base        boost::asio::error::detail::addrinfo_category::~addrinfo_category()

00aab3a4  w   DF .text  00000034  Base        boost::asio::error::detail::addrinfo_category::name() const

00aab3d8  w   DF .text  000000f8  Base        boost::asio::error::detail::addrinfo_category::message(int) const

00aab50c  w   DF .text  0000003c  Base        boost::asio::error::detail::misc_category::~misc_category()

在这里您可以注意到以下符号 "boost::asio::error::detail::misc_category::~misc_category()" 出现了两次。

我想了解为什么我们在符号 table 中得到重复的符号。也有兴趣知道为什么我的应用程序 运行 在有重复符号时很好 [哪个链接器应该理想地抛出重复符号错误] 还想知道在符号 tables 中有重复符号会增加大小"so" 最终导致应用程序大小增加

如果发生这种情况,我如何确保在符号 table 中只获得唯一的条目。 注意:- 我们正在使用 clang

I am noticing duplicate symbols present in .so file

像这样?

$ cat foo.c
int foo(void)
{
    return 42;
}

编译:

$ gcc -Wall -fPIC -c foo.c

检查目标文件中的符号 foo:

$ readelf -s foo.o | grep foo
     1: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS foo.c
     8: 0000000000000000    11 FUNC    GLOBAL DEFAULT    1 foo

一击。

创建一个共享库:

$ gcc -Wall -shared -o libfoo.so foo.o

检查 foo:

的共享库中的符号
$ readelf -s libfoo.so | grep foo
     5: 000000000000057a    11 FUNC    GLOBAL DEFAULT    9 foo
    29: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS foo.c
    44: 000000000000057a    11 FUNC    GLOBAL DEFAULT    9 foo

现在两次点击。

这里没有问题。查看更多图片:

$ readelf -s foo.o | egrep '(foo|Symbol table|Ndx)' 
Symbol table '.symtab' contains 9 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     1: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS foo.c
     8: 0000000000000000    11 FUNC    GLOBAL DEFAULT    1 foo

一个目标文件有一个符号table,它的静态符号table .symtab, linker 使用它来进行 link 时间符号解析。但是:

$ readelf -s libfoo.so | egrep '(foo|Symbol table|Ndx)' 
Symbol table '.dynsym' contains 11 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     5: 000000000000057a    11 FUNC    GLOBAL DEFAULT    9 foo
Symbol table '.symtab' contains 48 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
    29: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS foo.c
    44: 000000000000057a    11 FUNC    GLOBAL DEFAULT    9 foo

一个共享库有两个符号tables:一个静态符号table.symtab,比如 一个目标文件,加上一个动态符号 table、.dynsym,由加载程序用于 运行 时间符号解析。

当您 link 目标文件进入共享库时,linker 默认转录 GLOBAL 符号从他们的 .symtab 到共享的 .symtab .dynsym 库,除了那些在目标文件中有 HIDDEN visibility 的符号 (它们是通过 attribute of hidden visibility 定义的 在编译时)。

目标文件中具有 HIDDEN 可见性的任何 GLOBAL 符号都被转录为 LOCAL 符号 在共享库的 .symtab 中具有 DEFAULT 可见性并且未被转录 完全进入共享库的.dynsym。因此,当共享库被 link 编辑时 其他任何东西,linker 和加载器都看不到编译时 HIDDEN 的全局符号。

但除了隐藏的符号,其中经常有 none,相同的全局符号 将出现在共享库的 .symtab.dynsym table 中。每个定义的符号 出现在两个 table 中的是同一个定义。

稍后OP评论

I took the symbol table by running objdump -T command, which should ideally list symbols present only in dynamic symbol table.

这让我们有了不同的解释,因为 objdump -T 确实只报告了 动态符号 table(如 readelf --dyn-syms)。

注意符号报告了两次:

...
00aab4d0  w   DF .text  0000003c  Base        boost::asio::error::detail::misc_category::~misc_category()
...
00aab50c  w   DF .text  0000003c  Base        boost::asio::error::detail::misc_category::~misc_category()
...

在第 2 列中被 class 化 w(您代码段中的所有其他符号也是如此)。 objdump 的意思是 该符号是 weak.

让我们重新进行观察:

foo.hpp

#pragma once
#include <iostream>

struct foo
{
    explicit foo(int i)
    : _i{i}
    {
        std::cout << __PRETTY_FUNCTION__ << std::endl;
    }
    ~foo()
    {
        std::cout << __PRETTY_FUNCTION__ << std::endl;
    }
    int _i = 0;
};

bar.cpp

#include "foo.hpp"

foo bar()
{
    return foo(2);
}

gum.cpp

#include "foo.hpp"

foo gum()
{
    return foo(1);
}

编译并制作共享库:

$ g++ -Wall -Wextra -c -fPIC bar.cpp gum.cpp
$ g++ -shared -o libbargum.so bar.o gum.o

查看 动态 符号 objdump 来自 struct foo 的报告:

$ objdump -CT libbargum.so | grep 'foo::'
00000000000009bc  w   DF .text  0000000000000046  Base        foo::foo(int)
00000000000009bc  w   DF .text  0000000000000046  Base        foo::foo(int)

构造函数的重复弱导出 foo::foo(int)。就像你一样 注意到了。

尽管打勾。 foo::foo(int) 是 C++ 方法签名,但不是 实际上是 link 人可以识别的 符号 。让我们再做一次,这次 没有拆解:

$ objdump -T libbargum.so | grep 'foo'
00000000000009bc  w   DF .text  0000000000000046  Base        _ZN3fooC1Ei
00000000000009bc  w   DF .text  0000000000000046  Base        _ZN3fooC2Ei

现在我们看到了 linker 看到的符号,不再看到重复: _ZN3fooC1Ei != _ZN3fooC2Ei,虽然两个符号有相同的地址和

$ c++filt _ZN3fooC1Ei
foo::foo(int)
$ c++filt _ZN3fooC2Ei
foo::foo(int)

他们都分解成相同的东西,foo::foo(int)。事实上有5 不同的符号 - _ZN3fooCNEi,对于 1 <= N <= 5 - 分解为 foo::foo(int). (而g++实际上在对象中使用了_ZN3fooC1Ei_ZN3fooC2Ei_ZN3fooC5Ei 文件 bar.ogum.o)。

所以实际上,动态符号中没有重复的符号table: 名称分解映射的偷偷摸摸的多对一性质使它看起来像那样。

但是为什么呢?

恐怕这里的答案太长太复杂了。

执行摘要

GCC C++ 编译器使用了两个弱符号 demangle 以不同的方式相同地引用 global inline class-method,作为 它的库存公式用于在位置独立代码中启用全局内联 class 方法的成功 linkaqe。 这对任何编译器来说都是一个不可忽视的问题,GCC 公式并不是唯一可能的。 Clang有一个不同的 解决方案,确实涉及使用同义但不同的符号,因此不 产生你所见过的虚幻的 "duplication" 符号。