54

某些平台要求您向链接器提供共享库的外部符号列表。但是,在大多数 unixish 系统上,这不是必需的:默认情况下,所有非静态符号都可用。

我的理解是 GNU 工具链可以选择性地将可见性限制为明确声明的符号。使用 GNU ld 如何实现这一点?

4

5 回答 5

77

GNUld可以在 ELF 平台上做到这一点。

以下是如何使用链接器版本脚本执行此操作:

/* foo.c */
int foo() { return 42; }
int bar() { return foo() + 1; }
int baz() { return bar() - 1; }

gcc -fPIC -shared -o libfoo.so foo.c && nm -D libfoo.so | grep ' T '

默认情况下,所有符号都被导出:

0000000000000718 T _fini
00000000000005b8 T _init
00000000000006b7 T bar
00000000000006c9 T baz
00000000000006ac T foo

假设您只想导出bar()baz(). 创建一个“版本脚本” libfoo.version

FOO {
  global: bar; baz; # explicitly list symbols to be exported
  local: *;         # hide everything else
};

将其传递给链接器:

gcc -fPIC -shared -o libfoo.so foo.c -Wl,--version-script=libfoo.version

观察导出的符号:

nm -D libfoo.so | grep ' T '
00000000000005f7 T bar
0000000000000609 T baz
于 2009-01-17T07:51:03.153 回答
47

我认为最简单的方法是添加-fvisibility=hidden到 gcc 选项并明确地在代码中公开某些符号的可见性(通过__attribute__((visibility("default"))))。请参阅此处的文档。

可能有一种方法可以通过 ld 链接器脚本来实现,但我对此知之甚少。

于 2009-01-12T23:56:33.463 回答
7

为调用任何导出的函数或使用任何导出的全局变量而生成的代码比未导出的代码效率低。涉及到一个额外的间接级别。这适用于任何可能在编译时导出的函数。gcc 仍会为稍后由链接描述文件导出的函数产生额外的间接。因此,使用可见性属性将产生比链接描述文件更好的代码。

于 2009-10-12T23:04:51.937 回答
4

似乎有几种方法可以在 GNU/Linux 上管理导出的符号。根据我的阅读,这些是3种方法:

  • 源代码注释/装饰:
    • 方法一:-fvisibility=hidden配合__attribute__((visibility("default")))
    • 方法 2(从 GCC 4 开始):#pragma GCC visibility
  • 版本脚本:
    • 方法 3:版本脚本(又名“符号映射”)传递给链接器(例如-Wl,--version-script=<version script file>

我不会在这里讨论示例,因为它们大部分都被其他答案所涵盖,但这里有一些关于我脑海中不同方法的注意事项、优缺点:

  • 使用带注释的方法允许编译器稍微优化代码(少一个间接)。
  • 如果使用带注释的方法,那么也考虑使用strip --strip-all --discard-all.
  • 带注释的方法可以为内部功能级单元测试添加更多工作,因为单元测试可能无法访问符号。这可能需要构建单独的文件:一个用于内部开发和测试,另一个用于生产。(从单元测试纯粹主义者的角度来看,这种方法通常不是最佳的。)
  • 使用版本脚本会失去优化,但允许符号版本控制,这似乎不适用于带注释的方法。
  • 假设代码首先构建到存档 (.a) 文件中,然后链接到 DSO (.so) 中,则使用版本脚本允许进行单元测试。单元测试将与 .a 链接。
  • Mac 不支持版本脚本(至少在使用 Mac 提供的链接器时不支持,即使编译器使用 GCC 也是如此),因此如果需要 Mac,请使用带注释的方法。

我确定还有其他人。

以下是一些我认为有帮助的参考资料(附有示例):

于 2019-10-23T16:39:30.007 回答
1

如果您使用的是 libtool,还有另一种选择,类似于 Employed Russian 的回答。

使用他的例子,它会是这样的:

cat export.sym
bar
baz

然后使用以下选项运行 libtool:

libtool -export-symbols export.sym ...

请注意,使用 -export-symbols 时,默认情况下不会导出所有符号,仅导出 export.sym 中的符号(因此 libfoo.version 中的“local: *”行实际上隐含在此方法中)。

于 2017-02-27T22:14:15.590 回答