iOS中Mach-O概覽

希望通過本文來記錄對于iOS開發對Mach-O需要有的基本了解。

蘋果推出Mach-O的背景:

  1. 過渡至基于 Mach 內核的操作系統:蘋果于 2001 年推出了 macOS(當時稱為 Mac OS X)操作系統,該操作系統采用了基于 Mach 內核的架構。為了適應新的操作系統架構,蘋果需要引入一種新的文件格式來支持該架構,用于可執行文件、靜態庫和動態庫的存儲和交互。
  2. 提高性能和可擴展性:Mach-O 文件格式相對于舊的目標文件格式(如 a.out 格式)具有更好的性能和可擴展性。它采用了更加緊湊和高效的數據結構,使得應用程序的加載、鏈接和執行更高效。這在日益復雜的應用程序和需求下變得尤為重要。
  3. 支持 Objective-C 和 Cocoa 框架:蘋果廣泛采用 Objective-C 編程語言和 Cocoa 框架來開發 macOS 和 iOS 上的應用程序。Mach-O 文件格式與 Objective-C 運行時和 Cocoa 框架的集成緊密相關,使得開發者可以更好地利用這些技術進行應用程序開發。
  4. 跨平臺支持和移植性:Mach-O 文件格式不僅用于 macOS 和 iOS,還可以支持其他基于 Darwin 內核的操作系統,如 tvOS 和 watchOS。這種一致的文件格式使得開發者可以更方便地在不同的蘋果平臺上共享和移植代碼,提高開發效率和代碼復用性。
  5. 操作系統集成:Mach-O 文件格式與蘋果操作系統的內核(XNU)緊密集成。蘋果控制了 Mach-O 格式的規范和解析器,從而使得操作系統和應用程序可以更緊密地進行交互和整合。

一、認識Mach-O

Xcode工程中,我們可以看到編譯設置里面有一個Mach-O type, 可以看到主工程的格式是Executable(可執行文件)。

可執行文件.png

而在組件化工程里,有一些本地或私有庫我們可能會在podspec中聲明s.static_framework = true,這樣就會是靜態庫;三方庫沒有這個聲明默認是動態庫。

靜態庫 動態庫
假設本地庫Home模塊.png
AFNetworking.png

Mach-OMach Object的縮寫,它是Mac/iOS 中用于存儲程序、庫的標準格式。作為 a.out 格式的替代,Mach-O提供了更強的擴展性,并提升了符號表中信息的訪問速度。

類型 代表文件
Executable(可執行文件) xxx.app/xxx、推送擴展
Dynamic Library(動態庫文件) .dylib(一般是系統動態庫)和 xxx.framework/xxx (三方動態庫)
Bundle 一種特定結構的文件夾,可以包含可執行文件、動態庫、靜態庫和各種資源文件,以及配置文件等,通常作為插件或擴展。需通過dlopen加載。
Static Library 靜態庫文件(.a文件,是多個.o文件的集合),如pod庫聲明s.static_framework = true,產物是靜態框架
Relocatable Object File 目標文件(.o文件,編譯源代碼得到的中間文件)

二、Mach-O的類型

1. 有哪些類型

我們可以在Xcode.app查看到Mach-O的類型定義如下,路徑為:
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/mach-o/loader.h

#define MH_OBJECT   0x1     /* relocatable object file */
#define MH_EXECUTE  0x2     /* demand paged executable file */
#define MH_FVMLIB   0x3     /* fixed VM shared library file */
#define MH_CORE     0x4     /* core file */
#define MH_PRELOAD  0x5     /* preloaded executable file */
#define MH_DYLIB    0x6     /* dynamically bound shared library */
#define MH_DYLINKER 0x7     /* dynamic link editor */
#define MH_BUNDLE   0x8     /* dynamically bound bundle file */
#define MH_DYLIB_STUB   0x9     /* shared library stub for static linking only, no section contents */
#define MH_DSYM     0xa     /* companion file with only debug  sections */
#define MH_KEXT_BUNDLE  0xb     /* x86_64 kexts */
#define MH_FILESET  0xc     /* a file composed of other Mach-Os to be run in the same userspace sharing a single linkedit. */
#define MH_GPU_EXECUTE  0xd     /* gpu program */
#define MH_GPU_DYLIB    0xe     /* gpu support functions */

2. 常見的Mach-O類型

MH_OBJECT:目標文件即 .o 文件 以及靜態庫文件即 .a 文件(多個.o文件合并在一起);
MH_EXECUTE:可執行文件,即App編譯運行后生成的可執行文件,在/Products路徑下;
MH_DYLIB:動態庫文件,即.dylib文件 或者 .framework文件;
MH_DYLINKER:/usr/lib/dyld路徑下的dyld文件;
MH_DSYM:Xcode打包后生成的符號表文件,即.dSYM文件;

3. 如何查看mach-o文件類型

找到我們的APP后可通過命令行查看類型。

  • 通過file命令
// 1.查看APP的可執行文件
file xxx.app/xxx
輸出: Mach-O 64-bit executable arm64
// 2.查看pod三方庫的machO
file xxx.app/Frameworks/AFNetworking.framework/AFNetworking
輸出:Mach-O 64-bit dynamically linked shared library arm64

