39

我们最近被要求发布我们其中一个库的 Linux 版本,之前我们是在 Linux 下开发并为 Windows 发布的,在 Windows 中部署库通常要容易得多。我们遇到的问题是将导出的符号剥离为仅暴露界面中的符号。想要这样做有三个很好的理由

  • 保护我们技术的专有方面不通过导出的符号暴露。
  • 防止用户遇到符号名称冲突的问题。
  • 加快图书馆的加载速度(至少我被告知)。

那么举个简单的例子:

测试.cpp

#include <cmath>

float private_function(float f)
{
    return std::abs(f);
}

extern "C" float public_function(float f)
{
    return private_function(f);
}

使用 (g++ 4.3.2, ld 2.18.93.20081009) 编译

g++ -shared -o libtest.so test.cpp -s

并检查符号

nm -DC libtest.so

         w _Jv_RegisterClasses
0000047c T private_function(float)
000004ba W std::abs(float)
0000200c A __bss_start
         w __cxa_finalize
         w __gmon_start__
0000200c A _edata
00002014 A _end
00000508 T _fini
00000358 T _init
0000049b T public_function

显然不足。所以接下来我们将公共函数重新声明为

extern "C" float __attribute__ ((visibility ("default"))) 
    public_function(float f)

并编译

g++ -shared -o libtest.so test.cpp -s -fvisibility=hidden

这使

         w _Jv_RegisterClasses
0000047a W std::abs(float)
0000200c A __bss_start
         w __cxa_finalize
         w __gmon_start__
0000200c A _edata
00002014 A _end
000004c8 T _fini
00000320 T _init
0000045b T public_function

这很好,除了暴露了 std::abs 。更大的问题是,当我们开始链接我们无法控制的其他(静态)库时,我们从这些库中使用的所有符号都会被导出。另外,当我们开始使用 STL 容器时:

#include <vector>
struct private_struct
{
    float f;
};

void other_private_function()
{
    std::vector<private_struct> v;
}

我们最终从 C++ 库中得到了许多额外的导出

00000b30 W __gnu_cxx::new_allocator<private_struct>::deallocate(private_struct*, unsigned int)
00000abe W __gnu_cxx::new_allocator<private_struct>::new_allocator()
00000a90 W __gnu_cxx::new_allocator<private_struct>::~new_allocator()
00000ac4 W std::allocator<private_struct>::allocator()
00000a96 W std::allocator<private_struct>::~allocator()
00000ad8 W std::_Vector_base<private_struct, std::allocator<private_struct> >::_Vector_impl::_Vector_impl()
00000aaa W std::_Vector_base<private_struct, std::allocator<private_struct> >::_Vector_impl::~_Vector_impl()
00000b44 W std::_Vector_base<private_struct, std::allocator<private_struct> >::_M_deallocate(private_struct*, unsigned int)
00000a68 W std::_Vector_base<private_struct, std::allocator<private_struct> >::_M_get_Tp_allocator()
00000b08 W std::_Vector_base<private_struct, std::allocator<private_struct> >::_Vector_base()
00000b6e W std::_Vector_base<private_struct, std::allocator<private_struct> >::~_Vector_base()
00000b1c W std::vector<private_struct, std::allocator<private_struct> >::vector()
00000bb2 W std::vector<private_struct, std::allocator<private_struct> >::~vector()

注意:通过优化,您需要确保实际使用了向量,以便编译器不会优化未使用的符号。

我相信我的同事已经设法构建了一个涉及版本文件和修改似乎有效的 STL 标头(!)的临时解决方案,但我想问:

有没有一种干净的方法可以从 linux 共享库中删除所有不必要的符号(不属于公开库功能的 IE 符号)?我已经为 g++ 和 ld 尝试了很多选项,但收效甚微,所以我更喜欢已知有效的答案而不是相信的答案。

尤其是:

  • 来自(闭源)静态库的符号不会被导出。
  • 标准库中的符号不​​会被导出。
  • 目标文件中的非公共符号不会被导出。

我们导出的接口是C。

我知道关于 SO 的其他类似问题:

但在答案方面收效甚微。

4

6 回答 6

9

所以我们现在的解决方案如下:

测试.cpp

#include <cmath>
#include <vector>
#include <typeinfo>

struct private_struct
{
    float f;
};

float private_function(float f)
{
    return std::abs(f);
}

void other_private_function()
{
    std::vector<private_struct> f(1);
}

extern "C" void __attribute__ ((visibility ("default"))) public_function2()
{
    other_private_function();
}

extern "C" float __attribute__ ((visibility ("default"))) public_function1(float f)
{
    return private_function(f);
}

出口版本

LIBTEST 
{
global:
    public*;
local:
    *;
};

编译

g++ -shared test.cpp -o libtest.so -fvisibility=hidden -fvisibility-inlines-hidden -s -Wl,--version-script=exports.version

00000000 A LIBTEST
         w _Jv_RegisterClasses
         U _Unwind_Resume
         U std::__throw_bad_alloc()
         U operator delete(void*)
         U operator new(unsigned int)
         w __cxa_finalize
         w __gmon_start__
         U __gxx_personality_v0
000005db T public_function1
00000676 T public_function2

这与我们正在寻找的非常接近。但是有一些陷阱:

  • 我们必须确保我们不在内部代码中使用“exported”前缀(在这个简单的例子中是“public”,但显然在我们的例子中更有用)。
  • 许多符号名称仍然出现在字符串表中,这似乎归结为 RTTI,-fno-rtti 使它们在我的简单测试中消失,但这是一个相当核心的解决方案。

我很高兴接受任何人提出的任何更好的解决方案!

于 2010-01-19T09:33:13.993 回答
8

您对默认可见性属性和 -fvisibility=hidden 的使用应使用 -fvisibility-inlines-hidden 进行扩充。

您还应该忘记尝试隐藏 stdlib 导出,请参阅这个 GCC 错误了解原因。

此外,如果您将所有公共符号都放在特定的标头中,则可以将它们包装起来,#pragma GCC visibility push(default)#pragma GCC visibility pop不是使用属性。尽管如果您正在创建跨平台库,请查看控制共享库的导出符号,了解一种统一 Windows DLL 和 Linux DSO 导出策略的技术。

于 2010-01-18T21:05:48.373 回答
7

请注意,Ulrich Drepper 写了一篇关于为 Linux/Unix编写共享库的(所有?)方面的文章,其中涵盖了对导出符号的控制以及许多其他主题。

这对于明确如何从共享库中仅导出白名单上的函数非常方便。

于 2012-01-10T15:46:49.787 回答
6

如果您将私有部分包装在匿名命名空间中,则符号表中既不可见std::abs也不private_function可见:

namespace{
#include<cmath>
  float private_function(float f)
  {
    return std::abs(f);
  }
}
extern "C" float public_function(float f)
{
        return private_function(f);
}

编译(g++ 4.3.3):

g++ -shared -o libtest.so test.cpp -s

检查:

# nm -DC libtest.so
         w _Jv_RegisterClasses
0000200c A __bss_start
         w __cxa_finalize
         w __gmon_start__
0000200c A _edata
00002014 A _end
000004a8 T _fini
000002f4 T _init
00000445 T public_function
于 2010-01-19T09:49:35.610 回答
3

一般来说,跨多个 Linux 和 Unix 系统,这里的答案是在链接时这里没有答案。它对于 ld.so 的工作方式是相当基础的。

这导致了一些相当劳动密集型的替代方案。例如,我们将 STL 重命名为存在于 _STL 中,而不是std避免与 STL 发生冲突,并且我们使用命名空间 high、low 和 in-between 来防止我们的符号与其他人的符号可能发生冲突。

这是您不会喜欢的解决方案:

  1. 仅使用您公开的 API 创建一个小的 .so。
  2. 让它用 dlopen 打开真正的实现,并用 dlsym 链接。

只要您不使用 RTLD_GLOBAL,您现在就可以完全隔离,如果不是特别保密的话.. -Bsymbolic 也可能是可取的。

于 2010-01-18T18:57:50.090 回答
0

实际上,在 ELF 结构中有 2 个符号表:“symtab”和“dynsym” -> 参见: Hiding symbol names in library

于 2020-01-19T21:35:30.923 回答