理解Mach-O文件格式(1)

原文地址

寫在之前

之前工作中對Mach-O文件有一定的接觸, 原本早就想寫一篇文章分享一下,但是奈何只是不夠深入, 總怕分析的有問題誤導讀者。

最近又在閱讀深入解析Mac OS X 與iOS 操作系統,借著這個機會記錄下自己的學習成果, 并結合之前的經驗, 加上一些實例讓讀者更好的理解。
畢竟對于程序員來說 大部分人對抽象的概念的感覺就是 聽說過很多原理, 依然不知道大佬說的是什么

Mac OS 與 iOS 支持的文件類型

Unix-Like系列的操作系統, 可以通過命令 chmod +x 給予文件可執行權限, 但是這不代表這個文件具有可執行權限, 實際上 Apple家的操作系統只支持三種文件格式。

  1. #!開頭的腳本文件
  2. 通用二進制文件
  3. Mach-O格式文件

但是實際上 以#!開頭的腳本文件其實是shell解釋器找到后面指定的腳本解釋器來執行的, 而通用二進制文件其實是多個架構的Mach-O文件的打包體。
通用二進制文件其實有個更加形象化的名字fat binary
那么操作系統如何知道你打開的文件是何種類型的?
其實是通過這些文件頭的固定數字來區分的, 對于這些固定數字通常叫做 Magic Number(魔數).

對于fat binary的魔數是 0xcafebabe(小端)0xbebafeca大端
對于Mach-O的魔數是 0xfeedface(32位) 0xfeedfacf(64位)

多說無益~~上代碼

我們以/usr/bin/perl為例 (這是一個fat binary)
$ file /usr/bin/perl
/usr/bin/perl: Mach-O universal binary with 2 architectures: [x86_64:Mach-O 64-bit executable x86_64] [i386:Mach-O executable i386]
/usr/bin/perl (for architecture x86_64):    Mach-O 64-bit executable x86_64
/usr/bin/perl (for architecture i386):  Mach-O executable i386
$ otool -vh /usr/bin/perl
Mach header
      magic cputype cpusubtype  caps    filetype ncmds sizeofcmds      flags
MH_MAGIC_64  X86_64        ALL LIB64     EXECUTE    17       1800   NOUNDEFS DYLDLINK TWOLEVEL PIE

不過可能你覺得拿著系統的命令來看感覺不那么真實, 那么cat命令我們都用過吧,來看下


/usr/include/mach-o/fat.h路徑下有關于fat binary文件的頭文件定義

struct fat_header {
    uint32_t    magic;      /* FAT_MAGIC or FAT_MAGIC_64 */
    uint32_t    nfat_arch;  /* 包含的架構數 */
};

struct fat_arch {
    cpu_type_t  cputype;    /* cpu類型 */
    cpu_subtype_t   cpusubtype; /* 機器標示符  */
    uint32_t    offset;     /* 當前架構在這個文件中的便宜量 */
    uint32_t    size;       /* 當前架構在文件中的長度*/
    uint32_t    align;      /* 對齊方式 */
};

不知道大家還記得不記得之前使用windows的時候有System32和64之分, 那是因為在windows操作系統中不同架構的可執行文件是分開存放的。

蘋果在某次WWDC大會聲稱自己優雅的將多個架構合并在了一個文件中。引來果粉一陣鼓掌
其實fat binary文件的真正布局非常簡單。

以/usr/bin/perl為例



Apple的實現只是將不同架構的文件并排放在一起,然后在文件頭部添加不同架構的描述信息, 然后再加載當前架構的Mach-O文件 丟棄掉其他架構的部分即可。實在是簡單粗暴~~

Mach-O文件結構

Unix標準了一個可移植的二進制格式ELF但是蘋果并沒有實現它而是維護了一套NeXTSTEP的遺物 Mach-Object簡稱Mach-O
但是這并不是說蘋果不遵守POSXI規范,這個規范通常說的是源碼級別的跨平臺性,對于二進制則不強制要求。

下面是一個官方提供的圖片。


Mach-0 Header

先來介紹Mach-O的Header(只介紹64位)信息。
相關頭文件定義在/usr/include/mach-o/loader.h里面。如果需要使用只需要加載<mach-O/loader.h>

struct mach_header_64 {
    uint32_t    magic;      /* mach magic number identifier */
    cpu_type_t  cputype;    /* cpu specifier */
    cpu_subtype_t   cpusubtype; /* machine specifier */
    uint32_t    filetype;   /* 文件類型 */
    uint32_t    ncmds;      /* load commadns的個數 */
    uint32_t    sizeofcmds; /* load commands的總大小 */
    uint32_t    flags;      /* 動態連接器標志*/
    uint32_t    reserved;   /* 保留*/
};

/* Constant for the magic field of the mach_header_64 (64-bit architectures) */
#define MH_MAGIC_64 0xfeedfacf /* 小端 */
#define MH_CIGAM_64 0xcffaedfe /* 大端 */


注: Mach-O文件不僅僅是可執行文件, 也包括目標文件(.o) 動態庫, Bundle插件等。
標志位
flag 標記了一些dyld加載 執行 中可配置的信息。
關于Mach-O文件的魔數信息,有興趣的讀者可以按照之前的方式親自動手嘗試一下

Mach-O Load commands

