剥离linux共享库

时间:2010-01-18 18:47:34

标签: c++ linux gcc strip shared-libraries

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

  • 通过导出的符号保护我们技术的专有方面免受曝光。
  • 防止用户遇到符号名称冲突的问题。
  • 加快图书馆的装载(至少我知道)。

举一个简单的例子:

TEST.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的其他类似问题:

但答案却收效甚微。

6 个答案:

答案 0 :(得分:7)

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

TEST.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);
}

exports.version

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

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

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

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

答案 1 :(得分:6)

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

您还应该忘记尝试隐藏stdlib导出,请参阅this GCC bug了解原因。

此外,如果您在特定标头中包含所有公共符号,则可以将它们包含在#pragma GCC visibility push(default)#pragma GCC visibility pop中,而不是使用属性。虽然如果您要创建跨平台库,请查看Controlling Exported Symbols of Shared Libraries以获取统一Windows DLL和Linux DSO导出策略的技术。

答案 2 :(得分:5)

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

这非常方便明确如何从共享库中仅导出白名单上的功能。

答案 3 :(得分:4)

如果您将私有部分包装在匿名命名空间中,则符号表中既不会显示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

答案 4 :(得分:2)

一般来说,在多个Linux和Unix系统中,答案是链接时没有答案。这对于ld.so如何运作来说是相当基础的。

这导致一些相当劳动密集的替代方案。例如,我们将STL重命名为_STL而不是std以避免与STL发生冲突,并且我们使用高,低和中间名称空间来保持符号远离可能与其他人符号的冲突。

这是一个你不会喜欢的解决方案:

  1. 只使用您公开的API创建一个小的.so。
  2. 用dlopen打开真正的实现,并与dlsym链接。
  3. 只要您不使用RTLD_GLOBAL,如果不是特别保密,您现在可以完全绝缘。-Bsymbolic也可能是理想的。

答案 5 :(得分:0)

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

相关问题