了解iOS上的可執行文件和Mach-O格式

>>>  創業先鋒 眾人拾柴火焰高  >>> 簡體     傳統


很多朋友都知道,在Windows上exe是可直接執行的文件擴展名,而在Linux(以及很多版本的Unix)系統上ELF是可直接執行的文件格式,那么在蘋果的操作系統上又是怎樣的呢?在iOS(和Mac OS X)上,主要的可執行文件格式是Mach-O格式。本文就關于iOS上的可執行文件和Mach-O格式做一個簡要整理。


Mach-O格式是iOS系統上應用程序運行的基礎,了解Mach-O的格式,對于調試、自動化測試、安全都有意義。在了解二進制文件的數據結構以后,一切就都顯得沒有秘密。


0Mach與Mach-O



這里先提醒大家一下,Mach不是Mac,Mac是蘋果電腦Macintosh的簡稱,而Mach則是一種操作系統內核。Mach內核被NeXT公司的NeXTSTEP操作系統使用。在Mach上,一種可執行的文件格是就是Mach-O(Mach Object file format)。1996年,喬布斯將NeXTSTEP帶回蘋果,成為了OS X的內核基礎。所以雖然Mac OS X是Unix的“后代”,但所主要支持的可執行文件格式是Mach-O。


iOS是從OS X演變而來,所以同樣是支持Mach-O格式的可執行文件。


1iOS可執行文件初探



作為iOS客戶端開發者,我們比較熟悉的一種文件是ipa包(iPhone Application)。但實際上這只是一個變相的zip壓縮包,我們可以把一個ipa文件直接通過unzip命令解壓。


解壓之后,會有一個Payload目錄,而Payload里則是一個.app文件,而這個實際上又是一個目錄,或者說是一個完整的App Bundle。


在這個目錄中,里面體積最大的文件通常就是和ipa包同名的一個二進制文件。找到它,我們用file命令來看一下這個文件的類型:


XXX: Mach-O universal binary with 2 architectures

XXX (for architecture armv7): Mach-O executable arm

XXX (for architecture armv7s): Mach-O executable arm


由此看來,這是一個支持armv7和armv7s兩種處理器架構的通用程序包,里面包含的兩部分都是Mach-O格式。


對于一個二進制文件來講,每個類型都可以在文件最初幾個字節來標識出來,即“魔數”。比如PNG圖片的最初幾個字節是\211 P N G \r \n \032 \n (89 50 4E 47 0D 0A 1A 0A)。我們再來看下這個Mach-O universal binary的:


0000000 ca fe ba be 00 00 00 02 00 00 00 0c 00 00 00 09


沒錯,開始的4個字節是cafe babe,即“Cafe baby”。了解Java或者說class文件格式的同學可能會很熟悉,這也是.class文件開頭的“魔數”,但貌似是Mach-O在更早的時候就是用了它。在OS X上,可執行文件的標識有這樣幾個魔數(也就是文件格式):


cafebabe


feedface


feadfacf


還有一個格式,就是以#!開頭的腳本


cafebabe就是跨處理器架構的通用格式,feedface和feedfacf則分別是某一處理器架構下的Mach-O格式,腳本的就很常見了,比如#!/bin/bash開頭的shell腳本。


這里注意一點是,feedface和cafebabe的字節順序不同,我們可以用lipo把上面cafebabe的文件拆出armv7架構的,看一下開頭的幾個字節:


0000000 ce fa ed fe 0c 00 00 00 09 00 00 00 02 00 00 00


2Mach-O格式



接下來我們再來看看這個Mach-O格式到底是什么樣的格式。我們可以通過二進制查看工具查看這個文件的數據,結果發現,不是所有數據都是相連的,而是被分成了幾個段落。


在一位叫做JOE SAVAGE的老兄發布的圖片上來看,Mach-O的文件數據顯現出來是這個樣子的:



大家可以對數據的分布感受下。


雖然被五顏六色的標記出來,可能這還不是特別直接。再來引用蘋果官方文檔的示意圖:



