为什么OSX的工具条无法删除弱符号?

时间:2018-08-30 07:15:04

标签: c++ xcode macos strip mach-o

当试图从链接的可执行文件中删除不需要的东西时,我发现了一些奇怪的东西。 假设我们有一个简单,直接的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")声明该类很容易,但是不幸的是,要对实际项目进行操作并不容易。

2 个答案:

答案 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倾向于保留全局弱定义。