在尝试从链接的可执行文件中删除不需要的东西时,我发现了一些奇怪的事情。假设我们有一个简单,直接的C ++程序:
class Foo {
public:
template <typename T>
char* getPtr() {
static char c;
return &c;
}
};
char* bar() {
Foo i;
return i.getPtr<int>();
}
int main() {
bar();
}
使用clang++ t.cc
构建的二进制文件具有下一个动态符号表:
$ gobjdump -T ./a.out
./a.out: file format mach-o-x86-64
DYNAMIC SYMBOL TABLE:
0000000100000f40 g 0f SECT 01 0000 [.text] __Z3barv
0000000100000f60 g 0f SECT 01 0080 [.text] __ZN3Foo6getPtrIiEEPcv
0000000100001020 g 0f SECT 08 0080 [.data] __ZZN3Foo6getPtrIiEEPcvE1c
0000000100000000 g 0f SECT 01 0010 [.text] __mh_execute_header
0000000100000f80 g 0f SECT 01 0000 [.text] _main
0000000000000000 g 01 UND 00 0200 dyld_stub_binder
考虑到它是一个可执行文件而不是一个dylib,我想删除除了未定义符号之外的所有条目。理论上二进制仍然可以工作,因为有关所需的dyld绑定的信息仍然存在,并且在mach-o头之后的某些加载命令中定义了入口点(因此与符号表无关)。
尝试strip
给出了一些奇怪的结果:
$ strip ./a.out
$ gobjdump -T ./a.out
./a.out: file format mach-o-x86-64
DYNAMIC SYMBOL TABLE:
0000000005614542 d 3c OPT 00 0000 radr://5614542
0000000100000f60 g 0f SECT 01 0080 [.text] __ZN3Foo6getPtrIiEEPcv
0000000100001020 g 0f SECT 08 0080 [.data] __ZZN3Foo6getPtrIiEEPcvE1c
0000000100000000 g 0f SECT 01 0010 [.text] __mh_execute_header
0000000000000000 g 01 UND 00 0200 dyld_stub_binder
最后两个条目不应该被剥离,因为dyld需要它们来处理这个可执行文件。与此同时,我们看到_main
和__Z3barv
消失了。但来自class Foo
的符号仍然存在。他们和被剥夺的人之间的唯一区别是前者有N_WEAK_DEF
旗帜设置(0080)。以下是<mach-o/nlist.h>
关于该旗帜的一些信息:
/*
* The N_WEAK_DEF bit of the n_desc field indicates to the static and dynamic
* linkers that the symbol definition is weak, allowing a non-weak symbol to
* also be used which causes the weak definition to be discared. Currently this
* is only supported for symbols in coalesed sections.
*/
#define N_WEAK_DEF 0x0080 /* coalesed symbol is a weak definition */
不幸的是,它没有解释为什么strip
忽略带有该标志的符号。
所以问题是 - 如果用户只是不想导出它们,如何教strip
甚至删除N_WEAK_DEF
符号。
附:我已经查看了命令选项并且没有找到任何有用的东西(-N
也删除了未定义的符号,因此它不是一个选项)。用visibility("hidden")
声明这个类可以解决问题,但不幸的是,对于真正的项目来说这并不容易。
我的第一个结论是跳到它是雷达bug 5614542的结果因此这个奇怪的符号,但它与它无关。
我将绘制一些假设,并猜测你似乎在使用nlist重定位而不是新的基于字节码的重定位(你可以通过查找dyld信息加载命令来检查),这可以使用古老的工具链构建或者是一个主要可执行文件的MH_OBJECT
文件,尚未完成最后的链接步骤。我不是百分百确定这是否是这种情况 - 但不管怎样,
抱歉我的上述假设,但原始答案仍然适用,除非您真的想要选择退出符号合并,在这种情况下使用私有链接构建您的应用程序但是这个模板实例化强制该符号为弱的一个很好的理由,它有一个静态构造函数和一个隐式实例化的模板,它更喜欢安全性,因此它保留了符号。你不能在可执行文件之外的所有地方导出它,虽然这里有一个小例子,C ++程序倾向于使用诸如boost之类的东西,或者依赖于其他C ++库的C ++库,它们都会创建链,最终你会得到多个共享命名空间中的定义仅仅是因为C ++语义。在你的小测试用例中,除非你真的知道你在做什我认为我的原始答案仍然适用于一个主要部分,因为它解释了为什么你的符号被标记为弱(ODR是一个C ++特定的概念,但它由不同的静态链接器处理不同):
对于更长的解释 - 它与C ++语义有关,即一个定义规则(ODR),它是一个接近但不相同的概念,因为不能在同一个命名空间中有重复的强符号(我的意思是链接命名空间,而不是一个C ++命名空间,这很快就会引起混淆)。
如果你想知道为什么它被标记为弱,那么dyld能够在动态链接期间合并它,因为重用该模板会再次实例化它(导致ODR违规并且取决于上下文链接时间错误),因为它是隐式实例,可能需要也可能不需要合并(直到静态甚至动态链接时才知道,除非您将其定义为隐藏,在这种情况下您必须非常小心,因为语义会根据因素而变化很大比如它是否是模块化构建(我的意思是LLVM“模块”,而不是C ++的模块TS)。
如果没有它的弱点,你就会通过将它定义为隐藏在1个以上的转换单元中而导致每个C ++规则发生ODR违规(如果你重复使用该模板,比如模块中的标题,你会得到重复的符号错误)。你可以逃避违反ODR,因为它实际上没有实施,但是要为一些令人讨厌的惊喜做好准备(即通过使用非模块化构建,即“每个翻译单元都是一个模块”)。
通过将其定义为弱,dyld能够在运行时为每个最终链接对象选择正确的定义,即共享库或可执行文件(并且不要忘记共享缓存),并在其他平面命名空间内适当地绑定/重定位它们。
以上是很多能够由编译器推断出没有任何形式的提示,隐藏链接是一个非常糟糕的想法,除非你理解暗示,你想要internal
可见性,如果你真的想重新实例化和复制模板时间。 OSX有一个相当复杂的链接模型,很多地雷可能会踩到。
如果我对目标文件的说法是正确的,那么在它们被送入静态链接器之前,你不应该在目标文件上运行strip。
感谢Apple opensource项目,我终于找到了答案。看起来strip
永远不会删除全局弱def符号:
...
*
* In 64-bit applications, we only need to save coalesced
* symbols that are used as weak definitions.
*/
...
(不幸的是网站没有每行导航,或者我看起来不够彻底)这解释了我的情况,因为我的二进制文件是mach-o-x86-64
。
然而,这种行为背后的动机仍不清楚。
编辑:请参阅克里斯蒂娜的答案,了解为什么strip
更喜欢保持全球弱定义。