我注意到 .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
现在有两个命中。
这里没有错。查看更多图片:
$ 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 符号从他们的.symtabs 变成.symtab 和 共享的.dynsym
库,除了那些在目标文件中有 HIDDEN visibility 的符号
(它们是通过attribute of hidden visibility 定义的
编译时)。
目标文件中具有HIDDEN 可见性的任何GLOBAL 符号都被转录为LOCAL 符号
具有DEFAULT 对共享库的.symtab 的可见性并且不会被转录
完全进入共享库的.dynsym。所以当共享库链接到
否则,链接器和加载器都无法看到编译时为 HIDDEN 的全局符号。
但除了隐藏符号(通常没有这些符号)之外,相同的全局符号
将出现在共享库的.symtab 和.dynsym 表中。每个定义的符号
出现在两个表中的定义相同。
后来,OP cmets
我通过运行 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(与您的 sn-p 中的所有其他符号一样)。 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++ 方法签名,但不是
实际上是链接器可以识别的符号。让我们再做一次,这一次
无需拆解:
$ 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
不同的符号 - _ZN3fooCNEi,表示 1 N foo::foo(int) 分离。
(而g++实际上在对象中使用了_ZN3fooC1Ei、_ZN3fooC2Ei和_ZN3fooC5Ei
文件bar.o 和gum.o)。
所以实际上,动态符号表中没有重复的符号:
名称拆解映射的鬼鬼祟祟的多对一特性使它看起来如此。
但是为什么呢?
恐怕这里的答案太长太复杂了。
执行摘要
GCC C++ 编译器使用两个弱符号
demangle 以不同方式引用 全局内联类方法,作为
它的股票公式用于在 Position Independent Code 中实现全局内联类方法的成功链接。
对于任何编译器来说,这都是一个不可忽视的问题,而且它的 GCC 公式并不是唯一可能的问题。 Clang有不同的
解决方案,这确实涉及使用同义但不同的符号,因此不
引起你所看到的符号的虚幻“重复”。