Mach-O文件中最重要的元信息就是 load Commands,加載命令緊跟在文件頭信息之后。

//   [_mach_header_|___load_commands___||___load_commands___||____other____|]

struct load_command {
    uint32_t cmd;       /*  load command的類型 */
    uint32_t cmdsize;   /*  command 的長度 */
};

LC_SEGMENT

對于加載命令是LC_SEGMENT的命令指定了內核如何設置新運行的進程的內存空間
對應的頭文件也在<mach-o/loader.h>

struct segment_command_64 { /* for 64-bit architectures */
    uint32_t    cmd;        /* LC_SEGMENT_64 */
    uint32_t    cmdsize;    /* includes sizeof section_64 structs */
    char        segname[16];    /* segment name */
    uint64_t    vmaddr;     /* 當前segment加載的虛擬內存起始地址 */
    uint64_t    vmsize;     /* 當前segment加載的虛擬內存地址占用的長度  */
    uint64_t    fileoff;    /* segment在文件中的偏移 */
    uint64_t    filesize;   /* segment在文件中的長度 */
    vm_prot_t   maxprot;    /* 最大的保護級別 */
    vm_prot_t   initprot;   /* 初始化的保護級別 */
    uint32_t    nsects;     /* 包含sections的個數  */
    uint32_t    flags;      /* 標志位 */
};

由于有了LC_SEGMENT命令。對于每一個Segment,將文件中偏移量為fileOff長度為filesize的文件內容加載到虛擬地址為vmaddr的位置,長度為vmsize, 頁面的權限通過initprot來初始化(比如設定讀/寫/執行, 段的保護級別可以動態設置最大不超過maxprot

常見的Segment有以下幾個

  1. __TEXT 代碼段
  2. __PAGEZERO 空指針陷阱
  3. __DATA 數據段
  4. __LINKEDIT 包含需要被動態鏈接器使用的信息,包括符號表、字符串表、重定位項表等。
  5. __OBJC(現已經被合并到__DATA部分)包含會被Objective Runtime使用到的一些數據。

當然讀者如果有興趣查看其他所有的loadcommands可以去loader.h頭文件定義去查看,也可以實際操練一下
如 使用otool 查看某些mach-O文件的所有load_commands

otool -l /bin/ls

section

類型聲明如下
struct section_64 { /* for 64-bit architectures */
    char        sectname[16];   /* name of this section */
    char        segname[16];    /* segment this section goes in */
    uint64_t    addr;       /* memory address of this section */
    uint64_t    size;       /* size in bytes of this section */
    uint32_t    offset;     /* file offset of this section */
    uint32_t    align;      /* section alignment (power of 2) */
    uint32_t    reloff;     /* file offset of relocation entries */
    uint32_t    nreloc;     /* number of relocation entries */
    uint32_t    flags;      /* flags (section type and attributes)*/
    uint32_t    reserved1;  /* reserved (for offset or index) */
    uint32_t    reserved2;  /* reserved (for count or sizeof) */
    uint32_t    reserved3;  /* reserved */
};

對于__TEXT, __DATA下面, 又有細分的各種Section,常見的如

名稱 作用
TEXT.text 只有可執行的機器碼
TEXT.cstring 硬編碼去重后的C字符串
TEXT.const 初始化過的常量
DATA.data 初始化過的可變的數據
DATA.bss 沒有初始化的靜態變量
DATA.common 沒有初始化過的符號聲明
DATA.objc_clasname oc類名稱
DATA.objc_classlist 類列表
DATA.objc_protocollist 協議列表

···
其他的就不一一列舉,建議讀者親自動手試一試, 會發現很多有價值的東西

了解這些有什么用?

相信看了這些內容, 你已經大致知道Mach-O文件的物理布局, 那么我們知道了這個文件格式能用來做什么呢?
理解了這個可以用來做下面一些東西:

  1. 依賴解耦
  2. 元信息獲取
  3. 調試代碼
  4. CI工具插件檢測
  5. 逆向

相關一些示例放在下篇文章講解。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,967評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,273評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,870評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,742評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,527評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,010評論 1 322
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,108評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,250評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,769評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,656評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,853評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,371評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,103評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,472評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,717評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,487評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,815評論 2 372

推薦閱讀更多精彩內容

  • Mach-O 概述 和 部分命令介紹 我們知道Windows下的文件都是PE文件,同樣在OS X和iOS中可執行文...
    青花瓷的平方閱讀 14,964評論 2 52
  • 熟悉Linux和windows開發的同學都知道,ELF是Linux下可執行文件的格式,PE32/PE32+是win...
    Klaus_J閱讀 3,989評論 1 10
  • 這是Mach-O系列的第二篇,趣探 Mach-O:文件格式分析是本文的一個基礎 我們都知道 Mach-O是 OS ...
    Joy___閱讀 11,511評論 9 47
  • linux資料總章2.1 1.0寫的不好抱歉 但是2.0已經改了很多 但是錯誤還是無法避免 以后資料會慢慢更新 大...
    數據革命閱讀 12,196評論 2 33
  • 本文所讀的源碼,可以從這里找到,這是 Mach-O 系列的第一篇 我們的程序想要跑起來,肯定它的可執行文件格式要被...
    Joy___閱讀 24,217評論 9 97