Mac-O文件加載的全過程(一)
在Mac的開發中, 有沒有想過當我們點擊可執行文件之後,Mac究竟做了什麽事情才讓我們的程序運行起來? 對於應用層開發人員或者普通的用戶而言, 其實無需知道的這麽詳細;但是對於內核開發人員而言, 如果能了解這一系列的過程, 那麽將增強我們的內核的開發功底。
那麽下面我們開始分析我們的鼠標點擊之後, Mac都做了什麽事情。
1. Mac的歷史
這一部分有更好的文章
2. 準備工作
(1). 你需要下載XNU內核源代碼以及dyld源代碼.
(2). Xcode,vim或者其他什麽瀏覽源代碼的工具。
3. 分析
在分析加載過程的時候, 需要涉及到內核和應用層兩個部分。因此這篇文章也將分為兩部分分別闡述。
3.1 內核部分
Mac的內核經過多次的發展, 目前這部分被稱作XNU。具體為什麽叫這個名字, 大家可以去搜索一下。在最初開始設計XNU的時候, Apple是打算設計一個微內核, 也就是將最重要的事情放在內核裏面並且內核只負責仲裁而非邏輯處理。 這個內核也就是在Mac OS 9上面使用的內核,但是這個產品是一個效率底下系統。因此當喬布斯回歸以後對內核進行了大改, 在內核部分引入了FreeBSD的部分, 這個產品就是我們現在在使用的Mac OSX的內核XNU。 簡單的歷史說完了, 下面我們來點兒幹貨。
3.1.1 Mac-O文件格式的分析
既然是要分析Mac-O文件的加載過程, 那麽必須涉及到Mach-O的格式問題。 對於這個格式, 我們可以參考下面的圖:
從這這張圖片中, 我們可以看到Mach-O文件在結構上可以分成三個部分:
(1) 文件頭
(2) 命令區域
(3) 數據區域(包括數據, 代碼等等)
這三個部分共同組成了Mach-O文件格式。下面我們將以此討論這個三個部分, 在最後我講給出一些需要註意的部分和部分代碼。
3.1.1.1 Mach-O文件頭
Mach-O文件頭的定義如下:
struct mach_header(_64)
代碼來自:${XNU_ROOT}/EXTERNAL_HEADER/mach-o/loader.h:
struct mach_header { uint32_t magic; cpu_type_t cputype; cpu_subtype_t cpusubtype; uint32_t filetype; uint32_t ncmds; uint32_t sizeofcmds; uint32_t flags; }; struct mach_header_64 { uint32_t magic; cpu_type_t cputype; cpu_subtype_t cpusubtype; uint32_t filetype; uint32_t ncmds; uint32_t sizeofcmds; uint32_t flags; uint32_t reserved; };
上面的頭部定義包含了32bit和64bit的頭部。字段的含義如下(下面是以64bit的頭部說明的):
命令 | 含義 | 說明 |
---|---|---|
magic | 魔數字 | 主要用來區分當前Mach-O所支持的CPU架構(當前只有32bit和64bit)。 |
cputype | CPU類型 | 主要的CPU類型(32/64bit), 以及其他的屬性。 |
cpusubtype | CPU子類型 | cpu具體的類型。 |
filetype | 文件類型 | 文件類型比較多,比如MH_EXECUTE代表可執行文件。 |
ncmds | 命令個數 | 也就是下一個segment中得segment的數量。 |
sizeofcmd | 第三個部分的大小 | None。 |
flags | 當前Mach-O的屬性 | 比較常見的屬性包括MH_PIE(當前文件執行ASLR)等。 |
更多的值可以參考這裏.
3.1.1.2 Mach-O命令區域
這一部分是Mach-O文件中最重要的部分, 我們從上面的Mach-O結構圖中可以看到, 所謂的segment其實是數據區域的一個索引。每個segment都在數據區域對應了一段自己的區域。我們需要做的就是找到這些部分並執行他們。首先我們看一下Segment的結構:
Segment的結構定義如下:
struct load_command
代碼來自:${XNU_ROOT}/EXTERNAL_HEADER
struct load_command { uint32_t cmd; /* type of load command */ uint32_t cmdsize; /* total size of command in bytes */ };
這個結構對應的成員比較少, cmd代表當前段的類型。cmdsize當前段的大小。 我們主要看看cmd有哪些的類型, 這個至關重要。在這裏我需要說明的是, 下面的命令並不代表全部,之所以要在這裏列出的它們的原因是這些命令將在內核中被加載。 那麽你可能會問, 那麽其他的段命令呢?這個你和dyld去說好了。列表如下:
命令 | 十六進制 | 作用 |
---|---|---|
LC_SEGMENT LC_SEGMENT_64 |
0x01/0x19 | 將這些段加載到對應的進程空間上去(區分32位和64位) |
LC_LOAD_DYLINKER | 0x0E | 加載dyld, 值得註意的是,每個Mach-O文件只能有一個段 |
LC_UUID | 0x1B | 將UUID這個值保存到執行進程的上下文中,同樣每個Mach-O文件只能有一個段 |
LC_THREAD | 0x04 | 開啟一個Mach線程, 但是不分配棧(這個不常見) |
LC_UNIXTHREAD | 0x05 | 開啟一個UNIX線程,其實最主要的用途是告訴加載器當前主函數的位置. 這條命令在10.8之後被LC_MAIN取代。 |
LC_MAIN | 0x80000028 | 在10.8之後代替LC_UNIXTHREAD, 告訴加載器當前主函數的位置 |
LC_CODE_SIGNATURE | 0x1D | 這個是數字簽名段 |
LC_ENCRYPTION_INFO | 0x21 | 加密二進制文件, 貌似在IOS下使用的比較頻繁。 |
3.1.1.3 Mach-O數據區域
這一部分我們將在dyld的時候著重講。現在先略過。
3.1.1.4 例子
我們查看一下Mac for QQ的二進制文件格式:
首先, 我們查看一下QQ文件頭, 我們發現這個文件是一個32位的Mach-O文件。文件類型是MH_EXEXUTE,也就是可執行文件。 這個執行文件一共有56個segment, 全部的segment的大小有6580.最後一個數據室flag數據, 可以看到這個可執行文件,在加載的時候必須使用ASLR的保護技術,除此之外還有一個標誌位MH_NO_HEAP_EXECUTION,這個標誌位是防止當前的可執行文件的數據部分有執行權限, 一旦有執行權限, 黑客可以進行所謂的“堆噴射攻擊”。
在看第二張圖片, 我們發現當前segment中LC_SEGMENTM可以存在多個;還有就像我們上面說的,對於LC_UUID, LC_MAIN, LC_LOAD_DYLINKER等段來說,有且僅有一個。
值得註意的是, 當前這個可執行文件中存在段LC_MAIN, 因此說明當前QQ的編譯環境是Mac OSX 10.8+, 因為我們在表格中說過LC_MAIN是在10.8的時候引入的, 因此只有10.8或者更高版本的系統才能編譯出這個二進制文件。
http://blog.csdn.net/dongaxis/article/details/41114071
Mac-O文件加載的全過程(一)