在Mac OS X中隐藏共享库中的符号

11
我们已经在多个平台(Linux、Windows、Mac OS X、32位和64位)上构建了一个大型开源software,几年来没有遇到任何问题。然而,最近 Mac OS X 构建(64位)停止正常工作,并开始随机崩溃。这或多或少与我们的构建机器从 10.7 更新到 10.8.2 的 Mac OS X 更新同时发生(但编译器工具链没有改变,仍为 llvm-gcc 4.2.1)。
我们的应用程序由几个动态(共享)库和许多使用它们的可执行文件组成。其中一个共享库重载了 newdelete 运算符,出于各种原因。在 Mac OS X(和 Linux)上,默认情况下导出所有符号,包括我们重载的 newdelete 运算符。在 Mac OS X 上的崩溃似乎与一些内存使用了一个内存子系统(不是我们的),然后通过我们自己的(不兼容的)delete 实现释放有关。
最明智的解决方案似乎是防止超载运算符对共享库的用户可见。这可以通过两种方式实现:使用__attribute__((visibility("hidden")))标记运算符,或使用-unexported_symbols_list链接器命令行选项来防止某些符号被导出。不幸的是,第一种解决方案不起作用:gcc发出警告,说运算符已经以不同的方式(在<new>中)声明,因此属性将被忽略。从我在各个地方的阅读中得知,第二种解决方案似乎是解决这个问题的正确方法。但出于某种原因,我们无法使其工作。
在链接共享库时,我们向g++传递了-Wl,-unexported_symbols_list unexported_symbols_list.txt选项,然后应该将其传递给ld。 unexported_symbols_list.txt文件包含以下符号列表:
__ZdaPv
__ZdaPvRKSt9nothrow_t
__ZdlPv
__ZdlPvRKSt9nothrow_t
__ZdlPvS_
__Znam
__ZnamRKSt9nothrow_t
__Znwm
__ZnwmPv
__ZnwmRKSt9nothrow_t

这些是我们重载并希望隐藏的newdelete的所有变体。我们通过执行nm libappleseed.dylib,然后使用c++filt对符号名称进行解码来找到这些符号。
以下是CMake生成的命令行,用于链接libappeseed.dylib
/usr/bin/g++  -g -Werror -dynamiclib -Wl,-headerpad_max_install_names -framework Cocoa -lcurl    -Werror -Wl,-unexported_symbols_list -Wl,unexported_symbols_list.txt -o ../mac-gcc4/appleseed/libappleseed.dylib [...]

很不幸,尽管我们付出了所有的努力,但符号仍然存在(如nm所示)。

我们做错了什么?有没有其他的方法可以尝试?


更新于2012年12月19日:

我们的问题以及所谓的解决方案在苹果公司的技术说明中得到了很好的涵盖:http://developer.apple.com/library/mac/#technotes/tn2185/_index.html(“覆盖new/delete”部分)。

相关源代码指针:

在使用-fvisibility=hidden编译构建libappleseed.dylib并运行strip -x libappleseed.dylib后,nm输出的片段如下:
...
00000000002a41b0 T __ZdaPv
00000000002a41f0 T __ZdaPvRKSt9nothrow_t
00000000002a4190 T __ZdlPv
00000000002a41d0 T __ZdlPvRKSt9nothrow_t
00000000002a4060 T __Znam
00000000002a4130 T __ZnamRKSt9nothrow_t
00000000002a3ff0 T __Znwm
00000000002a40d0 T __ZnwmRKSt9nothrow_t
...

你能从nm中删除相关行,以显示符号仍然存在吗? - alexp
当然,这些代码行在上面的更新中。不仅仍然存在导致问题的符号,而且应用程序仍然会随机崩溃。 - François Beaune
你肯定已经看过了,但我注意到在g++命令行中,-unexported_symbols_list与文件名之间有一个逗号分隔 - 这不会导致错误吗? - Dan Nissenbaum
@DanNissenbaum 每个链接器参数都必须以“-Wl,”为前缀。就我所见,没有额外的逗号。感谢您的查看。 - François Beaune
2个回答

7
你应该使用-fvisibility=hidden进行构建,然后只导出你需要的内容。在这里阅读更多信息:http://gcc.gnu.org/wiki/Visibility。它还解释了-fvisibility-inlines-hidden。许多大型库(例如Qt)都使用了这种方法。好处是相当显著的。

1
@FrançoisBeaune 我猜想多次使用 -unexported_symbol symbol 而不是使用文件也不起作用吗?(例如 -unexported_symbol __ZdaPv -unexported_symbol __ZdaPvRKSt9nothrow_t [...] - Nikos C.
谢谢,我们也会尝试这个方法,虽然它似乎是-Wl,的同义词:https://dev59.com/Pmw05IYBdhLWcg3weBvu。另外,我们已经通过在g++上启用详细模式来检查选项是否正确传递给链接器。我们还将调查剥离是否有所不同(希望没有)。感谢您的帮助和建议。 - François Beaune
我们还没有找出问题所在,但最终我们决定不再覆盖newdelete运算符,因为在Mac OS X和Linux上内存块本来就是本地16字节对齐的(提供自己的分配功能的主要原因)。@NikosC我将向您归属赏金,感谢您的帮助和建议。 - François Beaune
@FrançoisBeaune 谢谢。但很遗憾听到问题仍未解决。我想如果苹果提供某种形式的付费支持(如果他们提供的话),那也不可能吧? - Nikos C.
@FrançoisBeaune,我刚刚在文档中发现了一个细节,之前没有注意到:“请注意,由于ISO C++规范要求,operator new和operator delete必须始终具有默认可见性。”来源:http://gcc.gnu.org/onlinedocs/gcc-4.7.2/gcc/Code-Gen-Options.html - Nikos C.
显示剩余5条评论

0

谢谢。使用gcc的__attribute__((visibility("default")))属性,这方面的事情应该已经得到解决了。 - François Beaune
截至目前,似乎clang/llvm仍不支持--version-script。 - codesniffer
@codesniffer,您能否确认在Xcode Clang CPP构建中不支持--version-script?谢谢。 - Boki
@Boki 我在10.10、10.12和10.15上使用--version-script时遇到了构建错误,而且在man页面中没有看到它。虽然这不应该有影响,但我只使用XCode CLT。我的10.15系统上的ld版本是LLVM版本11.0.0 (clang-1100.0.33.17)。你那边可以用吗? - codesniffer
@codesnifer 好的,谢谢,我正计划使用XCode 11.3在10.15上重构一些代码以进行构建,因为在HighSierra或Mohave上执行应用程序时遇到了问题/崩溃(在Catalina上没有问题)。因此,起初我的想法是使用版本脚本来减少/控制导出。当我看到你在这里的评论时,我曾经考虑过这个问题,但我认为我早就注意到了BuildSettings中链接器映射脚本或类似选项,我不确定,无论如何我会在周末重新检查。如果我找到有用的东西,我会通知你。 - Boki

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接