簡體   English   中英

剝離linux共享庫

[英]Stripping linux shared libraries

我們最近被要求發布我們的一個庫的 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 的其他類似問題:

但對答案收效甚微。

所以我們現在的解決方案如下:

測試.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 在我的簡單測試中使它們消失,但它是一個相當核心的解決方案。

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

您對默認可見性屬性和 -fvisibility=hidden 的使用應使用 -fvisibility-inlines-hidden 進行擴充。

您還應該忘記嘗試隱藏 stdlib 導出,請參閱此 GCC 錯誤以了解原因。

此外,如果您在特定標題中擁有所有公共符號,則可以將它們包裝在#pragma GCC visibility push(default)#pragma GCC visibility pop而不是使用屬性。 但是,如果您要創建跨平台庫,請查看控制共享庫的導出符號以了解統一 Windows DLL 和 Linux DSO 導出策略的技術。

請注意,Ulrich Drepper 寫了一篇關於為 Linux/Unix編寫共享庫的(所有?)方面的文章,其中涵蓋了許多其他主題中導出符號的控制。

這對於明確如何從共享庫中僅導出白名單上的函數非常方便。

如果您將私有部分包裝在匿名命名空間中,那么在符號表中將看不到std::absprivate_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

一般來說,跨多個 Linux 和 Unix 系統,這里的答案是鏈接時這里沒有答案。 這是 ld.so 如何工作的基礎。

這導致了一些相當勞動密集型的替代方案。 例如,我們將 STL 重命名為 _STL 而不是std以避免與 STL 發生沖突,並且我們使用命名空間 high、low 和 in-between 來防止我們的符號與其他人的符號可能發生沖突。

這是您不會喜歡的解決方案:

  1. 僅使用公開的 API 創建一個小的 .so。
  2. 讓它用 dlopen 打開真正的實現,並用 dlsym 鏈接。

只要您不使用 RTLD_GLOBAL,即使不是特別保密,您現在也可以完全隔離.. -Bsymbolic 也可能是可取的。

實際上,在 ELF 結構中有 2 個符號表:“symtab”和“dynsym”-> 請參閱: 在庫中隱藏符號名稱

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM