C/C++符號(hào)隱藏與依賴管理(二):庫(kù)的符號(hào)隱藏

當(dāng)程序規(guī)模變大之后境蔼,人們會(huì)對(duì)軟件進(jìn)行模塊劃分延塑,以便分而治之。有了模塊之后惕蹄,就可以將其構(gòu)建成庫(kù)(靜態(tài)庫(kù)或者動(dòng)態(tài)庫(kù))發(fā)布給別人使用。

前文所述的符號(hào)隱藏手段對(duì)于模塊內(nèi)代碼的信息隱藏是夠的,但是對(duì)于庫(kù)來(lái)說(shuō)是不夠的卖陵。

當(dāng)程序規(guī)模變大后遭顶,我們不可能把所有代碼都寫(xiě)到同一個(gè)C文件或者CPP文件中。當(dāng)代碼被拆分到多個(gè)實(shí)現(xiàn)文件中泪蔫,它們之間需要互相訪問(wèn)就必須通過(guò)頭文件暴露自己的可訪問(wèn)API給別人棒旗。但是當(dāng)所有文件都被打包在一起編譯成庫(kù)再提供給第三方的時(shí)候,這些內(nèi)部開(kāi)放的接口卻未必都需要被作為庫(kù)接口暴露出去鸥滨。

常見(jiàn)的一種做法是將庫(kù)的內(nèi)部頭文件和外部的頭文件分開(kāi)嗦哆,對(duì)外不發(fā)布內(nèi)部頭文件。這是C/C++常用的一種庫(kù)級(jí)別的頭文件管理手段婿滓,后面我們會(huì)專門(mén)介紹老速。遺憾的是,僅通過(guò)不發(fā)布私有頭文件凸主,并沒(méi)有解決所有問(wèn)題橘券。

即便不發(fā)布內(nèi)部頭文件,內(nèi)部跨編譯單元可被訪問(wèn)的符號(hào)默認(rèn)情況下仍舊會(huì)被庫(kù)全部導(dǎo)出卿吐。這樣不僅浪費(fèi)了二進(jìn)制的空間旁舰,增加了庫(kù)之間符號(hào)沖突的概率,而且還讓軟件包承擔(dān)了不必要的安全風(fēng)險(xiǎn)嗡官。導(dǎo)出的內(nèi)部符號(hào)仍舊可以被外部強(qiáng)制extern箭窜,或者是被拿來(lái)做一些hack的事情。

現(xiàn)代編程語(yǔ)言會(huì)引入module機(jī)制來(lái)管理軟件模塊或者庫(kù)的外部可見(jiàn)性問(wèn)題衍腥,讓開(kāi)發(fā)者在發(fā)布軟件的時(shí)候顯示的指定需要導(dǎo)出給外部的API磺樱,其它的符號(hào)都只能被內(nèi)部訪問(wèn)。但是C和C++語(yǔ)言由于歷史包袱重(新的特性需要盡量兼容已經(jīng)編譯過(guò)的既有代碼)婆咸,C++語(yǔ)言直到20版本才將module特性標(biāo)準(zhǔn)化竹捉,而C語(yǔ)言的module特性至今仍不見(jiàn)蹤影。(事實(shí)上Java的module特性從2011年提出直到2017年才通過(guò)Java9發(fā)布尚骄,也歷時(shí)七年之久)块差。

由于C++20標(biāo)準(zhǔn)剛剛出來(lái)不久,編譯器對(duì)module機(jī)制的支持還很不完善倔丈,所以該特性離進(jìn)入實(shí)用還有不少距離憨闰。感興趣的同學(xué)可以看看我的朋友張超寫(xiě)的這篇文章《C++ Modules 初窺》

