当试图从链接的可执行文件中删除不需要的东西时,我发现了一些奇怪的东西。 假设我们有一个简单,直接的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标头之后的某些load命令中定义了入口点(因此与符号表无关)。
尝试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
个符号,以防用户只是不想导出它们。
P.S。我研究了命令选项,但没有发现任何有用的信息(-N
也会删除未定义的符号,因此这不是一个选项)。用visibility("hidden")
声明该类很容易,但是不幸的是,要对实际项目进行操作并不容易。
答案 0 :(得分:2)
我的第一个结论是跳到雷达臭虫5614542的结果,因此这个怪异的符号,但与它无关。
我将做出一些假设,并从一个事实开始猜测,即您似乎正在使用nlist重定位,而不是基于新的基于字节码的重定位(您可以通过查找dyld info load命令来进行检查),使用古老的工具链构建而成,或者是尚未执行最后链接步骤的主要可执行文件的 MH_OBJECT
文件。我不确定100%是否是这种情况-但无论哪种方式,
很抱歉,我的上述假设仍然适用,除非您真的想退出符号合并,否则原始答案仍然适用。在这种情况下,应使用专用链接构建应用程序,但是由于非常好的原因,此模板实例再次将符号强制为弱,它有一个静态构造函数和一个隐式实例化的模板,它偏爱安全性,因此保留了符号。您不能完全将其导出到可执行文件之外,尽管您在这里有一个小案例,但是C ++程序倾向于使用诸如boost或依赖于其他C ++库的C ++库之类的东西,这些都创建了链,最终您最终得到多个仅仅因为C ++语义而在共享名称空间中定义它们。在较小的测试用例中,您可以在大型应用程序中使用它,除非您真的知道自己在做什么,并检查诸如dylib的依赖树之类的东西,只需让dyld来完成它的工作即可。我认为我的原始答案仍然适用于大部分内容,因为它解释了为什么将符号标记为弱(ODR是C ++特定概念,但是由不同的静态链接器处理的方式有所不同):
需要更长的解释-与C ++语义有关,即一个定义规则(ODR),这是一个接近但不相同的概念,因为不能在其中包含重复的强符号相同的名称空间(我的意思是链接名称空间,而不是C ++名称空间,这会很快造成混乱)。
如果您想知道为什么标记为弱,dyld可以在动态链接期间将其合并,因为重新使用该模板会再次实例化它(导致ODR违规,具体取决于上下文中的链接时间错误),因为它是一个隐式实例,它可能需要也可能不需要合并(直到静态或什至是动态链接时间才知道,除非您将其定义为隐藏,在这种情况下,您必须非常谨慎)请注意,因为语义会根据诸如是否为模块化构建之类的因素而变化很大(我的意思是LLVM“模块”,而不是C ++的Modules TS)。
如果没有弱点,您将通过将其定义为隐藏在多个翻译单元中而导致违反C ++规则(如果您重复使用该模板,例如在模块的标头中,则会得到重复的符号),从而导致违反C ++规则错误)。您可以避免违反ODR,因为它实际上并没有执行,但要为一些令人讨厌的意外做好准备(即,使用非模块化构建,也就是“每个翻译单元都是一个模块”)。
通过将其定义为弱,dyld可以为每个最终链接对象选择正确的定义,即在运行时共享库或可执行文件(不要忘记共享缓存)并绑定/在原本平坦的名称空间中适当地重新放置它们。
在没有任何形式的提示的情况下,编译器可以推断出以上内容,隐藏链接是一个非常糟糕的主意,除非您了解其中的含义,否则,如果希望重新internal
-每次实例化并复制模板。 OSX通常具有一个相当复杂的链接模型,很多地雷有可能被踩上。
如果我对目标文件的事情是正确的,在将目标文件馈入静态链接器之前,您不应真正在目标文件上运行剥离。 >
答案 1 :(得分:0)
感谢Apple开源项目,我终于找到了答案。看来strip
从未删除全局弱定义符号:
...
*
* In 64-bit applications, we only need to save coalesced
* symbols that are used as weak definitions.
*/
...
(不幸的是,该网站没有每行导航,或者我看的不够彻底)
这说明了我的情况,因为我的二进制文件是mach-o-x86-64
。
尽管如此,这种行为的动机仍不清楚。
编辑:请参阅克里斯蒂娜(Kristina)的答案,以了解为什么strip
倾向于保留全局弱定义。