從這張圖上來看,Mach-O文件的數據主體可分為三大部分,分別是頭部(Header)、加載命令(Load commands)、和最終的數據(Data)。


回過頭來,我們再看上面那張圖,也許就都明白了。黃色部分是頭部、紅色是加載命令、而其它部分則是被分割成Segments的數據。


3Mach-O頭部



這里,我們用otool來看下Mach-O的頭部信息,得到:


magic cputype cpusubtype  caps    filetype ncmds sizeofcmds    flags

0xfeedface      12          9  0x00          2    45       4788 0x00218085


更詳細的,我們可以通過otool的V參數得到翻譯版:


Mach header

magic cputype cpusubtype  caps    filetype ncmds sizeofcmds   flags

MH_MAGIC ARM   V7  0x00     EXECUTE    45       4788   NOUNDEFS DYLDLINK TWOLEVEL WEAK_DEFINES BINDS_TO_WEAK PIE


前面幾個字段的意義,上下對比就能看懂,我這里主要說下這樣幾個字段:


filetype,這個可以有很多類型,靜態庫(.a)、單個目標文件(.o)都可以通過這個類型標識來區分。


ncmdssizeofcmds,這個cmd就是加載命令,ncmds就是加載命令的個數,而sizeofcmds就是所占的大小。


flags里包含的標記很多,比如TWOLEVEL是指符號都是兩級格式的,符號自身+加上自己所在的單元,PIE標識是位置無關的。


4加載命令



上面頭部中的數據已經說明了整個Mach-O文件的基本信息,但整個Mach-O中最重要的還要數加載命令。它說明了操作系統應當如何加載文件中的數據,對系統內核加載器和動態鏈接器起指導作用。一來它描述了文件中數據的具體組織結構,二來它也說明了進程啟動后,對應的內存空間結構是如何組織的。


我們可以用otool -l xxx來看一個Mach-O文件的加載命令:


Load command 0

     cmd LC_SEGMENT

 cmdsize 56

 segname __PAGEZERO

  vmaddr 0x00000000

  vmsize 0x00004000

 fileoff 0

filesize 0

 maxprot ---

initprot ---

  nsects 0

   flags (none)

Load command 1

     cmd LC_SEGMENT

 cmdsize 736

 segname __TEXT

  vmaddr 0x00004000

  vmsize 0x00390000

 fileoff 0

filesize 3735552

 maxprot r-x

initprot r-x

  nsects 10

   flags (none)

Section

 sectname __text

  segname __TEXT

     addr 0x0000b0d0

     size 0x0030e7f4


上面這段是執行結果的一部分,是加載PAGE_ZERO和TEXT兩個segment的load command。PAGE_ZERO是一段“空白”數據區,這段數據沒有任何讀寫運行權限,方便捕捉總線錯誤(SIGBUS)。TEXT則是主體代碼段,我們注意到其中的r-x,不包含w寫權限,這是為了避免代碼邏輯被肆意篡改。


我再提一個加載命令,LC_MAIN。這個加載指令,會聲明整個程序的入口地址,保證進程啟動后能夠正常的開始整個應用程序的運行。


除此之外,Mach-O里還有LC_SYMTAB、LC_LOAD_DYLIB、LC_CODE_SIGNATURE等加載命令,大家可以去官方文檔查找其含義。


至于Data部分,在了解了頭部和加載命令后,就沒什么特別可說的了。Data是最原始的編譯數據,里面包含了Objective-C的類信息、常量等。


本文是對Mach-O文件格式的一個理解小結,希望能夠拋磚引玉,幫助各位朋友把握可執行文件的主題脈絡,進而解決各類問題。


(來源:三·石道)



CocoaChina 2015-08-23 08:44:21

[新一篇] 現實版吃豆人游戲 拿著手機奔跑吧!否則就被……

[舊一篇] 《暗黑黎明》新手成長分析:新手流程和玩法元素
回頂部
寫評論


評論集


暫無評論。

稱謂:

内容:

驗證:


返回列表