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

时间:2019-02-18 15:18:04

标签: android c++11 android-ndk .so

我是C ++符号表和库的新手,想了解符号表的行为。 我们正在使用一个具有本地支持的android应用程序。在分析共享库的符号表的过程中,我注意到.so文件中存在重复的符号。请找到符号表的示例列表。

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()”出现了两次。

我想了解为什么我们在符号表中得到重复的符号。还想知道为什么当有重复的符号时我的应用程序运行正常[理想的链接器应该抛出重复的符号错误]也想知道在符号表中重复符号会增加“ so”的大小,最终导致应用程序的大小

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

1 个答案:

答案 0 :(得分:2)

  

我注意到.so文件中存在重复的符号

喜欢吗?

$ 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

现在有2首。

这里没有错。看到更多图片:

$ 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

一个目标文件有一个符号表,其静态符号表.symtab, 链接器将其用于链接时符号解析。但是:

$ 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

一个共享库具有两个符号表:一个静态符号表.symtab,例如 目标文件,加上动态符号表.dynsym,供加载程序用于运行时符号解析。

将对象文件链接到共享库时,默认情况下,链接器会转录 GLOBAL个符号从它们的.symtab到共享的.symtab .dynsym 库,但目标文件中具有HIDDEN visibility的那些符号除外 (它们是通过用attribute of hidden visibility定义的 编译时)。

目标文件中任何具有GLOBAL可见性的HIDDEN符号都将被转录为LOCAL符号 对共享库的DEFAULT具有.symtab的可见性,并且不会被转录 进入共享库的.dynsym。因此,当共享库与 其他任何事情,链接器和加载器都看不到编译时HIDDEN的全局符号。

但是除了隐藏符号(通常不包含这些符号)之外,它们是相同的全局符号 将显示在共享库的.symtab.dynsym表中。每个定义的符号 两个表中出现的地址具有相同的定义。

稍后,OP评论

  

我通过运行objdump -T命令获取了符号表,理想情况下应该列出仅在动态符号表中存在的符号。

这引导我们做出另一种解释,因为objdump -T确实仅报告了 动态符号表(如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列中被归类为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的{​​em> dynamic 符号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)的弱导出重复。就像你一样 注意到了。

不过还是a一息。 foo::foo(int)是C ++方法签名,但不是 实际上是链接器可以识别的符号。这次我们再做一次 无需拆包:

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

现在,我们看到链接器看到的符号,并且不再可见重复项: _ZN3fooC1Ei!= _ZN3fooC2Ei,尽管两个符号都有相同的地址和

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

它们都分解为同一事物foo::foo(int)。实际上有5 不同符号-_ZN3fooC N Ei,表示1 <= N <= 5-与foo::foo(int)互斥。 (并且g++实际上在对象中使用_ZN3fooC1Ei_ZN3fooC2Ei_ZN3fooC5Ei 文件bar.ogum.o)。

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

但是为什么?

对于这个问题,恐怕答案太长且太复杂了。

执行摘要

GCC C ++编译器使用了两个弱符号 相同地分解,以不同方式引用全局内联类方法,作为 其股票公式,可以成功地在排名独立代码中链接全球内联类方法。 对于任何编译器而言,这都是一个不容忽视的问题,并且针对它的GCC公式并不是唯一可能的问题。 lang有不同 解决方案,它确实涉及使用同义但不同的符号,因此不 引起您所看到的符号的虚幻的“重复”。