圖解 Mach-O 中的 got

got 是什么

iOS 開發(fā)中垦藏,動態(tài)庫是個繞不開的話題,系統(tǒng)庫基本上是動態(tài)庫轰驳。它的一大優(yōu)勢是節(jié)約內(nèi)存弟灼,可讓多個程序映射同一份的動態(tài)庫,實現(xiàn)代碼共享田绑。動態(tài)庫本身也是一個 Mach-O 文件,也有數(shù)據(jù)段芒划、代碼段等欧穴。其中代碼段可讀可執(zhí)行,數(shù)據(jù)段可讀可寫涮帘。

動態(tài)庫共享的只是代碼段部分,為了達(dá)到代碼段共享的目的疮鲫,其符號地址在生成時就不能寫死,因為它映射到每個程序中虛擬內(nèi)存空間中的位置可能不一樣棚点。對于數(shù)據(jù)段部分,由于各個程序會對其進行修改瘫析,因此每個程序會單獨映射一份。

那么如何解決代碼段共享的問題呢咸包?聰明的人們杖虾,想出一種精妙的解決方式。通過添加一個中間層奇适,到另一個表中去查找符號的地址。這個表就叫 got嚷往,global offset table,全局符號偏移表籍琳,然后在運行時綁定地址信息贷祈,將地址填入到 got 中。這樣代碼段中的符號就與具體地址無關(guān)势誊,只和 got 有關(guān)。這種方式就叫 PIC闻丑,Program Independent Code勋颖,程序地址無關(guān)代碼。

或許你可能會想到饭玲,got 中保存的是符號地址,而每個程序的地址是不一樣的矮冬,那 got 肯定是不能共享的。沒錯胎署,所以 got 會保存在數(shù)據(jù)段中,每個程序單獨一份恢筝。在進行符號綁定時巨坊,更新 got 中對應(yīng)符號的地址即可。

got 的位置

在了解 got 是什么之后侄柔,我們再來看看 Mach-Ogot 到底放在了哪里。

通過下圖可以看出暂题,有個專門的 __got section 存放 got 數(shù)據(jù)妈候,而它是屬于 __DATA segment

image

對于 segmentsection,可能大家會有些困惑赶站。下面來簡單解釋一下。

section

section 稱為節(jié)贝椿,是編譯器對 .o 內(nèi)容的劃分,將同類資源在邏輯上劃分到一起瑟蜈。常見的 section 有:

  • 存放代碼指令渣窜,.text

  • 存放已初始化全局變量,.data

  • 存放未初始化的全局變量和靜態(tài)局部變量乔宿,.bss

  • 符號表,.symtab

  • 字符串表掂林,.strtab

segment

segment 稱為段,它是權(quán)限屬性相同 section 的集合精置。

在程序裝載時,操作系統(tǒng)并不關(guān)心 section 的數(shù)量和內(nèi)容脂倦,只對其權(quán)限敏感蹲堂,因此沒必要一個個加載 section,只需將權(quán)限相同的 section 合到一起加載即可政供。

另外,這樣還可節(jié)省內(nèi)存布隔。由于內(nèi)存按頁分配稼虎,即使不滿一個頁也得分配一整頁。若單個 section 大小非系統(tǒng)頁長度的整數(shù)倍哀军,會造成內(nèi)存碎片。而將其合并后杉适,會有效緩解這種情況柳击。

舉個栗子, .text.init 的權(quán)限都是只讀可執(zhí)行,.init 是程序初始化代碼蹬叭。

假設(shè)頁的大小是 4 KB.text 大小為 4098 字節(jié)秽五,.init 大小為 900 字節(jié)试幽。如下圖所示卦碾,若將它們單獨映射起宽,.text 會占用 2 個頁,.init 占用 1 個頁绿映,整體占用 3 個頁腐晾。

image

如果它們合并成代碼段,那么只需占用 2 個頁淹冰,減少內(nèi)存浪費。如下圖所示樱拴。

image

可執(zhí)行文件是由多個 .o 文件鏈接而成的洋满,每個 .o 文件有各自的 section。因此鏈接器將所有 .o 文件中權(quán)限相同的 section 合并到一起正罢,形成 segment驻民。操作系統(tǒng)只需將 segment 映射到虛擬內(nèi)存空間即可。

