首页 文章

剥离linux共享库

提问于
浏览
33

我们最近被要求发布我们的一个库的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 . 更有问题的是当我们开始链接我们控制之外的其他(静态)库时, all of the symbols we use from those libraries get exported . 另外,当我们开始使用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 Headers (!)的临时解决方案,但我想问:

Is there a clean way to strip all unnecessary symbols (IE ones that are not part of the exposed library functionality) from a linux shared library? 我更喜欢已知工作而不是相信的答案 .

特别是:

  • 不导出(闭源)静态库中的符号 .

  • 不导出标准库中的符号 .

  • 不导出目标文件中的非公共符号 .

我们的导出界面是C.

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

但答案却收效甚微 .

5 回答

  • 2

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

    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
    

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

    • 我们必须确保在内部代码中不使用"exported"前缀(在这个简单的示例"public"中,但在我们的例子中显然更有用) .

    • 许多符号名称仍然在字符串表中结束,这似乎是RTTI,-fno-rtti使它们在我的简单测试中消失,但它是一个相当核的解决方案 .

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

  • 4

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

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

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

  • 7

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

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

  • 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
    
  • 5

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

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

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

    • 只使用暴露的API创建一个小的.so .

    • 让它用dlopen打开真正的实现,并与dlsym链接 .

    只要您不使用RTLD_GLOBAL,如果不是特别保密,您现在可以完全绝缘 . -Bsymbolic也可能是理想的 .

相关问题