解析Mach-o檔案

酷酷的哀殿發表於2016-10-11

現在做iOS開發的挺多,瞭解一下在蘋果平臺上程式執行的原理

解析 MACH_O 檔案

這篇文章描述瞭如何解析 Mach-O 檔案並稍微解釋了一下它的格式。這不是一份權威指南,不過當你不知從何開始時,它可能有些幫助。想了解更多資訊,請考慮閱讀官方文件和作業系統提供的標頭檔案。

Macho-O 是什麼

維基百科 的簡單描述:

Mach-O 是 Mach object 檔案格式的縮寫,它是一種用於記錄可執行檔案、物件程式碼、共享庫、動態載入程式碼和記憶體轉儲的檔案格式。作為 a.out 格式的替代品,Mach-O 提供了更好的擴充套件性,並提升了符號表中資訊的訪問速度。

大多數基於 Mach 核心的作業系統都使用 Mach-O。NeXTSTEP、OS X 和 iOS 是使用這種格式作為本地可執行檔案、庫和物件程式碼的例子。

Mach-O 格式

Mach-O 沒有類似於 XML、YAML、JSON 等諸如此類的特殊格式,它只是一個二進位制位元組流,被劃分為了有意義的資料塊。這些塊包含元資訊,比如,位元組順序、cpu 型別、塊的大小,等等。

典型的 Mach-O 檔案(對應的 官方文件 )包含三個區域:

  1. 頭-包含該二進位制檔案的一般資訊:位元組順序、(魔數)、cpu 型別、載入指令的數量等等。
  2. 載入指令-它是一張包含很多內容的表,內容包括區域的位置、符號表、動態符號表等。每個載入指令都包含一個元資訊,比如指令型別、名稱、在二進位制檔案中的位置等等。
  3. 資料-通常是物件檔案中最大的部分。主要包含程式碼、資料,例如符號表,動態符號表等等。

這裡是一個簡化的圖形表示︰

解析Mach-o檔案

OS X 有兩種型別的目標檔案:Mach-O 檔案和通用二進位制檔案,也叫作胖檔案。它們之間的區別是:Mach-O 檔案包含一種架構(i386、x86_64、arm64 等等)的物件程式碼,而胖檔案可能包含若干包含不同架構(i386、x86_64、arm、arm64 等等)物件程式碼的物件檔案。

胖檔案的結構相當簡單︰ 胖檔案頭以及後面的 Mach-O 檔案:

解析Mach-o檔案

解析 Mach-O 檔案

OS X 沒有提供 libmacho 或任何類似的工具,我們唯一擁有的是一組定義在 /usr/include/mach-o/* 中的 C 結構體,因此我們需要自己實現解析。它可能非常棘手,但也並不是非常困難。

記憶體描述

在我們開始解析前,讓我們看看一個 Mach-O 檔案的詳細描述。簡單起見,下面的物件檔案是單個 i386 Mach-O 檔案(而不是胖檔案),它只包含兩個段型別的資料條目。

解析Mach-o檔案

僅需下面的結構體我們就可以描述該檔案:

下面是記憶體對映的情況:

解析Mach-o檔案

如果你想要從檔案中讀取特定的資訊,你只需要一個正確的資料結構和偏移量。

解析

讓我們來編寫一個程式,它能讀取 Mach-O 或 胖檔案 並列印每個段的名稱以及它編譯的目標架構。

結束時,我們可能會有類似這樣的東西︰

驅動

讓我們從一個簡單的“驅動”開始。

至少有兩種可用的方式來解析此類檔案︰ 載入檔案內容到記憶體中並直接處理緩衝區 或開啟一個檔案在其中來回跳轉。兩種方法都有自己的優點和缺點,但這裡我會選用第二種。此外,我假定沒有人會用錯誤的方式使用該程式,因此我沒有新增錯誤處理。

魔數、CPU、位元組序

為了至少閱讀物件檔案的頭,我們需要得到我們需要的所有資訊︰ CPU 架構(32 位或 64 位) 和位元組順序。但首先我們需要取出一個魔數︰

函式 read_magic 是非常直截了當的,但有一件事可能看起來很怪︰ fseek。問題是,每當有人讀取檔案,檔案內部的偏移量都會發生改變。最好顯式指定偏移量,以確保我們閱讀到我們實際上想要讀取的內容。此外,這個小技巧稍後也會有用。

描述 32 位和 64 位物件檔案的結構體是不同的(例如︰ mach_header 和 mach_header_64),我們需要檢查檔案的體系結構來選擇需要的結構體:

MH_MAGIC_64 和 MH_CIGAM_64 是系統提供的魔數。第二個看起來比第一個更多 magicly(譯者注:原文如此,magic 對應的形容詞是 magical,作者使用了magicly。)。解釋如下。

由於歷史的原因,不同的計算機可能使用不同的 位元組順序︰ 大端位元組序 (從左到右) 和小端位元組序 (從右至左)。魔數同樣儲存了這一資訊︰ MH_CIGAMMH_CIGAM_64 表示位元組順序不同於主機作業系統,因此所有位元組都應顛倒︰

Mach-O 頭

終於我們能夠讀取 mach_header 了。我們先來介紹幾個用於從檔案中讀取資料的常用函式。

注意︰ 資料應當在使用後釋放 !

為了不搞砸驅動函式,我們在這裡引入另一個函式 dump_mach_header。下一步是讀取所有段指令並列印它們的名字。問題是,mach-o 檔案通常也包含其他指令。如果你還記得 segment_command 結構的第一個欄位的是 uint32_t cmd;,此欄位表示指令的型別。下面是我們將使用的由系統提供的另一種結構體︰

除了以上的所有資訊 mach_header 還有許許多多載入命令,所以我們可以只是遍歷並跳過我們不感興趣的指令。此外,我們需要計算標頭結束位置的偏移量。這裡是 dump_mach_header 的最終版本︰

段指令

是時候去列印所有的段名了:

這個函式不需要 is_64 引數,因為我們可以從 cmd 型別本身 (LC_SEGMENT/LC_SEGMENT_64)推斷它。如果它不是段,我們只需跳過該命令並向前移動到下一個。

CPU 名

我想要展示的最後一件事是如何基於 mach_headercputype 獲得處理器的名稱。我相信這不是最好的選擇,但對於本文的示例來說,它是可以接受的︰

OS X 為大量的 CPU 提供了 CPU_TYPE_ *,所以我們可以 “容易”地為特定的魔數關聯一個字串。為了列印 CPU 的名稱,我們需要稍微修改 dump_mach_header

胖物件

這篇文章已經包含大量的內容,所以我不會描述如何處理胖物件,但你可以在這裡找到如何實現它︰ segment_dumper

接下來是什麼

大概就是以上這些。

這裡是一組可能有用的連結,如果你想要更深入地挖掘和了解更多關於 mach-o 的內容:

打賞支援我翻譯更多好文章,謝謝!

打賞譯者

打賞支援我翻譯更多好文章,謝謝!

任選一種支付方式

解析Mach-o檔案 解析Mach-o檔案

相關文章