回到現(xiàn)實(shí)中需五,在沒(méi)有語(yǔ)言直接支持的情況下鹉动,我們?nèi)绾坞[藏庫(kù)的內(nèi)部符號(hào),顯示的指定需要導(dǎo)出的API呢警儒?

方法是有的训裆,那就是借助編譯器擴(kuò)展眶根。

GCC4之后支持使用-fvisibility=hidden編譯選項(xiàng),將庫(kù)的所有符號(hào)默認(rèn)設(shè)置為對(duì)外不可見(jiàn)边琉。這樣編譯出的二進(jìn)制就不會(huì)導(dǎo)出可供外部鏈接的符號(hào)属百。然后再結(jié)合GCC的__attribute__ ((visibility ("default")))屬性,在代碼中明確指定可以暴露給外部的API变姨,于是我們就可以顯示的控制庫(kù)的對(duì)外API的可見(jiàn)性族扰。

如下代碼示例:

// entry.h

void function1();
__attribute__ ((visibility ("default"))) void entry_point();
// entry.cpp

#include "entry.h"

void function1() {
    // ...
}

void entry_point() {
    function1();
}

當(dāng)我們采用-fvisibility=hidden將entry.cpp編譯成靜態(tài)庫(kù)或者動(dòng)態(tài)庫(kù)后,無(wú)論用戶是靜態(tài)鏈接還是使用dlopen動(dòng)態(tài)庫(kù)的方式定欧,都只能訪問(wèn)到void entry_point()函數(shù)渔呵,而不能訪問(wèn)到void funcion1()

通過(guò)該方法砍鸠,我們不僅能顯示控制庫(kù)的導(dǎo)出API扩氢,還可以幫助編譯器和鏈接器優(yōu)化出更好的二進(jìn)制,并且縮短動(dòng)態(tài)庫(kù)的加載時(shí)間爷辱。

Windows下也有類似的機(jī)制__declspec(dllexport)录豺,它和gcc下的__attribute__ ((visibility ("default")))作用類似。稍微不同的是Windows下還存在__declspec(dllimport)用于API的使用方顯示導(dǎo)入外部API饭弓,以便編譯器對(duì)代碼進(jìn)行優(yōu)化双饥,但gcc下沒(méi)有對(duì)應(yīng)的擴(kuò)展。

為了讓使用上述編譯器擴(kuò)展的代碼能夠跨平臺(tái)弟断,使用該特性的時(shí)候可以封裝一個(gè)宏咏花,根據(jù)代碼所在的平臺(tái)和編譯器版本,自動(dòng)轉(zhuǎn)化成不同的實(shí)現(xiàn)阀趴。

// keywords.h

#if defined _WIN32 || defined __CYGWIN__
  #ifdef BUILDING_MOD
    #ifdef __GNUC__
      #define MOD_PUBLIC __attribute__ ((dllexport))
    #else
      #define MOD_PUBLIC __declspec(dllexport) // Note: actually gcc seems to also supports this syntax.
    #endif
  #else
    #ifdef __GNUC__
      #define MOD_PUBLIC __attribute__ ((dllimport))
    #else
      #define MOD_PUBLIC __declspec(dllimport) // Note: actually gcc seems to also supports this syntax.
    #endif
  #endif
  #define MOD_LOCAL
#else
  #if __GNUC__ >= 4
    #define MOD_PUBLIC __attribute__ ((visibility ("default")))
    #define MOD_LOCAL  __attribute__ ((visibility ("hidden")))
  #else
    #define MOD_PUBLIC
    #define MOD_LOCAL
  #endif
#endif

如上參考了"https://gcc.gnu.org/wiki/Visibility"中給出的宏定義昏翰。它根據(jù)不同的平臺(tái)和編譯器版本,定義了MOD_PUBLICMOD_LOCAL的不同實(shí)現(xiàn)舍咖。

#include "keywords.h"

MOD_PUBLIC void function(int a);

class MOD_PUBLIC SomeClass
{
   int c;
   // Only for use within this DSO(Dynamic Shared Object)
   MOD_LOCAL void privateMethod();
public:
   Person(int _c) : c(_c) { }
   static void foo(int a);
};

如上的例子中矩父,void function(int a)class SomeClass在庫(kù)的內(nèi)部和外部都可訪問(wèn)锉桑,但是類的void privateMethod()接口只能在庫(kù)的內(nèi)部使用排霉,外部是無(wú)法使用的。

至此民轴,我們給出當(dāng)前現(xiàn)狀下C/C++庫(kù)級(jí)別API的管理建議:可以使用編譯選項(xiàng)默認(rèn)隱藏庫(kù)的符號(hào)攻柠,然后使用編譯器屬性顯示指定庫(kù)需要導(dǎo)出的API

最后我們補(bǔ)充一點(diǎn)對(duì)動(dòng)態(tài)庫(kù)的要求后裸。

不同平臺(tái)對(duì)于靜態(tài)庫(kù)和動(dòng)態(tài)庫(kù)的使用大部分時(shí)候是相似的瑰钮,但在某些細(xì)節(jié)上仍然會(huì)有區(qū)別。

所有平臺(tái)下的靜態(tài)庫(kù)(.a或者.lib)都是可以缺符號(hào)的微驶,即在生成時(shí)可以存在待鏈接的外部符號(hào)浪谴。然而對(duì)于動(dòng)態(tài)庫(kù)开睡,OSX下要求不能缺符號(hào)(OSX下動(dòng)態(tài)庫(kù)是dylib格式,生成時(shí)是需要鏈接成功的苟耻,如果缺符號(hào)鏈接器會(huì)報(bào)錯(cuò))篇恒。而在Linux系統(tǒng)下動(dòng)態(tài)庫(kù)(.so)生成的時(shí)候卻是可以缺符號(hào)的。

在Linux下凶杖,如果是在鏈接期使用缺符號(hào)的so胁艰,需要構(gòu)建目標(biāo)通過(guò)指定其它的動(dòng)態(tài)庫(kù)或者靜態(tài)庫(kù)為缺失符號(hào)的so把符號(hào)補(bǔ)全,否則就會(huì)鏈接失敗智蝠。而如果是采用dlopen的方式打開(kāi)so的話腾么,那么該so必須自身符號(hào)是完備的,否則在動(dòng)態(tài)加載的時(shí)候會(huì)出錯(cuò)杈湾。

因此解虱,這里我們給出另一個(gè)C/C++庫(kù)符號(hào)管理的建議:保證動(dòng)態(tài)庫(kù)不要缺符號(hào),是自滿足的漆撞。如果違反了這條原則饭寺,那么這個(gè)動(dòng)態(tài)庫(kù)就無(wú)法用于動(dòng)態(tài)加載;即使只是鏈接期使用叫挟,因?yàn)榘逊?hào)缺失的細(xì)節(jié)泄露給了使用者艰匙,造成使用方的麻煩,所以也是不推薦的抹恳。