平常我們所說的代碼段回还、數(shù)據(jù)段,便是指鏈接后的 segment鞭盟。

動態(tài)庫符號類型

動態(tài)庫中的符號分為 non-lazy symbollazy symbol

  • non-lazy symbol,是指在啟動時就必須鏈接的符號本刽,確定好符號地址候味。

  • lazy symbol 洪灯,顧名思義,延遲綁定符號,只在使用時才進行鏈接坏快。

為啥要分為兩種類型呢憎夷?我們試想一下,如果所有動態(tài)庫的符號都是啟動時鏈接拾给,一個程序隨隨便便依賴的系統(tǒng)動態(tài)庫就有大幾十個。每個動態(tài)庫中符號還不少级及,并且也不是所有符號都會用到额衙,這樣勢必會拖慢啟動速度。所以采用延遲綁定技術(shù)入偷,只需在第一次用到時進行綁定,可提高性能殿雪。而數(shù)據(jù)符號相對較少锋爪,則可以采用 non-lazy 的方式,放到啟動時就鏈接其骄。

因此,Mach-O 中劃分了兩個 section 來保存 non-lazy symbollazy symbol索抓。其中 __got 中保存的是 non-lazy symbol__la_symbol_ptr 保存的是 lazy symbol逼肯。

下面桃煎,我們來實踐一下,驗證上述說法的正確性三椿。請將以下文件放在同一個目錄下缺菌。

print.c:


#include <stdio.h>

char *global = "hello";

void print(char *str)

{

 printf("%s\n", str);

}

main.c:


void print(char *str);

extern char *global;

int main()

{

 print(global);

  return  0;

}

run.sh:


// 生成 main.o伴郁,目標(biāo)版本 14.0

xcrun -sdk iphoneos clang -c main.c -o main.o -target arm64-apple-ios14.0

// 生成 libPrint.dylib 動態(tài)庫

xcrun -sdk iphoneos clang -fPIC -shared print.c -o libPrint.dylib -target arm64-apple-ios14.0

// 鏈接生成可執(zhí)行文件纽乱,"-L .", 表示在當(dāng)前目錄中查找。"-l Print"租冠,鏈接 libPrint.dylib 動態(tài)庫

xcrun -sdk iphoneos clang main.o -o main -L . -l Print -target arm64-apple-ios14.0

run.sh 添加可執(zhí)行權(quán)限后再運行,生成可執(zhí)行文件顽爹。


chmod +x run.sh

./run.sh

執(zhí)行完畢后骆姐,在目錄中會生成 libPrint.dylib 動態(tài)庫和 main 可執(zhí)行文件。

main 拖到 MachOView 中肉渴,如下圖所示:

image

右邊紅框中的 _global 就是動態(tài)庫 libPrint.dylib 中的符號带射。它被放到了 __got 中,并且其初始地址為 0窟社。它是表的第一項,表地址是 0x10008000关炼,那么 0x10008000 中的值就是符號地址匣吊。

另外,我們還發(fā)現(xiàn)色鸳,在 __got 中還有一條記錄 dyld_stub_binder,初始地址也是 0。它是表的第二項池户,也就是 0x10008008 地址中的值為符號地址凡怎。稍后會講它的作用赊抖。

_global 在啟動時會進行鏈接,那么如何知道需要鏈接哪個動態(tài)庫呢氛雪?我們點開 Symbol Table,會看到如下信息:

image

可見浴鸿,符號表中已經(jīng)包含了 _global 所屬動態(tài)庫的信息弦追,libPrint.dylib。同樣 dyld_stub_binder 劲件,它在 libSystem.B.dylib 中。

雖然動態(tài)庫中的符號苗分,在生成可執(zhí)行文件時牵辣,沒有進行鏈接,但是在符號表中記錄了它在哪個動態(tài)庫中服猪。這樣在運行時進行鏈接,才能到相應(yīng)動態(tài)庫中找到罢猪。

dyld_stub_binder