三、Mach-O文件結構

項目 內容
結構圖
Mach-O文件結構.png
Header 包含Mach-O文件的基本信息,例如文件類型,支持的CPU架構類型,加載指令的數量,所占內存大小等
Load Command 不同數據段segment的加載命令,指導加載器加載數據
Data 在Load Command中定義的Segment的原始數據。

四、查看Mach-O的方式

otool -l <file>:顯示 Mach-O 文件的加載命令信息。
otool -t <file>:顯示 Mach-O 文件的文本節信息。
otool -L <file>:顯示 Mach-O 文件的依賴庫信息。
使用 man otool 命令查看 otool 的幫助文檔
  • lipo命令
lipo -info 文件 // 查看架構信息
lipo <file> -thin 目標架構 -output 輸出文件 // 導出某種架構
lipo <file1> <file2> -output 輸出文件 // 合并多個架構
  • objdump命令
objdump --macho --private-headers <file>

五、文件結構中各部分內容細節

源碼頭文件地址:
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/mach-o/loader.h

1. Header

源碼中結構

struct mach_header_64 {
    uint32_t    magic;      /* mach magic number identifier 確定是64位還是32位 */
    int32_t     cputype;    /* cpu specifier */
    int32_t     cpusubtype; /* machine specifier */
    uint32_t    filetype;   /* type of file */
    uint32_t    ncmds;      /* number of load commands */
    uint32_t    sizeofcmds; /* the size of all the load commands */
    uint32_t    flags;      /* flags 標識二進制文件所支持的功能,主要與系統的加載、鏈接有關*/
    uint32_t    reserved;   /* reserved */
};
// 通過otool命令查看一個Mach-O的頭部信息
otool -hv xxx
Mach header
      magic  cputype cpusubtype  caps    filetype ncmds sizeofcmds      flags
MH_MAGIC_64    ARM64        ALL  0x00     EXECUTE   138      13384   NOUNDEFS DYLDLINK TWOLEVEL WEAK_DEFINES BINDS_TO_WEAK PIE

2. Load Commands

這部分的作用是本質就是確定如何加載段segment數據,主結構是:

struct load_command {
    uint32_t cmd;       /* type of load command */
    uint32_t cmdsize;   /* total size of command in bytes */
};

可以通過MachOView來查看有哪些段命令.

項目 內容
加載命令
加載命令段.png
LC_SEGMENT_64 segment段加載指令
LC_DYLD_INF0_0NLY 加載動態鏈接庫信息(重定向地址、弱引用綁定、懶加載綁定、開放函數等的偏移值等信息)
... ...
2.1 其中LC_SEGMENT_64命令的結構
struct segment_command { /* for 32-bit architectures */
    uint32_t    cmd;        /* LC_SEGMENT  加載命令的類型*/
    uint32_t    cmdsize;    /* includes sizeof section structs  加載命令的所占內存大小*/
    char        segname[16];    /* segment name */
    uint32_t    vmaddr;     /* memory address of this segment 段Segment的虛擬內存地址*/
    uint32_t    vmsize;     /* memory size of this segment 段Segment的虛擬內存大小*/
    uint32_t    fileoff;    /* file offset of this segment 段Segment的在文件中的偏移量*/
    uint32_t    filesize;   /* amount to map from the file 段Segment在文件中所占的內存大小*/
    vm_prot_t   maxprot;    /* maximum VM protection 表示頁面所需要的最高內存保護*/
    vm_prot_t   initprot;   /* initial VM protection 表示頁面初始的內存保護*/
    uint32_t    nsects;     /* number of sections in segment 段Segment包含節區sections的數量*/
    uint32_t    flags;      /* flags 表示段的標志信息*/
}
  • 結構體中segname是加載目標段Segment的名稱,常見的段segment有四個(可以從上面圖中看到)
    __PAGEZERO : 在可執行文件有的,動態庫里沒有,這個段開始地址為0(NULL指針指向的位置),是一個不可讀、不可寫、不可執行的空間,能夠在空指針訪問時拋出異常。
    __TEXT:代碼段,里面主要是存放代碼的,該段是可讀可執行,但是不可寫;
    __DATA:數據段,里面主要是存放數據,該段是可讀可寫,但不可執行;
    __LINKEDIT:用于存放簽名信息,該段是只可讀,不可寫不可執行;
2.1 每個命令包含的內容

我們在MachOView展開LC_SEGMENT_64(__TEXT)LC_SEGMENT_64(__DATA),可以看到很多的section header. 這個是數據段和代碼段的各個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 */
};

sectname:section的名稱,常見的section有_text、stubs等等;
segname:當前section所隸屬的Segment,例如__TEXT(代碼段);
addr: section在內存的起始位置;
size: section所占內存大小;
offset: section在文件中的偏移量;
align:字節大小對齊,2的align次方;
reloff:重定位入口的文件偏移;
nreloc: 需要重定位的入口數量;
flags:包含section的type和attributes;

3. Data部分

