Xcode構(gòu)建過(guò)程的后臺(tái)工作(一)構(gòu)建過(guò)程
Xcode構(gòu)建過(guò)程的后臺(tái)工作(二)clang構(gòu)建
Xcode構(gòu)建過(guò)程的后臺(tái)工作(三)swift構(gòu)建
構(gòu)建過(guò)程:鏈接
這是 Xcode構(gòu)建過(guò)程的最后一步塞淹。
首先瀏覽一下我們要討論的內(nèi)容矢空。我們將討論鏈接器是 什么蕾久,它所采用的輸入湘纵,即dylib和目標(biāo)文件及其定義嘱兼。還會(huì)講到符號(hào)及其內(nèi)容邦邦。最后會(huì)舉例總結(jié),因?yàn)閮?nèi)容比較難懂酥筝。
鏈接器是什么
鏈接器是構(gòu)建中的最后一個(gè)過(guò)程滚躯。我們所做的是將兩個(gè)編譯器構(gòu)建的所有.o文件 組合成一個(gè)可執(zhí)行文件。它所做的就是移動(dòng)和修補(bǔ)代碼嘿歌。 它無(wú)法創(chuàng)建代碼,這 很重要茁影,我將在示例中顯示宙帝。我們有兩種輸入文件。第一個(gè)是目標(biāo)文件(.o)募闲,在構(gòu)建過(guò)程中產(chǎn)生步脓。第二個(gè)是庫(kù),包括dylib浩螺,tbd和.a文件或靜態(tài)文檔靴患。
符號(hào)(symbols)
符號(hào)是一個(gè)代表代碼或數(shù)據(jù)片段的名稱。當(dāng)一個(gè)函數(shù)調(diào)用另一個(gè)函數(shù)要出,這些片段可能會(huì)指向其他符號(hào)鸳君。符號(hào)可以有很多影響鏈接器行為方式的屬性。我只舉一個(gè) 弱符號(hào)的例子患蹂。弱符號(hào)的注釋表示當(dāng)我們?cè)谙到y(tǒng)上運(yùn)行或者執(zhí)行文件時(shí)它可能不存在或颊。還有可用性標(biāo)記,標(biāo)記這個(gè)API用于iOS12传于,那個(gè)API用于iOS11.這就引出了現(xiàn)在的主題鏈接器囱挑。鏈接器可以確定哪些符號(hào)肯定會(huì)出現(xiàn)和哪些符號(hào)可以在運(yùn)行時(shí)處理。如前所述沼溜,語(yǔ)言可以通過(guò)命名修飾將數(shù)據(jù)編碼為符號(hào)平挑。在C++和Swift中都能看到。所以符號(hào)就是指代碼和數(shù)據(jù)的名稱系草。
目標(biāo)文件
目標(biāo)文件就是代碼和數(shù)據(jù)的集合通熄。它們不可執(zhí)行否淤。因?yàn)槭蔷幾g代碼,所以還沒(méi)有完成棠隐。還有缺失就需要鏈接器整合和修復(fù)石抡。每個(gè)文件的片段都用符號(hào)表示。例如對(duì)于printf函數(shù)助泽,就以符號(hào)代替代碼啰扛。對(duì)于PetKit的代碼后文會(huì)展示。
片段可能引用未定義的符號(hào)嗡贺。 因此隐解,如果您的.o文件引用另一個(gè).o文件中的函數(shù),那么.o文件是未定義的诫睬。鏈接器將找到那些未定義的符號(hào)并進(jìn)行匹配煞茫。所以目標(biāo)文件是編譯器操作的輸出。那么什么是庫(kù)摄凡?庫(kù)是定義符號(hào)的文件续徽,但不屬于構(gòu)建的目標(biāo)。我們有動(dòng)態(tài)庫(kù)亲澡,那些Mach-O文件钦扭,顯示了可執(zhí)行文件的代碼和數(shù)據(jù)片段。這些是系統(tǒng)的一部分床绪。這就是我們的框架客情。你也可能會(huì)用自己的框架。
還有TBD文件癞己,基于文本的dylib文件膀斋。在為iOS和macOS制作SDK時(shí),會(huì)有所有這些dylibs和以及您可能想要使用的功能痹雅,如MapKit和WebKit仰担。但是我們不想把所有這些跟SDK一起加載,因?yàn)樗鼤?huì)很大而且編譯器和鏈接器不需要它练慕,它只在運(yùn)行程序時(shí)有用惰匙。因此,我們創(chuàng)建了stub dylib铃将,刪除所有符號(hào)的主體项鬼,只留下名稱。完成之后劲阎,轉(zhuǎn)用文本表示绘盟,這對(duì)我們來(lái)說(shuō)更容易使用。目前,它們僅用于分發(fā)SDK以減小大小龄毡。所以你在項(xiàng)目中看到它們時(shí)不必?fù)?dān)心吠卷,它們只是符號(hào)。
最后是靜態(tài)庫(kù)(static archives)沦零。靜態(tài)庫(kù)是使用AR工具構(gòu)建的.o文件的集合祭隔,也可能是lib,它是lib工具的包裝器路操。根據(jù)AR操作文檔疾渴,AR創(chuàng)建并維護(hù)的文件組,將它們合并為一個(gè)庫(kù)屯仗。聽(tīng)起來(lái)很像TAR文件或ZIP文件搞坝,這正是它的本質(zhì)。實(shí)際上魁袜,.a格式是UNIX在使用更強(qiáng)大的工具之前使用的原始庫(kù)格式桩撮。但是現(xiàn)在的編譯器和連接器可以完全理解它們,所以繼續(xù)使用它們峰弹。它就只是個(gè)檔案 文件店量。值得注意的是,它們?cè)杏藙?dòng)態(tài)鏈接垮卓,在過(guò)去所有代碼都會(huì)被存檔垫桂。因此, 不能使用的是一個(gè)函數(shù)涵蓋所有C庫(kù) 粟按。所以行為是,如果.o文件中有符號(hào)霹粥,我們會(huì)將整個(gè).o文件從庫(kù)中拉出來(lái)灭将。但是不會(huì)引入其他.o文件。如果你在它們之間引用符號(hào)后控,只要帶入即可庙曙。如果你是非符號(hào)行為,比如靜態(tài)初始化程序浩淘,或者將它們重新導(dǎo)出為您自己的dylib的一部分捌朴,您要明確地用到強(qiáng)制加載,或定制加載讓鏈接器提取所有或者這些文件张抄,即便之間沒(méi)有關(guān)聯(lián)砂蔽。我們通過(guò)一個(gè)例子串聯(lián)起這些內(nèi)容。
上面是playSound函數(shù)的例子署惯,只看寵物不聽(tīng)聲音有什么樂(lè)趣左驾?cat上有一個(gè)調(diào)用playSound的函數(shù)。上圖右邊是生成的程序集。輸出文件是cat.o诡右。字符串purr.aac安岂,是AAC聲音文件。這會(huì)被復(fù)制到cat.o. 您會(huì)注意到名稱purr文件不見(jiàn)了帆吻。因?yàn)樗庆o態(tài)的域那。如果你熟悉C語(yǔ)言,這是非導(dǎo)出命名猜煮。沒(méi)有其他人可以引用它次员。既然如此,我們不需要它友瘤,排除掉翠肘。
然后我們看到Cat purr變成了符號(hào):-[Cat purr]。跟預(yù)想的差不多辫秧。
然后我們要把這個(gè)變量傳遞給playSound束倍。這里出現(xiàn)了兩個(gè)指令,這是因?yàn)槲覀儾恢肋@個(gè)字符串最后在可執(zhí)行文件中的位置盟戏,我們沒(méi)有具體的地址 绪妹。 但是我們知道RM64就是這個(gè)程序集,它最多可能需要兩條指令柿究。所以編譯器給我們留下了兩條指令邮旷。它留下符號(hào)偏移量,值為PAGE和PAGEOFF,鏈接器之后回來(lái)修復(fù)蝇摸。 最后婶肩,既然已經(jīng)將字符串加載到x0中,我們可以調(diào)用playSound貌夕,我們寫(xiě)入__z9playSoundPKc律歼。這是一個(gè)變形的符號(hào),如果仔細(xì)看會(huì)看到cat.mm啡专,這是Objective-C++险毁。playSound實(shí)際上是一個(gè)C++函數(shù)。所以如果你不熟悉们童,你可以在終端輸入命令畔况。
如果運(yùn)行Swift-demangle并傳入符號(hào),然后反修飾慧库。沒(méi)有用跷跪,它不是swift的符號(hào)。但是C++ 反修飾器C++ filts告訴我們這實(shí)際上是playSound的符號(hào)完沪。 除了playSound域庇,它還有一個(gè)實(shí)參嵌戈。這個(gè)參數(shù)是一個(gè)const char* 因?yàn)镃++會(huì)將更多信息編入修飾符號(hào)中。現(xiàn)在有了.o文件听皿,實(shí)際構(gòu)建中會(huì)有更多熟呛。那我們?cè)撛趺醋瞿兀?br>
首先,構(gòu)建系統(tǒng)將把所有.o作為輸入傳遞給鏈接器尉姨。鏈接器會(huì)創(chuàng)建一個(gè)文件來(lái)放入它們 庵朝。這里構(gòu)建的PetKit,是PetWall的內(nèi)嵌框架又厉。 因此九府,我們只要復(fù)制,創(chuàng)建一個(gè)文本片段用來(lái)保存app的所有代碼的覆致。然后復(fù)制cat.o到這里侄旬。但是要分成兩部分,一個(gè)用于字符串煌妈,一個(gè)用于可執(zhí)行代碼±芨幔現(xiàn)在已知這些東西的絕對(duì)地址,因此 鏈接器可以重寫(xiě)cat.o璧诵,以從特定偏移量加載汰蜘。 你會(huì)注意到第二條指令就消失了。它被一個(gè)null指令代替之宿,沒(méi)有任何行動(dòng)族操。但我們無(wú)法刪除 指令,因?yàn)槲覀儫o(wú)法創(chuàng)建或刪除代碼比被,這會(huì)打亂所有已完成的工作色难。所以與其刪除,不如替換為無(wú)行動(dòng)等缀。
最后是分支莱预。我們有一個(gè)未定義的符號(hào),我們將繼續(xù)瀏覽所有已經(jīng)導(dǎo)入的.o文件项滑。
所以我們將開(kāi)始查看靜態(tài)庫(kù),上圖是PetSupport.a涯贞。在PetSupport.a中有幾個(gè)文件枪狂,包括PetSounds.o。大家能看到匹配playSound的符號(hào)宋渔。 所以我們把它拉進(jìn)來(lái)州疾。PetCare.o不能被引入,因?yàn)?o文件沒(méi)有任何符號(hào)能被app的其他部分引用皇拣。我們把它拉進(jìn)來(lái)严蓖,但現(xiàn)在需要_open薄嫡,但是我們沒(méi)有定義。拉入的對(duì)話已經(jīng)變成_open $stub颗胡。為什么呢毫深?因?yàn)槲覀儼l(fā)現(xiàn)open的副本在lib系統(tǒng)的TBD文件中。
我們知道這不是系統(tǒng)庫(kù)的一部分毒姨,我不會(huì)其復(fù)制到我的app中哑蔫。但是我需要在app中加入足夠的信息 以便調(diào)用它。
因此弧呐,我們創(chuàng)建了一個(gè)假函數(shù) 闸迷,它只是一個(gè)模板,用來(lái)代替從lib系統(tǒng)拿走的函數(shù)俘枫,這里就是open腥沽。觀察該函數(shù),它實(shí)際上是來(lái)自指針open$pointer鸠蚪,然后跳到它今阳。這需要一個(gè)函數(shù)指針,就像任何普通的C函數(shù)指針一樣邓嘹。然后在數(shù)據(jù)段中 創(chuàng)建它酣栈,如果有全局變量,那么就會(huì)出現(xiàn)在這里汹押。但它只是設(shè)置為零矿筝,如果如果空引用會(huì)導(dǎo)致崩潰。然后我們添加一個(gè)LINKEDIT部分棚贾。
LINKEDIT是鏈接器工具用于為操作系統(tǒng)保留信息的元數(shù)據(jù)窖维,這就是在運(yùn)行時(shí)解決問(wèn)題的動(dòng)態(tài)鏈接器。有關(guān)這方面的更多信息妙痹,請(qǐng)查看2016年的 Optimizing App Startup Time 演講铸史。