動(dòng)態(tài)庫(kù)可以和靜態(tài)庫(kù)進(jìn)行鏈接员凝,以獲取自己需要的符號(hào)。但是有些時(shí)候我們只想要和靜態(tài)庫(kù)進(jìn)行鏈接奋献,卻不想在動(dòng)態(tài)庫(kù)中將靜態(tài)庫(kù)中的符號(hào)間接暴露出去健霹。這時(shí)可以采用-fvisibility=hidden選項(xiàng)重新編譯該靜態(tài)庫(kù)。但遺憾的是我們不總是能夠控制第三方靜態(tài)庫(kù)的編譯過(guò)程瓶蚂,這時(shí)可以借助鏈接器提供的顯示指定符號(hào)表的方法糖埋。該方法需要按照鏈接器的規(guī)范寫(xiě)一個(gè)導(dǎo)出符號(hào)表,在鏈接期通過(guò)參數(shù)傳遞給鏈接器窃这,這樣就可以精細(xì)的控制動(dòng)態(tài)庫(kù)需要暴露的符號(hào)了瞳别。該方法并不常用,因此我們不多做介紹杭攻,具體用法可以參考https://www.gnu.org/software/gnulib/manual/html_node/LD-Version-Scripts.html祟敛。

而動(dòng)態(tài)庫(kù)和動(dòng)態(tài)庫(kù)的鏈接,其實(shí)并不需要把對(duì)方的二進(jìn)制真實(shí)鏈接進(jìn)來(lái)兆解。目標(biāo)的動(dòng)態(tài)庫(kù)會(huì)記住它所依賴的動(dòng)態(tài)庫(kù)(通過(guò)目標(biāo)動(dòng)態(tài)庫(kù)中的rpath)馆铁。這種情況下也算該動(dòng)態(tài)庫(kù)是自滿足的,因?yàn)橛脩粼谑褂迷搫?dòng)態(tài)庫(kù)的時(shí)候锅睛,并不需要再為其尋找依賴埠巨。

最后我們總結(jié)一下對(duì)于庫(kù)符號(hào)管理的一些建議:

1)推薦使用編譯選項(xiàng)默認(rèn)隱藏庫(kù)的所有符號(hào)历谍,然后使用編譯器屬性顯示指定庫(kù)需要導(dǎo)出的API;
(建議對(duì)該方法進(jìn)行封裝辣垒,以保證代碼兼容各種平臺(tái)和編譯器版本)

2)保證動(dòng)態(tài)庫(kù)不要缺符號(hào)扮饶,是自滿足的;

C/C++符號(hào)隱藏與依賴管理(三):頭文件管理

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末乍构,一起剝皮案震驚了整個(gè)濱河市甜无,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌哥遮,老刑警劉巖岂丘,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異眠饮,居然都是意外死亡奥帘,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)仪召,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)寨蹋,“玉大人,你說(shuō)我怎么就攤上這事扔茅∫丫桑” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵召娜,是天一觀的道長(zhǎng)运褪。 經(jīng)常有香客問(wèn)我,道長(zhǎng)玖瘸,這世上最難降的妖魔是什么秸讹? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮雅倒,結(jié)果婚禮上璃诀,老公的妹妹穿的比我還像新娘。我一直安慰自己蔑匣,他們只是感情好劣欢,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著殖演,像睡著了一般氧秘。 火紅的嫁衣襯著肌膚如雪年鸳。 梳的紋絲不亂的頭發(fā)上趴久,一...
    開(kāi)封第一講書(shū)人閱讀 49,007評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音搔确,去河邊找鬼彼棍。 笑死灭忠,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的座硕。 我是一名探鬼主播弛作,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼华匾!你這毒婦竟也來(lái)了映琳?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤蜘拉,失蹤者是張志新(化名)和其女友劉穎萨西,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體旭旭,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡谎脯,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了持寄。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片源梭。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖稍味,靈堂內(nèi)的尸體忽然破棺而出废麻,到底是詐尸還是另有隱情,我是刑警寧澤模庐,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布脑溢,位于F島的核電站,受9級(jí)特大地震影響赖欣,放射性物質(zhì)發(fā)生泄漏屑彻。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一顶吮、第九天 我趴在偏房一處隱蔽的房頂上張望社牲。 院中可真熱鬧,春花似錦悴了、人聲如沸搏恤。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)熟空。三九已至,卻和暖如春搞莺,著一層夾襖步出監(jiān)牢的瞬間息罗,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工才沧, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留迈喉,地道東北人绍刮。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像挨摸,于是被迫代替她去往敵國(guó)和親孩革。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容