隐藏在使用 Xcode 构建的静态库中的符号
Posted
技术标签:
【中文标题】隐藏在使用 Xcode 构建的静态库中的符号【英文标题】:Symbol hiding in static libraries built with Xcode 【发布时间】:2011-03-17 15:07:05 【问题描述】:我试图弄清楚我是否可以构建一个静态库来隐藏它的所有内部对象和函数等,除了我想要导出的接口。我正在试验 Xcode (gcc 4.2)。
根据this documentation,我在一些C++ 类上使用了__attribute__((visibility("hidden")))
属性。我还将小辅助 C 函数定义为文件本地(静态)等。
但是,当我在生成的 .a 库文件上运行 strings
时,即使在发布配置中编译时,我仍然会看到我表面上隐藏的类的名称,以及它们的方法名称,甚至是文件的名称——当地的功能也散落在那里。
我已将 -fvisibility=hidden
甚至 -fno-rtti
添加到 gcc 标志中。虽然这减少了一些字符串,但类名、方法名和静态函数名仍然以普通或损坏但可读的形式存在。
有没有一种可靠的方法可以让编译器在不将所有内部内容的字符串名称发送到二进制文件中的情况下构建这些内容?任何外部客户端都不需要链接。
(澄清一下:我问的是内部命名的混淆,而不是文字导出绑定需求。我很不安所有内部工作都可以通过strings
命令看到,无论这些符号是正式导出还是不是。)
谢谢。
【问题讨论】:
如果启用 RTTI,无论如何都会嵌入所有类名,以使type_info::name()
工作。您可以尝试使用-fno-rtti
重新编译,看看是否有所不同。当然,如果您在任何地方使用dynamic_cast
,这将不起作用。
感谢您的提示。试过了,但所有的名字仍然以错误的形式存在。
你真的应该考虑重新授予答案,因为这个答案不需要任何技巧。它不仅删除了我的大部分名字,而且大大减少了我需要共享的库的大小。
@DavidH:完成,感谢您的提醒。有一段时间没有重新审视这个问题了。
【参考方案1】:
隐藏内部名称需要一些简单的 Xcode 构建设置,一般不需要修改源代码或更改构建产品的类型。
-
通过执行单对象预链接消除模块之间所需的任何内部符号。将名为“Perform Single-Object Prelink”的 Xcode 构建设置设置为 Yes (GENERATE_MASTER_OBJECT_FILE=YES)。这会导致 ld 使用“-r”标志运行。
确保“条带样式”设置为“非全局符号”(STRIP_STYLE=non-global),这会将“-x”传递给 ld。
只有在启用了后处理(这不是默认设置)的情况下才会在静态库上实际执行剥离。将 Xcode 构建设置“部署后处理”设置为是。 (DEPLOYMENT_POSTPROCESSING=YES)。还要确保“使用单独的条带”设置为“是”(并非总是默认设置)(SEPARATE_STRIP=YES)。
如果除了局部符号之外,如果您需要删除一些全局符号,您可以在 Xcode 构建设置“附加条形标志”下为条形命令提供附加选项。例如。我通常使用 strip "-R somefile" 选项来提供一个文件,其中包含我想从全局符号表中删除的符号的附加列表。
【讨论】:
太棒了!我只是按照您对 Xcode 5.1 Beta 的建议进行了操作,并且效果很好。正是我需要的。 我建议将 5. 添加到上述列表中:将“SymbolsHiddenByDefult”设置为“是”。 (它在代码生成部分)。它相当于 -fvisibility=hidden。第 1..4 步帮助我删除了字符串,但第 5 步也隐藏了非公共方法、静态变量和缓冲区。 @DanielHsH OP 的问题表明他已经控制了符号的可见性,但是是的,对于尚未实施明确控制的其他情况,您的建议是一个好主意。但是,测试最终产品是否可以正确链接(到动态部分或可执行文件)确实是值得的,因为调用者可能会无意中链接到私有符号。 “使用单独的条带”不再是一个选项——被 Apple 弃用,声称剥离总是单独进行。尽管如此,通过其余的工作,我仍然从这篇文章中获得了一些收获——似乎奏效了。【参考方案2】:在静态库中隐藏符号的主要技巧是生成一个可重定位对象文件(与仅包含单个 .o 文件集合的静态库存档相反)。要构建一个可重定位的目标文件,您需要在 XCode 中选择您的目标作为 Bundle(而不是“Cocoa Touch 静态库”)。 Bundle 目标出现在 OS X 模板下,如果您正在为 ios 构建,您可以在构建设置中将其目标设置为 iOS。
正确设置目标后,以下步骤将有助于正确隐藏符号:
在构建设置中将“符号默认隐藏”选项设置为“是”。这样可以确保文件中编译的所有符号都标记为私有。
由于这是一个库,您确实需要公开一些符号。您应该将要保持公开可见的函数的代码放在单独的文件中,并使用 -fvisibility=default
标志编译这些文件(您可以在 Xcode 中为单个文件“构建阶段 > 编译源 > -- 编译器标志”设置此标志)。或者,您可以使用 __attribute__((visibility("default")))
指令为您希望可见的函数/类的名称添加前缀。
在 X-code 项目中的链接设置下,将 Mach-O 类型设置为“可重定位对象文件”。这意味着所有 .o 文件将被重新链接以生成单个目标文件。当 .o 文件链接到一个文件中时,这一步有助于将所有符号标记为私有。如果您构建一个静态库(即.a
文件),则不会发生此重新链接步骤,因此符号永远不会被隐藏。因此,选择可重定位目标文件作为目标至关重要。
即使将符号标记为私有后,它们仍会显示在 .o 文件中。您需要启用剥离以摆脱私有符号。这可以通过在构建设置中将“已剥离的链接产品”设置为“是”来完成。设置此选项会在目标文件上运行 strip -x
命令,从目标文件中删除私有符号。
通过对构建过程生成的最终可重定位目标文件运行 nm 命令,仔细检查所有内部符号是否已消失。
以上步骤将帮助您摆脱 nm 命令中的符号名称。如果您在对象文件上运行 strings 命令,您仍然会看到一些函数名和文件名(由于某些字符串和对象名是通过异常编译的)。我的一个colleagues 有一个脚本,它通过查看二进制部分并重命名这些字符串来重命名其中一些符号。我已经把它贴在这里供你使用:https://gist.github.com/varungulshan/6198167。您可以将此脚本添加为 Xcode 中的额外构建步骤。
【讨论】:
感谢您的回答,我们做到了,但现在我们无法在模拟器上运行它,它没有链接。它仅适用于设备。我们得到的错误是: ld: Assertion failed: (atom->fixupCount() == 1), function targetClassName, file /SourceCache/ld64/ld64-136/src/ld/parsers/macho_relocatable_file.cpp, line 4973。 clang:错误:链接器命令失败,退出代码为 1(使用 -v 查看调用)。你遇到过这个问题吗?谢谢 嗨 Yoav,抱歉回复晚了。不,我从来没有在模拟器上试过这个,所以它可能会破坏那里的链接。我们的一位工程师提出了一种更好的方法(他编写了脚本以通过扫描二进制部分来删除所有本地可见的符号)。我会尽快发布。 @VarunGulshan 会花时间回答我的问题吗?谢谢***.com/questions/56045295/…【参考方案3】:我有点不清楚如何根据之前的答案从 linux 命令行环境中隐藏静态库中的符号,所以我将在这里发布我的解决方案以供后代使用(鉴于这是关于谷歌这个问题)。
假设您有这两个 .c 文件:
// f1.c
const char *get_english_greeting(void)
return "hello";
__attribute__((visibility("default")))
const char *get_greeting(void)
return get_english_greeting();
和
// f2.c
#include <stdio.h>
const char *get_english_greeting(void);
__attribute__((visibility("default")))
void print_greeting(void)
puts(get_english_greeting());
您希望将这两个文件转换为一个静态库,同时导出 get_greeting
和 print_greeting
但不是 get_english_greeting
,您不想将其设为静态,因为您希望在整个库中使用它。
以下是实现这一目标的步骤:
gcc -fvisibility=hidden -c f1.c f2.c
ld -r f1.o f2.o -o libf.o
objcopy --localize-hidden libf.o
ar rcs libf.a libf.o
现在可以了:
// gcc -L. main.c -lf
void get_greeting(void);
void print_greeting(void);
int main(void)
get_greeting();
print_greeting();
return 0;
这不是:
// gcc -L. main.c -lf
const char *get_english_greeting(void);
int main(void)
get_english_greeting();
return 0;
对于后者,您会收到此错误:
/tmp/ccmfg54F.o: In function `main':
main.c:(.text+0x8): undefined reference to `get_english_greeting'
collect2: error: ld returned 1 exit status
这是我们想要的。
请注意,隐藏的符号名称在静态库中仍然可见,但链接器将拒绝在所述静态库之外与它们进行链接。要完全删除符号名称,您需要剥离和混淆。
【讨论】:
如何从静态库中删除符号名称(get_english_greeting)?我试过“strip -x libf.a”“objcopy -N get_english_greeting libf.a”,但都没有奏效。 @prgbenz,我不知道该怎么做。我尝试运行strip -N get_english_greeting libf.a
,但我得到了strip: not stripping symbol 'get_english_greeting' because it is named in a relocation
。我认为这表明您需要找到一种方法来预链接 libf.o 以使其预解决重定位。我还没有找到任何方法来做到这一点。此外,这样做有什么好处?以上是关于隐藏在使用 Xcode 构建的静态库中的符号的主要内容,如果未能解决你的问题,请参考以下文章
与静态库中的 std::string 相关的 C++ 未定义符号