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
不同的符号 - _ZN3fooC
NEi
,对于 1 <= N <= 5 - 分解为 foo::foo(int)
.
(而g++
实际上在对象中使用了_ZN3fooC1Ei
、_ZN3fooC2Ei
和_ZN3fooC5Ei
文件 bar.o
和 gum.o
)。
所以实际上,动态符号中没有重复的符号table:
名称分解映射的偷偷摸摸的多对一性质使它看起来像那样。
但是为什么呢?
恐怕这里的答案太长太复杂了。
执行摘要
GCC C++ 编译器使用了两个弱符号
demangle 以不同的方式相同地引用 global inline class-method,作为
它的库存公式用于在位置独立代码中启用全局内联 class 方法的成功 linkaqe。
这对任何编译器来说都是一个不可忽视的问题,GCC 公式并不是唯一可能的。 Clang有一个不同的
解决方案,确实涉及使用同义但不同的符号,因此不
产生你所见过的虚幻的 "duplication" 符号。
我是 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
不同的符号 - _ZN3fooC
NEi
,对于 1 <= N <= 5 - 分解为 foo::foo(int)
.
(而g++
实际上在对象中使用了_ZN3fooC1Ei
、_ZN3fooC2Ei
和_ZN3fooC5Ei
文件 bar.o
和 gum.o
)。
所以实际上,动态符号中没有重复的符号table: 名称分解映射的偷偷摸摸的多对一性质使它看起来像那样。
但是为什么呢?
恐怕这里的答案太长太复杂了。
执行摘要
GCC C++ 编译器使用了两个弱符号 demangle 以不同的方式相同地引用 global inline class-method,作为 它的库存公式用于在位置独立代码中启用全局内联 class 方法的成功 linkaqe。 这对任何编译器来说都是一个不可忽视的问题,GCC 公式并不是唯一可能的。 Clang有一个不同的 解决方案,确实涉及使用同义但不同的符号,因此不 产生你所见过的虚幻的 "duplication" 符号。