在上節(jié)中,我們遇到了 dyld_stub_binder 這個陌生人粘捎。從字面意思危彩,我們大致可以猜到,它是用來做符號綁定用的汤徽。前面提到過,函數(shù)符號都是在第一次使用時才進行綁定拼坎,其實是通過 dyld_stub_binder 來進行符號查找與地址重定位浮毯。鑒于它肩負(fù)重大使命债蓝,因此必須預(yù)先綁定好地址,所以會放到 __got 中饰迹。

dyld_stub_binder 是用匯編實現(xiàn)的余舶,在 dyld_stub_binder.s 中。它的調(diào)用鏈路如下:


// 匯編中調(diào)用 fastBindLazySymbol
1. dyld::fastBindLazySymbol

// 調(diào)用 ImageLoader 處理
2. ImageLoaderMachOCompressed::doBindFastLazySymbol

// 符號綁定
3. ImageLoaderMachOCompressed::bindAt

// 符號地址解析
4. ImageLoaderMachOCompressed::resolve

// 符號地址更新
5. ImageLoaderMachO::bindLocation

其中 resolve 是解析符號地址莉掂,bindLocation 進行符號地址更新。

lazy 符號重定位

上面我們說到憎妙,函數(shù)符號的重定位是通過 dyld_stub_binder 來做的曲楚,那么有沒有依據(jù)可尋呢?當(dāng)然有啦龙誊。

從下圖可以看出,_print 的地址是 0x100007FAC鹤树,不是說在第一次調(diào)用時才綁定地址嗎?為什么該函數(shù)的地址會有值呢罕伯?沒錯叽讳,但它需要有人幫忙來進行地址重定位,這個幫手就是 0x100007FAC 處的神秘嘉賓岛蚤。

image

這個地址處在 __TEXT 段范圍,通過查看 __TEXT 段各個 section 的地址范圍单雾,我們很容易發(fā)現(xiàn)它處在 __stub_helper 中。如下圖所示:

image

請注意看圖上的 1蜂奸、2、3 標(biāo)號。地址 0x100007FAC 處于 1 號围详。它對應(yīng)的匯編代碼功能是:

  • 取出 0x100007fb4 處的值放入 w16,也就是將 w16 清 0买羞。

  • b 是無返回跳轉(zhuǎn)指令,跳轉(zhuǎn)到 0x100007f94畜普,也就是開頭 2 號處群叶。

然后,從 2 號處開始執(zhí)行街立,一直到 3 號位置。3 號區(qū)域的功能是:

  • 第一行是相對地址偏移取值指令赎离。在距離當(dāng)前行地址 0x10007FA4 偏移 0x64 的地方取出值,放入 x16虽画。也就是取出 0x10007FA4 + 0x64 = 0x10008008 處的內(nèi)容荣病。

  • br x16,進行函數(shù)調(diào)用众雷,跳轉(zhuǎn)到 x16 中的地址。

所以砾省,最主要是得弄清楚 0x10008008 地址里面的內(nèi)容是啥,根據(jù) br 指令推斷轩性,它肯定是個函數(shù)地址狠鸳。

有沒有覺得 0x10008008 有些熟悉呢悯嗓?再看看下面這張圖卸察,其實在第一節(jié)的圖中我們已經(jīng)看到過它。got 中第二項的地址就是 0x10008008坑质,而它正好存儲的是 dyld_stub_binder 地址。

image

這樣稼跳,一切都清楚了吃沪。

  • 函數(shù)符號的地址綁定會調(diào)用到 dyld_stub_binder

  • 通過它獲取到地址后票彪,再更新下圖中紅框處的值為函數(shù)的真正地址。

  • 以后就不用走 dyld_stub_binder 地址綁定的流程了锉屈,直接跳轉(zhuǎn)到函數(shù)地址去執(zhí)行。

image

got 符號值查找

查找原理

變量和函數(shù)統(tǒng)稱為符號颈渊,所有符號信息都在符號表 Symbol Table 中终佛,符號值在字符串表 String Table 中。符號表只是記錄了它在字符串表中的下標(biāo)铃彰,因為這樣可以節(jié)省空間。

