一種跨平臺的C/C++動態庫的符號隱藏方式

源地址:http://blog.guorongfei.com/2018/04/11/symbol-visibility/

    <h1 id="什麼是符號隱藏"><a href="#什麼是符號隱藏" class="headerlink" title="什麼是符號隱藏"></a>什麼是符號隱藏</h1><p>在同一個文件中,如果有一些函數我們並不想要讓外部訪問,我們通常會添加 static

修飾符,把它設置爲內部鏈接屬性。

1
static void foo();

但是通常庫不太可能是單文件組成,這些文件中有些是做接口給外部使用,有些則單純的只是庫的內部實現。對於外部使用者來說,內部實現的這些符號沒有實際的作用,理論上我們完全可以像對待文件內部符號一樣把它們統統隱藏掉。但是在語言層面我們並沒有相關的語法用於表達這個概念(Java中的包訪問權限和C#中的internal類似這個概念)。不同的編譯器提供了不同的方式來完成這件事情,這篇文章總結了一種跨平臺的處理方式。

符號隱藏的作用

一般來說做符號隱藏有以下三個作用:

  • 安全,去掉不必要的符號,可以增加逆向破解的難度。
  • 壓縮空間,符號實際上是放在 dll 中的,去掉這些符號可以縮減 dll 的大小
  • 性能,符號隱藏掉意味着它不會參與到動態鏈接過程,編譯器可以有更大的優化空間,可能會產生更好的性能。

如何做符號隱藏

符號隱藏可以採用下面幾個步驟(文中假定你使用MSVC或者4.0以上版本的GCC,低版本GCC不支持符號隱藏):

1. 動態庫

符號能否隱藏在於它在動態鏈接的過程中是否需要用到。靜態庫實際上是目標文件的集合,它並沒有完成鏈接過程。所以符號隱藏通常都是基於動態庫的,靜態庫的符號隱藏沒有很好的跨平臺方式,如果想要嘗試,可以參考下面這些鏈接。

2. 默認隱藏所有的符號

MSVC和GCC在動態庫符號的默認屬性上面有較大的差別,MSVC默認所有的符號都是隱藏的,而GCC默認所有的符號都是可見的。雖然我不太喜歡臃腫的MSVC,但是我不得不承認在這一點上,我更傾向於MSVC的選擇。

如果你使用MSVC編譯器,這個步驟你可以什麼都不做,如果你使用GCC,你需要給你的編譯器加上-fvisibility=hidden選項,你也可以加上-fvisibility-inlines-hidden把內聯函數隱藏掉。如果你使用Autotool,你可以通過設置LD_CXXFLAG來控制默認隱藏,如果你使用CMake,可以通過set(CMAKE_CXX_VISIBILITY_PRESET hidden)來完成這一點。

3. 把你想要公開的接口的屬性設置爲外部可見

在MSVC中,我們通過在編譯的時候設置__declspec(dllimport)和使用的時候設置__declspec(dllexport)來完成這一點,在GCC中則簡單一些統一設置成__attribute__ ((visibility ("default")))即可。

輔助宏

上面這些步驟比較繁瑣,通常會定義宏來協助處理這一部分內容,下面是來自GCC WIKI的一個模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// Generic helper definitions for shared library support
#if defined _WIN32 || defined __CYGWIN__
#define FOX_HELPER_DLL_IMPORT __declspec(dllimport)
#define FOX_HELPER_DLL_EXPORT __declspec(dllexport)
#define FOX_HELPER_DLL_LOCAL
#else
#if __GNUC__ >= 4
#define FOX_HELPER_DLL_IMPORT __attribute__ ((visibility ("default")))
#define FOX_HELPER_DLL_EXPORT __attribute__ ((visibility ("default")))
#define FOX_HELPER_DLL_LOCAL __attribute__ ((visibility ("hidden")))
#else
#define FOX_HELPER_DLL_IMPORT
#define FOX_HELPER_DLL_EXPORT
#define FOX_HELPER_DLL_LOCAL
#endif
#endif

// Now we use the generic helper definitions above to define FOX_API and FOX_LOCAL.
// FOX_API is used for the public API symbols. It either DLL imports or DLL exports (or does nothing for static build)
// FOX_LOCAL is used for non-api symbols.

#ifdef FOX_DLL // defined if FOX is compiled as a DLL
#ifdef FOX_DLL_EXPORTS // defined if we are building the FOX DLL (instead of using it)
#define FOX_API FOX_HELPER_DLL_EXPORT
#else
#define FOX_API FOX_HELPER_DLL_IMPORT
#endif // FOX_DLL_EXPORTS
#define FOX_LOCAL FOX_HELPER_DLL_LOCAL
#else // FOX_DLL is not defined: this means FOX is a static lib.
#define FX_API
#define FOX_LOCAL
#endif // FOX_DLL

我們想要導出一個符號的時候使用FOX_API

1
class FOX_API Fox {};

在編譯動態庫的時候,設置FOX_DLLFOX_DLL_EXPORTS這兩個宏。在使用動態庫的是,定義FOX_DLL這個宏。

</div>
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章