Data部分主要放的是__Text段和__DATA段的數據,根據不同功能分為不同的節(section)。
段數據的頭部信息是存放在Load Commands中的.

段數據部分

代碼段和數據段的各個節分別代表什么可以通過這篇linkmap文章來了解
通過LinkMap來了解Mach-O

六、MachO里面各部分占用大小

我們還可以通過size命令行查看一個Mach-O大小信息:

// 假設app工程名xxx,
// -l 參數可以顯示目標文件的完整節(section)和段(segment)信息
// -m 參數用于指定目標文件的格式
size -l -m xxx.app/xxx 
輸出文件信息包含四個部分:
Segment __PAGEZERO
Segment __TEXT
Segment __DATA
Segment __LINKEDIT
具體的信息可以打印你APP的對照看

接下來看看每個段的打印信息具體是什么意思。

  • Segment __PAGEZERO: 4294967296 (zero fill) (vmaddr 0x0 fileoff 0)
    該段信息指的是一個名為 __PAGEZERO 的段(segment),其大小為 4GB,對應的是零填充的內存。__PAGEZERO 段的虛擬內存地址(vmaddr)為 0x0,文件偏移量(fileoff)為 0。
  • Segment __TEXT: 43319296 (vmaddr 0x100000000 fileoff 0)的意思?
    該段信息指的是一個名為 __TEXT 的段(segment),其大小為 約等于 41.31 MB。__TEXT 段的虛擬內存地址(vmaddr)為 0x100000000,文件偏移量(fileoff)為 0。虛擬內存地址指示了段在程序運行時被加載到內存的位置,而文件偏移量指示了段在目標文件中的位置。
  • Segment __DATA: 5849088 (vmaddr 0x102950000 fileoff 43319296)
    該段信息指的是一個名為 __DATA 的段(segment),其大小約等于 5.572 MB。__DATA 段的虛擬內存地址(vmaddr)為 0x102950000,文件偏移量(fileoff)為 43319296。
  • Segment __LINKEDIT: 2736128 (vmaddr 0x102ee4000 fileoff 48840704)
    該段信息指的是一個名為 __LINKEDIT 的段(segment),其大小約等于 2.61 MB。__LINKEDIT 段的虛擬內存地址(vmaddr)為 0x102ee4000,文件偏移量(fileoff)為 48840704。

1. __PAGEZERO

在 Mach-O 文件格式中,__PAGEZERO 段用于標識虛擬內存空間的起始位置,并指示該段之前的內存區域應該被清零填充。__PAGEZERO(又稱為 Page Zero)是一個特殊的節(Section)名稱,它在 Mach-O(Mach Object)文件中定義了虛擬地址空間的第一個頁。它沒有任何實際的可執行代碼或數據,僅作為一種占位符存在,用于確保虛擬內存空間的連續性和保護。

一些主要的特點和作用如下:

  • 安全保護:__PAGEZERO 節的存在是為了提供一種安全機制,用于檢測和防止針對軟件漏洞的攻擊。通過將可執行文件或可加載文件的第一個頁設置為沒有權限的頁,可以防止非法訪問者利用指向第一個頁的指針進行漏洞利用。

  • 空節:__PAGEZERO 節本身不包含實際的代碼或數據。它的大小通常為0字節,所以在執行時不會占用任何實際內存。

  • 地址空間布局:__PAGEZERO 節通常位于可執行文件或可加載文件的開始位置,即位于虛擬地址空間的最低部分。它的存在確保了后續節的虛擬地址是從一個明確定義的位置開始的。

2.__TEXT

在 Mach-O 文件格式中,__TEXT 段包含了可執行程序的實際代碼和只讀數據。它是二進制文件中的一個重要段,存儲了程序的代碼段和只讀數據段。

3.__DATA

在 Mach-O 文件格式中,__DATA 段存儲了可執行程序的靜態變量和全局變量等數據。它包含了程序在運行時的可寫數據段,即存儲程序在運行過程中產生的數據的空間。

4. __LINKEDIT

在 Mach-O 文件格式中,__LINKEDIT 段存儲與鏈接器相關的信息,比如符號表、重定位信息等。鏈接器主要負責將不同的目標文件合并成可執行文件,__LINKEDIT 段存儲與該過程相關的一些信息,因此該段也被稱為鏈接器的信息段。

具體來說,__LINKEDIT 段包含以下內容:

  • 符號表(Symbol Table):用于存儲程序中定義和引用的各種符號(變量、函數、類等)的信息,鏈接器通過符號表進行符號解析和重定位等操作。
  • 字符串表(String Table):存儲符號表中的字符串,用于標識符號的名稱。
  • 動態符號表(Dynamic Symbol Table):包含一些在運行時動態加載的符號信息。
  • 重定位表(Relocation Table):存儲在鏈接過程中需要進行地址重定位的部分,包括指令中需要修改的地址和重定位類型等信息。
    __LINKEDIT 段的存在使得鏈接器能夠在程序運行時解析和重定位符號,從而正確地連接和加載各種模塊。

相關資料:
Mach-O入門理解
Mach-O
iOS逆向06 -- Mach-O

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

推薦閱讀更多精彩內容