而我們上文中提到的 global 是個外部全局變量竹揍,那么它存在了符號表中的哪里?可以通過何種路徑找到它呢芬位?下面來探尋一下带到。

首先讓我們回到 Mach-OLoad Commands 中。它里面有一系列的加載命令,告訴系統(tǒng)如何加載不同的 segment四康。加載命令中包含了 Section Header 的數(shù)組狭握,header 里面包含了每個 section 的基礎(chǔ)信息,比如節(jié)名稱论颅、所屬 segment 的名稱、地址嗅辣、大小澡谭、偏移、保留字段等等蛙奖。

既然 __got 是一個 section杆兵,那么肯定也有對應(yīng)的頭信息。從下圖可以看到琐脏,在 LG_SEGMENT_64(__DATA_CONST) 中,包含了 __gotheader日裙。

image

注意右邊紅框中 Indirect Sym Indx 部分吹艇,它表示了 __got 中的第一個符號在間接表中的下標(biāo)受神,間接表其實就是動態(tài)庫符號表格侯。如果 __got 中有多個符號,那么下標(biāo)依次 +1 即可联四。

舉個栗子,假設(shè) __got 第一個符號在間接表中的下標(biāo)是 x碎连,那么第二個符號的下標(biāo)為 x+1,第三個為 x+2廉嚼,以此類推。如下圖所示:

image

而間接表中的內(nèi)容是該符號在符號表的下標(biāo)怠噪,取出內(nèi)容,然后到符號表中查找矫夷,便可找到符號信息。到這里還沒完双藕,由于符號值并不是直接存在符號表中阳仔,而是在字符串表。最后拿字符串下標(biāo)到字符串表中查找近范。

這里有點繞,流程如下:


1.  通過 __got section header评矩,拿到 indirectSymIndex。

2.  拿 indirectSymIndex 到間接表中(indirect symbol table)取到符號表中的下標(biāo) symIndex虱颗。

3.  拿 symIndex 到符號表中取到最終的符號信息,這里有它在字符串表中的下標(biāo) strIndex上枕。

4.  拿 strIndex 到字符串表中取到符號字符字符串弱恒。

整體圖示如下(注:符號表中僅畫出了下標(biāo),省略了其他信息):

image

實踐驗證

光說不練假把式锈玉,下面我們來驗證一下。

__got section header 中在間接符號表的下標(biāo)為 1拉背,也就是說第一個符號下標(biāo)為 1。從上文圖中可以看到椅棺,__got 中總共有 2 個符號,分別為 _globaldyld_stub_bind两疚。如果找到的符號為 _global,那么表示上述結(jié)論是正確的诱渤。

此時 __got section header 的數(shù)據(jù)如下圖所示,indirect sym index = 1

image

那我們到 dynamic symbol table 中去瞧一瞧递胧,找到下標(biāo)為 1 的數(shù)據(jù)信息,即第二個數(shù)據(jù)缎脾。如下所示:

image

從上圖可以看出占卧,在對應(yīng)的 Data 一列中,內(nèi)容為 3屉栓,表示它在符號表中的下標(biāo)為 3友多。

此時 indirect symbol table 中的數(shù)據(jù)如下所示:

image

然后繼續(xù)到符號表中看看下標(biāo)為 3 的數(shù)據(jù)是啥堤框。如下圖所示:

image

第四項數(shù)據(jù) String Table Index,它的值是 0x1c蜈抓,轉(zhuǎn)換為十進制為 28,這就是字符串表中的下標(biāo)委可。

此時符號表中的數(shù)據(jù)如下所示:

image

最后一步,來到字符串表中着倾⊙嗌伲看看下標(biāo)為 28 的內(nèi)容是什么?一行是 16 字節(jié)客们,第二行倒數(shù)第四個數(shù)就是符號開始處(不放心的可以自己數(shù)一數(shù)??)材诽。

image

其中恒傻,5F_ascii 碼,67gascii 碼碌冶,...,一直到 . 號為止譬重。正好對應(yīng)的是 _global,也就證明了查找過程的正確性臀规。

此時字符串表數(shù)據(jù)如下:

image

那對于第二個符號 dyld_stub_binder 栅隐,你是否可以自行實踐出來呢?

其實租悄,以上查找不僅限于 __got 中的符號,對于延遲加載符號一樣適用泣棋。下圖中 __la_symbol 同樣也有 Indirect Sym Index。動態(tài)庫中的符號都是這種查找方式潭辈。

image

總結(jié)

這篇文章中,我們介紹了什么是 got寄摆、got 在 mach-o 中的位置、函數(shù)符號如何與 dyld_stub_binder 進行關(guān)聯(lián)婶恼,以及如何一步步查找動態(tài)庫符號的值柏副。希望對你有用處~

參考資料:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市搓扯,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌锨推,老刑警劉巖公壤,帶你破解...
    沈念sama閱讀 207,248評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件厦幅,死亡現(xiàn)場離奇詭異,居然都是意外死亡确憨,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評論 2 381
  • 文/潘曉璐 我一進店門休弃,熙熙樓的掌柜王于貴愁眉苦臉地迎上來圈膏,“玉大人,你說我怎么就攤上這事稽坤。” “怎么了尿褪?”我有些...
    開封第一講書人閱讀 153,443評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長祈匙。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么跪帝? 我笑而不...
    開封第一講書人閱讀 55,475評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮伞剑,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘黎泣。我一直安慰自己,他們只是感情好抒倚,可當(dāng)我...
    茶點故事閱讀 64,458評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著托呕,像睡著了一般频敛。 火紅的嫁衣襯著肌膚如雪馅扣。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,185評論 1 284
  • 那天差油,我揣著相機與錄音,去河邊找鬼蓄喇。 笑死,一個胖子當(dāng)著我的面吹牛公罕,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播楼眷,決...
    沈念sama閱讀 38,451評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼铲汪,長吁一口氣:“原來是場噩夢啊……” “哼掌腰!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起齿梁,我...
    開封第一講書人閱讀 37,112評論 0 261
  • 序言:老撾萬榮一對情侶失蹤肮蛹,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后伦忠,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,609評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡昆码,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,083評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了赋咽。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,163評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡淘钟,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出日月,到底是詐尸還是另有隱情袱瓮,我是刑警寧澤尺借,帶...
    沈念sama閱讀 33,803評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站燎斩,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏栅表。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,357評論 3 307
  • 文/蒙蒙 一怪瓶、第九天 我趴在偏房一處隱蔽的房頂上張望践美。 院中可真熱鬧,春花似錦陨倡、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽杂曲。三九已至,卻和暖如春擎勘,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背货抄。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評論 1 261
  • 我被黑心中介騙來泰國打工朱转, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人藤为。 一個月前我還...
    沈念sama閱讀 45,636評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像缅疟,于是被迫代替她去往敵國和親遍愿。 傳聞我的和親對象是個殘疾皇子耘斩,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,925評論 2 344

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

  • 最近學(xué)習(xí)了一下 Mach-O ,這里做個筆記記錄,整理思路括授,加深理解。原文出處 Valar Morghulis 的...
    whlpkk閱讀 1,879評論 3 7
  • 熟悉Linux和windows開發(fā)的同學(xué)都知道薛夜,ELF是Linux下可執(zhí)行文件的格式,PE32/PE32+是win...
    Klaus_J閱讀 3,903評論 1 10
  • 上一篇說到源碼經(jīng)過預(yù)處理梯澜、編譯、匯編之后生成目標(biāo)文件晚伙,這一章介紹一下iOS、Mac OS中目標(biāo)文件的格式Mach-...
    Tenloy閱讀 2,008評論 2 9
  • 推薦指數(shù): 6.0 書籍主旨關(guān)鍵詞:特權(quán)撬腾、焦點、注意力民傻、語言聯(lián)想、情景聯(lián)想 觀點: 1.統(tǒng)計學(xué)現(xiàn)在叫數(shù)據(jù)分析漓踢,社會...
    Jenaral閱讀 5,701評論 0 5
  • 昨天,在回家的路上喧半,坐在車?yán)镉圃沼圃盏乜粗摹度龉衬墓适隆罚冶焕锩娴膬?nèi)容深深吸引住了挺据,盡管上學(xué)時...
    夜闌曉語閱讀 3,778評論 2 9