概述
??本文檔描述了Mach-O文件格式的結構,它被用來存儲程序和庫到硬盤中,作為Mac OS X程序的二進制接口(ABI).想要了解xcode工具和Mach-O文件怎么工作以及底層任務調試的,需要了解這些信息
??Mach-O文件格式提供中間物(構建過程中產生的)和最終存儲有機器碼和數據的文件。它被設計成一個靈活的BSD a.out的替代品。這種文件被編譯器和靜態鏈接器使用,并在運行時包含靜態鏈接的可執行代碼。隨著Mac OS X目標的發展,動態鏈接的特性也被添加進來,從而為靜態鏈接和動態鏈接的代碼形成了單一的文件格式。
基本結構
??一個Mach-O文件包含三個主要區域(如圖1所示)
??在每個Mach-O文件的開始部分都有一個Header結構,這個Header標志這個文件是Mach-O文件。這個頭部也包含了其他基礎文件類型信息,標明目標體系結構,包含指定選項的標志,這些標志會影響對文件剩余部分的解釋。
??在header之后是一系列大小可變的加載命令,它們指定文件的布局和鏈接特征。在其他信息中,load命令可以指定:
- 文件在虛擬內存中的初始化布局
- 符號表的位置(被用來動態鏈接使用)
- 程序主線程的初始執行狀態
- 包含主可執行文件導入符號定義的共享庫的名稱
??在加載指令之后,所有的Mach-O文件都包含一個或多個段的數據。每個段有零個或多個section,段的每個section都包含特定類型的代碼或數據。每個段定義了虛擬內存區域,被連接器用來映射到進程的地址空間中,段和section的確切數量和布局由load命令和文件類型指定。
??在用戶級完全鏈接的Mach-O文件中,最后一個段是鏈接編輯段。此段包含鏈接編輯信息的表,如符號表、字符串表等,動態加載程序使用這些信息將可執行文件或Mach-O包鏈接到其附屬庫。
?? Mach-O文件中的各種表按編號引用section(節)。節編號從1(不是0)開始,并跨越段邊界。因此,在文件中第一個段可能包含1、2節,第二個段可能包含3、4節。
??當使用Stabs調試格式時,符號表還包含調試信息。使用DWARF時,調試信息存儲在圖像對應的dSYM文件中,該文件由uuid_command(第20頁)結構指定。
?? 注釋: stabs取名于symbol table strings,因為開始的時候,調試信息是以字符串的形式存儲在Unix的a.out目標文件的符號表中。 stabs以字符串的形式編碼程序的信息。最開始的時候,stabs很簡單,但是后來變得越來越復雜,難解,而且不一致。此外,stabs沒有形成標準,文檔也不夠詳細。Sun Microsystem基于stabs作了大量擴展;GCC在對SUn的擴展進行反向工程的過程中,作了其它的擴展。stabs仍然被廣泛使用。
??DWARF已經被廣泛使用,包括GCC和LLVM。DWARF也是基于嵌套結構存儲調試信息。
?? DWARF源于Unix System V Release 4中的C編譯器以及sdb調試器。1989年的文檔形成了DWARF 1。1900發布了DWARF 2的一個draft標準。隨后,因為Motorola一個項目的失敗,支持團隊被解散。隨后,DWARF 2的擴展泛濫,就有了各種各樣的實現,沒能形成最終標準。直到2006年發布的最終標準DWARF 3. 2010年發布了DWARF 4.
頭部結構和加載命令
??一個Mach-O文件包含了一個架構的代碼和數據。Mach-O文件的頭結構指定了目標架構,這使得內核能夠確保,例如,為基于powerpc的Macintosh計算機設計的代碼不會在基于intel的Macintosh計算機上執行。
??你可以將多個Mach-O文件組合成一個二進制文件,用 “Universal Binaries and 32-bit/64-bit PowerPC Binaries” 相應格式描述即可。
??包含多個體系結構的目標文件的二進制文件不是Mach-O文件。他們存檔一個或多個Mach-O文件。
??段和節通常按名稱訪問。按照慣例,段的命名是使用所有大寫字母加上兩個下劃線(例如,用字母組合);section的命名應該使用所有小寫字母加上兩個下劃線(例如,用字母組合)。這個命名約定是標準的,盡管對于工具的正確操作不是必需的。
段(segment)
??段定義了Mach-O文件中的一個字節范圍,以及地址和內存保護屬性,當動態鏈接器加載應用程序時,這些字節被映射到虛擬內存中。因此,段總是與虛擬內存頁對齊。一個段包含零個或多個節。
??運行時比構建時需要更多內存的段可以指定比實際磁盤上更大的內存大小。PowerPC可執行文件的鏈接器生成的__PAGEZERO段的虛擬內存大小為一頁,而在磁盤上的大小為0。它不需要占用可執行文件中的任何空間。
note: 段的末尾必須用大小為0的節填充,否則標準工具將沒辦法成功操作Mach-O文件。
??為了緊湊性,中間對象文件只包含一個段。這段沒有名字,在最終的對象文件中,它包含了不同段所定義的sections。定義了一個section(page 23)的數據結構包含了段中節的名稱,靜態鏈接器將每個節放在最終的目標文件中。
??為了更好的性能,段應該和虛擬內存頁的邊界對齊,對于PowerPC和x86處理器每個虛擬內存頁大寫是4096b。累加每個section的大小,然后對結果取整作為下個虛擬內存頁的分界線(4096b或者4kb).用這種算法,一個段最小是4kb,接下來會以4kb作為增量。
??出于分頁的目的,頭部和加載命令會作為第一個段的一部分。在一個可執行文件中,通常這個意思是頭部和加載命令__TEXT開始的位置,因為這是第一個包含數據的段。__PAGEZERO段在硬盤上面沒有數據,所以一般會忽略它。
??這些是標準的Mac OS X開發工具(包含在Xcode工具CD中)可能包含在Mac OS X可執行文件中的片段:
- 靜態連接器會創建一個
__PAGEZERO
段作為可執行文件的第一個段。這個段位于虛擬內存的0位置,而且沒有分配任何保護權限,它們的組合會導致對NULL
的訪問,這是一種常見的C編程錯誤,會立即崩潰。__PAGEZERO
段對于現在的架構就是一頁虛擬內存頁的大小(對于基于Intel和Power-PC內核的Mac計算機,一般是4096字節或者十六進制0x1000).因為__PAGEZERO
段沒有數據,在文件中沒有占用任何空間(段命令中的文件大小是0) -
__TEXT
段包含了可執行代碼和只讀數據。允許內核直接從可執行文件映射到共享內存中,靜態連接器設置這個段虛擬內存的權限為不允許寫入。當段被映射到內存中時,它可以在所有對其內容感興趣的進程之間共享。(這個主要用在frameworks,bundles,和共享庫,但是可以在Mac OS X中運行同一可執行文件的多個副本,這也適用于這種情況。)只讀屬性也意味著這些內存也組成的__TEXT
段是不可以會寫到磁盤中的。當內核需要釋放物理內存的時候,他可以放棄一個或多個__TEXT
,如果下次有需要,就從磁盤重新讀取他們 -
__DATA
段包含的是可寫數據。靜態連接器設置這段虛擬內存的權限為可讀可寫。因為是可寫的,因為它是可寫的,所以框架或其他共享庫的數據段在邏輯上被復制到與該庫鏈接的每個進程。當組成__DATA
段的內存頁可讀可寫時,內核將它們標記為“寫時復制”;因此當一個進程寫入這些頁的其中一頁時,該進程會收到當前進程私有的當前頁備份。 -
__OBJC
段包含了OC語言runtime支持庫所用到的數據。 -
__IMPORT
段包含了符號樁和指向可執行文件中未定義的符號的非懶加載指針。這個段僅在IA-32架構的目標可執行文件中生成。 -
__LINKEDIT
段包含了動態連接器所用到的原始數據,例如符號、字符串、和重定位表記錄
Sections(節)
__TEXT
和__DATA
段包含了許多標準section,列于表1、表2、和表3。__OBJC
段包含了許多OC編譯器私有section。請注意,靜態鏈接器和文件分析工具使用節類型和屬性(而不是節名稱)來確定它們應該如何處理節。section名稱、類型和屬性將在section數據類型的描述(第23頁)中進一步說明。
表1??__TEXT
段的節
段?和?節名稱 | 內容 |
---|---|
__TEXT, __text | 可執行機器碼。編譯器通常只放可執行代碼,沒有其他類型的表和數據 |
__TEXT, __cstring | C字符串常量。c字符串就是一個以'\0'結尾的非空的字節序列。靜態連接器在構建最終結果時會合并c常量字符串值,移除重復的 |
__TEXT,__picsymbol_stub | 位置無關的間接符號樁。有關更多信息,請參閱Mach-O編程主題中的“動態代碼生成”。 |
__TEXT,__symbol_stub | 簡介符號樁,有關更多信息,請參閱Mach-O編程主題中的“動態代碼生成” |
__TEXT, __const | 初始化的常量變量。編譯器會在這個section中放置用const 修飾的不可重定位數據 |
__TEXT, __litera14 | 4字節的字面量。編譯器在本節中放置單精度浮點常量。在構建最終文件時,靜態鏈接器合并這些值,刪除重復項。對于某些架構,編譯器使用立即加載指令比添加到本節更有效。 |
__TEXT, __litera18 | 8字節的字面量。編譯器在本節中放置雙精度浮點常量。在構建最終文件時,靜態鏈接器合并這些值,刪除重復項。對于某些架構,編譯器使用立即加載指令比添加到本節更有效。 |
表2 ?? __DATA
段中的節
段?和?節名稱 | 內容 |
---|---|
__DATA, __data | 初始化的可變變量,例如可寫的c字符串和數據數組 |
__DATA, __la_symbol_ptr | 懶加載符號指針,它間接引用了來自不同文件的重要函數。了解更多信息,參閱Mach-O編程主題中的“生成動態代碼” |
__DATA, __nl_symbol_ptr | 非懶加載符號指針,它間接引用了來自不同文件的數據項 。了解更多信息,參閱Mach-O編程主題中的“生成動態代碼”。 |
__DATA, __dyld | 動態連接器使用的占位符部分 |
__DATA, __const | 初始化的可重定位的常量變量 |
__DATA, __mod_init_func | 模塊初始化函數。C++編譯器放置靜態構造函數的地方 |
__DATA, __mod_term_func | 模塊終止函數 |
__DATA, __bss | 未初始化的靜態變量的數據 (比如,static int i; ) |
__DATA, __common | 導入的未初始化符號定義(比如:int i; )位于全局范圍內的(聲明在函數之外的) |
表3 ?? __IMPORT
段的節
段?和?節名稱 | 內容 |
---|---|
__IMPORT, __jump_table | 動態庫中函數調用的存根。 |
__IMPORT,__pointers | 非懶加載符號指針,它直接引用從不同文件導入的函數。 |
注意:編譯器或任何創建Mach-O文件的工具都可以定義額外的節名。這些額外的名稱沒有出現在表1中。
數據類型
這里介紹的是構成Mach-O文件的數據類型。除了fat_header(第56頁)和fat_arch(第56頁)以外,所有Mach-O數據結構中的整數類型的值都是使用主機CPU的字節排序方案寫入的,它們都是按大端字節順序寫入的。
頭部數據結構
mach_header
指定文件的一般屬性。出現在以32位體系結構為目標的目標文件的開頭。聲明在/usr/include/mach-o/loader.h
,也可以看mach_header_64
(第14頁)
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;
};
字段
magic
:包含了一個整數值,標志這個文件是32位的Mach-O的文件。如果文件打算在與運行編譯器的計算機的字節序相同的CPU上使用,請使用常量MH_MAGIC。 當目標計算機的字節排序方案與主機CPU相反時,可以使用常量MH_CIGAM。
cputype
:這個整數值指示了你準備在什么架構上運行此文件。適當的值有以下幾種:
-
CPU_TYPE_POWERPC
目標是基于PowerPC的Mac電腦 -
CPU_TYPE_I386
目標是基于Intel的Mac電腦
cpusubtype
指定CPU的精確模型的整數。在Mac OS X內核支持的所有PowerPC或x86處理器上運行,這個應該設置為CPU_SUBTYPE_POWERPC_ALL
或者CPU_SUBTYPE_I386_ALL
filetype
該整數值標明了文件的用途和對齊方式。這個字段有效的值包括:
-
MH_OBJECT
文件類型是用于中間目標文件的格式。它包含了一個段中所有的節格式很緊湊。編譯器和匯編器通常會為每個源碼文件創建一個MH_OBJECT
文件。按照慣例,這個文件名字的后綴格式是.o
。 -
MH_EXECUTE
文件類型是標準可執行程序使用的格式。 -
MH_BUNDLE
文件類型通常由運行時加載的代碼使用(通常稱為bundle或插件)。按照慣例,這種格式的文件擴展名是.bundle
。 -
MH_DYLIB
文件類型用于動態共享庫。它包含一些額外的表來支持多個模塊。按照慣例,這種格式的文件擴展名是dylib
,除了框架的主共享庫外,它通常是不會有文件擴展名的。 -
HM_PRELOAD
文件類型是一種可執行格式,用于Mac OS X內核沒有加載的特殊用途程序,例如刻錄到可編程ROM芯片中的程序。不要將此文件類型與MH_PREBOUND
標志混淆,MH_PREBOUND
標志是靜態鏈接器在頭結構中設置的標志,用于標記預綁定圖像。 -
MH_CORE
文件類型用于儲存核心文件,這通常是在程序崩潰時創建的。核心文件存儲進程崩潰時的整個地址空間。稍后,您可以在核心文件上運行gdb來找出為什么會發生崩潰。 -
MH_DYLINKER
文件類型用于動態連接器共享庫。這是dyld
文件類型。 -
MH_DSYM
文件類型指定存儲對應二進制文件的符號信息的文件。
ncmds
:在頭部結構后面指示了加載命令的數量
sizeofcmds
:在header結構后面指示這加載命令所占用的字節數量
flags
:包含了一組位標志,指示著Mach-O文件格式某些可選特性的狀態。這些是你可以用來操作該字段的掩碼:
-
MH_NOUNDEFS
--該對象文件在構建時不包含未定義的引用。 -
MH_INCRLINK
--該對象文件是相對于基類文件增量連接的輸出而且不能被重復連接。 -
MH_DYLDLINK
--該對象文件是動態連接器的輸入,不能被再次靜態連接。 -
MH_TWOLEVEL
--該鏡像用二級命名空間綁定。 -
MH_BINDATLOAD
--當文件被加載時,動態攔截器應該綁定未定義的引用。 -
MH_PREBOUND
--文件的未定義引用是預綁定的。 -
MH_PREBINDABLE
--文件未定義的引用需要預綁定 -
MH_NOFIXPREBINDING
--動態鏈接器不會將此可執行文件通知預綁定代理。 -
MH_ALLMODSBOUND
--指示此二進制文件綁定到其附屬庫的所有兩級名稱空間模塊。僅當MH_PREBINDABLE和MH_TWOLEVEL被設置時使用。 -
MH_CANONICAL
--通過從文件中取消預綁定-清除預綁定信息,此文件已被規范化。有關詳細信息,請參閱redo_prebinding手冊頁。 -
MH_SPLIT_SEGS
--該文件將只讀段和只寫段分開 -
MH_FORCE_FLAT
--可執行文件強制所有映像使用平面名稱空間綁定。 -
MH_SUBSECTIONS_VIA_SYMBOLS
--對象文件的各個部分可以劃分為單獨的塊。如果其他代碼不使用這些塊,那么它們就是死區。有關詳細信息,請參閱Xcode用戶指南中的“鏈接”。 -
MH_NOMULTIDEFS
--這把傘保證在它的子圖像中沒有符號的多重定義。因此,總是可以使用兩級名稱空間提示。
加載命令數據結構
加載命令位于對象文件中頭部后面,它指定了文件的邏輯結構以及在虛擬內存中的布局。每個加載命令都以一個指定命令類型和命令數據大小的字段開始。
load_command
包含所有加載命令通用的字段。
struct load_command
{
uint32_t cmd;
uint32_t cmdsize;
};
字段
cmd
:一個整數,代表加載命令的類型。表4列出了有效的加載命令類型。
cmdsize
:這個字段指定了記載命令數據結構總共的字節大小。根據load命令不同的類型,每個load命令結構包含一組不同的數據,所以每組數據大小可能不同。在32位架構中,這組數據的大小總是4的倍數,在64位架構中是8的倍數。如果load命令數據不能被4或8整除(這取決于目標體系結構是32位還是64位),則在末尾添加包含0的字節,直到它被整除為止。
討論
表四列出了有效的加載命令類型,以及每種類型完整數據的連接。
表四 Mach-O加載命令
命令 | 數據結構 | 用途 |
---|---|---|
LC_UUID | uuid_command(page 20) | 指定圖像或其對應的dSYM文件的128位UUID |
LC_SEGMENT | segment_command | 加載此文件時,定義映射到進程地址空間中所需的文件段。而且每個段中包含了所有的節 |
LC_SYMTAB | symtab_command | 指定了文件的符號表。靜態鏈接器和動態連接器連接文件的時候都需要用到這些信息,還可以通過調試器將符號映射到生成符號的原始源代碼文件。 |
LC_DYSYMTAB | dysymtab_command | 指定了動態連接器用到的附帶符號表信息 |
LC_THREAD LC_UNIXTHREAD | thread_command | 對于可執行文件,LC_UNIXTHREAD 命令定義了進程主線程的線程狀態。LC_THREAD 和LC_UNIXTHREAD 一樣,但是LC_THREAD 不會引起內核分配堆棧 |
LC_LOAD_DYLIB | dylib_command | 定義此文件鏈接的動態共享庫的名稱。 |
LC_ID_DYLIB | dylib_command | 定義了動態共享庫安裝名稱 |
LC_PREBOUND_DYLIB | prebound_dylib_command | 對于此可執行文件鏈接預綁定的共享庫,指定使用的共享庫中的模塊。 |
LC_LOAD_DYLINKER | dylinker_command | 指定內核執行加載文件所需的動態連接器 |
LC_ID_DYLINKER | dylinker_command | 標志這個文件可以作為動態連接器 |
LC_ROUTINES | routines_command | 包含共享庫初始化例行程序的地址(由鏈接器的-init 選項指定)。 |
LC_ROUTINES_64 | routines_command_64 | 包含共享庫64位初始化例行程序的地址(由鏈接器的-init 選項指定)。 |
LC_TWOLEVEL_HINTS | twolevel_hints_command | 包含兩級命名空間查詢提示表。 |
LC_SUB_FRAMEWORK | sub_framework_command | 將此文件標識為傘形框架的子框架的實現。傘形框架的名稱存儲在字符串參數中。(傘形框架可以包含多個子框架,蘋果不推薦這樣使用) |
LC_SUB_UMBRELLA | sub_umbrella_command | 指定此文件作為傘框架的子傘 |
LC_SUB_LIBRARY | sub_library_command | 標志這個文件可以作為傘框架的一個字庫的實現。請注意,Apple尚未為子庫定義受支持的位置。 |
LC_SUB_CLIENT | sub_client_command | 子框架可以明確地允許另一個框架或包鏈接到它,方法是包含一個LC_SUB_CLIENT load命令,該命令包含框架的名稱或包的客戶端名稱。 |
uuid_command
指定鏡像或者與之相匹配的dSYM文件的128位通用唯一標識符
struct uuid_command
{
uint32_t cmd;
uint32_t cmdsize;
uint8_t uuid[16];
};
字段
cmd
為此結構設置LC_UUID。
cmdsize
設置sizeof(uuid_command)
uuid
128位唯一標識符
segment_command
指定組成段的32位Mach-O文件中的字節范圍。這些字節通過加載器映射到程序的地址空間中。聲明在/usr/include/mach-o/loader.h
。
struct segment_command
{
uint32_t cmd;
uint32_t cmdsize;
char segname[16];
uint32_t vmaddr;
uint32_t vmsize;
uint32_t fileoff;
uint32_t filesize;
vm_prot_t maxprot;
vm_prot_t initprot;
uint32_t nsects;
uint32_t flags;
};
字段
??cmd
所有加載命令的結構中都有此字段。設置LC_SEGMENT
對于次結構體。
??cmdsize
所有加載命令結構體中都有此字段。對于這個結構,將這個字段設置為sizeof(segment_command)
加上后面所有section數據結構的大小(sizeof(segment_command + (sizeof(section) * segment->nsect))
。
??segname
一個用C字符串作為名字的段。這個字段值可以是任意順序的ASCII字符,Apple定義的段名以兩個下劃線開頭,并由大寫字母組成(如在__TEXT
和__DATA
中)。該字段的長度固定為16字節。
??vmaddr
指向段在虛擬內存地址中開始的位置。
??vmsize
指示此段占用的虛擬內存字節數。參見下面的filesize
大小說明。
??fileoff
指示數據文件被映射到虛擬內存中,相對于開始位置的偏移量。
??filesize
指定了段在磁盤上面占用的字節數量。對于這些段,它們在運行時要求的內存要比構建時多,vmsize
要比filesize
大。比如,對于MH_EXECUTABLE
文件連接器生成的__PAGEZERO
段分配的虛擬內存大小是0x1000但是文件大小是0。因為__PAGEZERO
段中沒有數據,這里是不會為它分配任何內存的,直到運行時。而且,靜態連接器經常分配未初始化的數據在__DATA
段后面;通過這些例子,vmsize
要大于filesize
。加載程序保證這種類型的任何內存都是用零初始化的。
??maxprot
表示此段被允許的且受保護的最大虛擬內存。
??initprot
表示此段受保護的初始化虛擬內存。
??nsects
表示此加載命令后面節數據結構的數量。
??flags
定義了一組標志,此段的加載會受到這組標志的影響:
-
SG_HIGHVM
--該段的文件內容為虛擬內存空間的高位部分;較低的部分是零填充(用于核心文件中的堆棧); -
SG_NORELOC
--這一段沒有任何東西被移動到它里面也沒有任何東西被移動到它里面。它可以安全更換,無需搬遷。
segment_command_64
表示由段組成的Mach-O文件的字節范圍。這些字節被映射到加載器加載程序的地址空間。
struct segment_command_64
{
uint32_t cmd;
uint32_t cmdsize;
char segname[16];
uint64_t vmaddr;
uint64_t vmsize;
uint64_t fileoff;
uint64_t filesize;
vm_prot_t maxprot;
vm_prot_t initprot;
uint32_t nsects;
uint32_t flags;
};
section_64
定義64位節使用的元素。直接跟在一個segment_command_64
數據結構后面的一組section_64
數據結構,segment_command_64
結構中,nsects
是這組數據的準確數量。
struct section_64
{
char sectname[16];
char segname[16];
uint64_t addr;
uint64_t size;
uint32_t offset;
uint32_t align;
uint32_t reloff;
uint32_t nreloc;
uint32_t flags;
uint32_t reserved1;
uint32_t reserved2;
};
字段
??secname
一個字符串,指定了這個節的名字。這個字段的值可以是任意序列的ASCII字符,蘋果定義的名字由兩個下劃線開始,并且由小寫字母組成(比如__text
和__data
)。這個字段的長度固定為16字節。
??segname
表示包含此節的段的字符串名字。為了緊湊性,中間對象文件--類型是MH_OBJECT
只包含一個段,并且把所有的節都放在這個一個段中。靜態連接器在構建最終產品時,會將每個節放到指定的段中(任意類型文件不僅是MH_OBJECT
)
??addr
一個整數表示了節的虛擬內存地址。
??size
一個整數標志此節占用的虛擬內存的字節大小
??offset
一個整數表示此節在文件中的偏移。
??align
一個整數表示節的字節對齊方式。這個整數是2的冪數;比如,具有8字節對齊的節的對齊值為3
??reloff
指定此部分的第一個重定位項的文件偏移量的整數。
??nreloc
一個整數,用于指定位于本節的重定位項的數目。
??flags
這個整數被分為兩部分。最低有效8位包含節類型,最多的有效24位包含了一組標記,這些標記表示節的其他屬性。這些類型和標記主要是靜態連接器和文件分析工具在使用,比如otool
,決定了要怎么修改和顯示這些節。下面是有效類型:
-
S_REGULAR
--這個節沒有特殊的類型。標準工具會創建這個類型的__TEXT
的__text
節。 -
S_ZEROFILL
--按需填充零的節--當這個section第一次讀取或者寫入的會后,每頁都會自動的被包含0的字節填充。 -
S_CSTRING_LITERALS
--這個節僅包含C的常量字符串。標準工具會創建此類型的__TEXT
段__cstring
section。 -
S_4BYTE_LITEBALS
--這個節僅包含4字節長的常量值。標準工具會創建此類型的__TEXT
的__literal4
節。 -
S_8BYTE_LITERALS
--這個節僅包含8字節長的常量值。標準工具會創建此類型的__TEXT
的__literal8
節。 -
S_LITERAL_POINTERS
--這個section僅包含常量值的指針。 -
S_NON_LASY_SYMBOL_POINTERS
--這個section僅包含非懶加載符號指針。標準工具會創建此類型的__DATA
段的__nl_symbol_ptrs
節 -
S_SYMBOL_STUBS
--這個section包含了符號樁(存根),標準工具會創建此類型的__TEXT
的__symbol_stub
節和__TEXT
的__picsymbol_stub
節。詳情請看Mach-O編程主題中的“動態代碼生成”。 -
S_MOD_INIT_FUNC_POINTERS
--這個section包含了模塊初始化函數的指針。標準工具會此類型的__DATA
的__mod_init_func
節。 -
S_MOD_TERM_FUNC_POINTERS
--這個section包含了模塊終止函數的指針。標準工具會創建此類型的__DATA
的__mod_term_func
節。 -
S_COALESCED
--本節包含由靜態鏈接器(可能還有動態鏈接器)合并的符號。多個文件可以包含同一符號的合并定義,而不會導致多個定義符號錯誤。 -
S_GB_ZEROFILL
--這是一個零填充隨需應變的section。他可能大于4GB。這個section一定要被放在僅包含零填充section的段中。如果你將一個零填充section放到一個非零填充的段中,會導致那些section用31位偏移訪問不到。這個結果源于一個事實,即一個零填充的部分的大小可以大于4 GB(在32位地址空間中)。正如結果一樣,靜態連接器是不能構建輸出文件的。
討論
在Mach-O文件中的每個section都包含類型和一組屬性標記。在中間對象文件中,這個類型和屬性決定了靜態連接器怎么將section拷貝到最終產品中。對象文件分析工具(例如otool)用類型和屬性決定怎么讀取和現實這些section。有些section類型和屬性是動態連接器用到的。
這些是符號類型和屬性的重要靜態鏈接變體:
- Regular sections.在常規部分中,中間對象文件中只能存在外部符號的一個定義。如果發現任何重復的外部符號定義,靜態鏈接器將返回一個錯誤。
- Coalesced sections.在最終產品中,靜態連接器在每個合并section符號定義中只有一個實例。為了支持復雜的語言(比如C++的vtables和RTTI)編譯器會為每個中間對象文件創建一個特別的符號定義。然后,靜態連接器和動態連接器將多個定義減少到程序用的單個定義。
- Coalesced sections with weak definitions弱引用符號定義可能僅顯示在合并section中。當用靜態連接器查找一個符號的多個定義時,它將忽略一些合并符號定義中被設置為弱定義。如果這里沒有非弱定義,第一個弱引用定義將被替換。這樣設計是為了支持C++模板;它允許一個明確的模板實例去復寫模糊的另一個模板。C++編譯器會將明確的定義放到工整的section中,而且會把模糊的定義放到合并section中,標記為若定義。用弱定義構建的中間目標文件(以及靜態存檔庫)只能在Mac OS X v10.2及更高版本的靜態鏈接器中使用。如果最終產品不會被用到macOS更好版本中,就不會包含若定義。
section_64
定義了64位section用到的元素。是一個section_64
數據結構的數組,直接放在segment_command_64
數據結構后面,這個數組的數量由segment_command_64
機構中的nsects
字段決定。
struct section_64
{
char sectname[16];
char segname[16];
uint64_t addr;
uint64_t size;
uint32_t offset;
uint32_t align;
uint32_t reloff;
uint32_t nreloc;
uint32_t flags;
uint32_t reserved1;
uint32_t reserved2;
};
字段
sectname
一個字符串指定了section的名稱。這個字段值可以是任意序列的ASCII字符,然而蘋果規定section名字的開始是兩個下劃線,后面由小寫字母組成。這個字段最多16個字節。
segname
一個字符串指定了最終包含此section的段的名字。為了緊湊性,類型為MH_OBJECT
的中間對象文件僅包含一個段,并將所有的section放到里面。靜態連接器。在靜態連接器構建最終產品時,回見每個section放到指定的段中。(任意文件不僅限于類型是MH_OBJECT
)。
addr
整數,指定了section在虛擬內存中的地址。
size
整數,指定了section在虛擬內存中所占大小。
offset
整數,指定了section在文件中的偏移。
align
整數,指定了section的字節對齊方式。這個整數是2的冪次方;比如,一個八字節對齊方式的section的align值是3.
reloff
整數,指定了此section第一個重定位入口的文件偏移。
nreloc
整數,指定了此section重定位入口的數量,位于reloff。
??flags
這個整數被分為兩部分。最低有效8位包含節類型,最多的有效24位包含了一組標記,這些標記表示節的其他屬性。這些類型和標記主要是靜態連接器和文件分析工具在使用,比如otool
,決定了要怎么修改和顯示這些節。下面是有效類型:
-
S_REGULAR
--這個節沒有特殊的類型。標準工具會創建這個類型的__TEXT
的__text
節。 -
S_ZEROFILL
--按需填充零的節--當這個section第一次讀取或者寫入的會后,每頁都會自動的被包含0的字節填充。 -
S_CSTRING_LITERALS
--這個節僅包含C的常量字符串。標準工具會創建此類型的__TEXT
段__cstring
section。 -
S_4BYTE_LITEBALS
--這個節僅包含4字節長的常量值。標準工具會創建此類型的__TEXT
的__literal4
節。 -
S_8BYTE_LITERALS
--這個節僅包含8字節長的常量值。標準工具會創建此類型的__TEXT
的__literal8
節。 -
S_LITERAL_POINTERS
--這個section僅包含常量值的指針。 -
S_NON_LASY_SYMBOL_POINTERS
--這個section僅包含非懶加載符號指針。標準工具會創建此類型的__DATA
段的__nl_symbol_ptrs
節 -
S_SYMBOL_STUBS
--這個section包含了符號樁(存根),標準工具會創建此類型的__TEXT
的__symbol_stub
節和__TEXT
的__picsymbol_stub
節。詳情請看Mach-O編程主題中的“動態代碼生成”。 -
S_MOD_INIT_FUNC_POINTERS
--這個section包含了模塊初始化函數的指針。標準工具會此類型的__DATA
的__mod_init_func
節。 -
S_MOD_TERM_FUNC_POINTERS
--這個section包含了模塊終止函數的指針。標準工具會創建此類型的__DATA
的__mod_term_func
節。 -
S_COALESCED
--本節包含由靜態鏈接器(可能還有動態鏈接器)合并的符號。多個文件可以包含同一符號的合并定義,而不會導致多個定義符號錯誤。 -
S_GB_ZEROFILL
--這是一個零填充隨需應變的section。他可能大于4GB。這個section一定要被放在僅包含零填充section的段中。如果你將一個零填充section放到一個非零填充的段中,會導致那些section用31位偏移訪問不到。這個結果源于一個事實,即一個零填充的部分的大小可以大于4 GB(在32位地址空間中)。正如結果一樣,靜態連接器是不能構建輸出文件的。
以下是一個部分可能的屬性:
-
S_ATTR_PURE_INSTRUCTIONS
--這個section僅包含可執行機器指令。標準工具會為__TEXT
的__text
、__TEXT
的__symbol_stub
和__TEXT
的__picsymbol_stub
設置此flag。 -
S_ATTR_SOME_INSTRUCTIONS
--這個section包含了可執行機器指令。 -
S_ATTR_NO_TOC
--section包含了合并符號,而且它是不會被放到靜態歸檔庫內容的表中的。 -
S_ATTR_EXT_RELOC
--section包含了一定被重定位的引用。這些引用涉及到了其他文件存在的數據(未定義的符號)。為了支持重定位擴展,段中最大受保護的虛擬內存包含的section必須允許被讀寫。 -
S_ATTR_LOC_RELOC
--section包含了一定被重定位的引用。這些引用涉及到了文件中的數據。 -
S_ATTR_STRIP_STATIC_SYMS
--如果鏡像mach_header
頭部中的結構設置了MH_DYLDLINK
標志,那么這個section的靜態符號是可以被去掉的。 -
S_ATTR_NO_DEAD_STRIP
--這部分不能拆裝。詳情可以參考Xcode用戶指南中的"Linking"。 -
S_ATTR_LIVE_SUPPORT
--如果引用的代碼是活躍的,但引用是不可檢測的,則此部分不能被拆裝。
討論
在Mach-O文件中的每個section都包含類型和一組屬性標記。在中間對象文件中,這個類型和屬性決定了靜態連接器怎么將section拷貝到最終產品中。對象文件分析工具(例如otool)用類型和屬性決定怎么讀取和現實這些section。有些section類型和屬性是動態連接器用到的。
這些是符號類型和屬性的重要靜態鏈接變體:
- Regular sections.在常規部分中,中間對象文件中只能存在外部符號的一個定義。如果發現任何重復的外部符號定義,靜態鏈接器將返回一個錯誤。
- Coalesced sections.在最終產品中,靜態連接器在每個合并section符號定義中只有一個實例。為了支持復雜的語言(比如C++的vtables和RTTI)編譯器會為每個中間對象文件創建一個特別的符號定義。然后,靜態連接器和動態連接器將多個定義減少到程序用的單個定義。
- Coalesced sections with weak definitions弱引用符號定義可能僅顯示在合并section中。當用靜態連接器查找一個符號的多個定義時,它將忽略一些合并符號定義中被設置為弱定義。如果這里沒有非弱定義,第一個弱引用定義將被替換。這樣設計是為了支持C++模板;它允許一個明確的模板實例去復寫模糊的另一個模板。C++編譯器會將明確的定義放到工整的section中,而且會把模糊的定義放到合并section中,標記為若定義。用弱定義構建的中間目標文件(以及靜態存檔庫)只能在Mac OS X v10.2及更高版本的靜態鏈接器中使用。如果最終產品不會被用到macOS更好版本中,就不會包含若定義。
twolevel_hints_command
定義了LC_TWOLEVEL_HINTS
加載命令的一些屬性。
struct twolevel_hints_command
{
uint32_t cmd;
uint32_t cmdsize;
uint32_t offset;
uint32_t nhints;
};
字段
cmd 所有加載命令的結構都有,次結構設置為LC_TWOLEVEL_HINTS
。
cmdsize 所有下載命令的結構都有。次結構,設置為sizeof(twoevel_hints_command)
。
offset 表示從文件開始到twolevel_hint
數據結構的數組的偏移量,稱為二級命名空間提示表。
nhints 位于偏移處二級命名數據結構的數量。
討論
當靜態連接器構建一個二級命名空間鏡像時,會增加LC_TWOLEVEL_HINTS
加載命令和二級命名空間提示表在輸出文件中。
特殊注意事項
默認,在HM_BUNDLE
文件中ld
是不包含LC_TWOLEVEL_HINTS
命令或者耳機命名空間提示表的,因為這樣命令的存在會導致Mac OS X v10.0裝載動態連接器時會崩潰。如果你的代碼僅運行在Mac OS X v10.1及之后版本,明確二級命名空間提示表可以被使用。
twolevel_hint
指定二級命名空間提示變的一個入口
struct twolevel_hint {
uint32_t isub_image:8,
itoc:24;
};
字段
isub_image 定義符號中的子鏡像。它是構成傘形鏡像的鏡像列表的索引。如果該字段為0,則符號在傘鏡像本身中。如果鏡像不是傘形框架或庫,則此字段為0。
itoc 將符號索引放入由isub_image字段指定的鏡像內容表中。
討論
兩級名稱空間提示表為動態鏈接器提供了建議的位置,以便開始在當前鏡像所鏈接的庫中搜索符號。
在兩級名稱空間映像中,每個未定義的符號(即N_UNDF或N_PBUD類型的每個符號)在同一索引處的兩級提示表中都有相應的條目。
當構建二級命名空間鏡像時,靜態連接器會添加LC_TWOLEVEL_HINTS
加載命令和二級命名空間提示表到輸出文件中。
默認情況下,連接器是不會將LC_TWOLEVEL_HINTS
加載命令或者二級命名空間提示表加到MH_BUNDLE
文件中的,因為這個load命令的存在會導致Mac OS X v10.0附帶的動態鏈接器版本崩潰。
lc_str
定義一個可變長度字符串。
union lc_str
{
uint32_t offset;
#ifndef __LP64__
char *ptr;
#endif
};
字段
offset 長整型。從包含此字符串的加載命令開始到字符串數據的開始的字節偏移。
ptr 指向字節數組的指針。在運行時,這個指針包含了字符串數據的虛擬內存地址。在Mach-O文件中不適用ptr
字段。
討論
加載命令存儲了可變長度的數據,比如使用lc_str
數據結構的庫名稱。除非另外指定,數據由一個C字符串組成。
指向的數據存儲在load命令之后,而且大小是加到加載命令大小上的。這個字符串應該以null終止;任何額外的字節都應該是null。通過減少加載命令數據結構中的字段cmdsize
來決定字符串的大小。
dylib
定義動態鏈接器使用的數據,以便將共享庫與鏈接到共享庫的文件進行匹配。專用于dylib_command(第32頁)數據結構。
struct dylib {
union lc_str name;
uint_32 timestamp;
uint_32 current_version;
uint_32 compatibility_version;
};
字段
name 類型為lc_str
的數據結構。表示共享庫的名稱。
timetamp 構建共享庫數據的時間
current_version 共享庫當前版本號
compatibility_version 共享庫的兼容版本號
dylib_command
定義了LC_LOAD_DYLIB
和LC_ID_DYLIB
加載命令的屬性
struct dylib_command {
uint_32 cmd;
uint_32 cmdsize;
struct dylib dylib;
};
字段
cmd 所有加載命令結構都有的字段。次結構中,設置為LC_LOAD_DYLIB
,LC_LOAD_WEAK_DYLIB
或者LC_ID_DYLIB
。
cmdsize 所有加載命令結構都有的字段。此結構中,設置為sizeof(dylib_command)加上由dylib字段的name字段指向的數據的大小。
dylib 類型為dylib
的數據結構。表示共享庫的屬性。
討論
文件鏈接到的每個共享庫,靜態鏈接器創建一個LC_LOAD_DYLIB
命令,并將其dylib字段設置為目標庫的LC_ID_DYLD
裝載命令的dylib字段的值。所有的LC_LOAD_DYLIB
命令一起組成一個列表,根據文件中的位置排序,最早的LC_LOAD_DYLIB
命令放在第一個。對于二級命名空間文件,符號表中未定義的符號項通過索引引用到此列表中的父共享庫。這個索引被稱為library ordinal
,并且它被存儲在nlist
數據結構中的n_desc
字段中。
在運行時,動態連接器用LC_LOAD_DYLIB
命令dyld
字段中的name字段定位共享庫。如果找到共享庫,動態連接器拿LC_LOAD_DYLIB
加載命令中版本信息和動態庫的做比較。要使動態鏈接器成功鏈接共享庫,共享庫的兼容版本必須小于或等于LC_LOAD_DYLIB
命令中的兼容版本。
動態連接器用時間戳決定是否能被預綁定信息。函數NSVersionOfRunTimeLibrary
返回的當前版本信息,由你來決定你程序用到庫的版本。
dylinker_command
定義了LC_LOAD_DYILNKER
和LC_ID_DYLINKER
加載命令的屬性。
struct dylinker_command {
uint32_t cmd;
uint32_t cmdsize;
union lc_str name;
};
字段
cmd 所有加載命令結構都有。此結構中,設置為LC_ID_DYLINKER
或者LC_LOAD_DYLINKER
。
cmdsize 所有加載命令結構都有。此結構中,設置為sizeof(dylinker_command)
加上name
字段指向的數據大小。
name 一個類型為lc_str
的數據結構。表示動態連接器的名稱。
討論
每個動態鏈接的可執行文件都包含一個指定動態連接器名稱的LC_LOAD_DYLINKER
指令,這個名稱是內核為了加載可執行文件所必需的。動態連接器本身的名稱用LC_ID_DYLINKER
加載指令表示。
prebound_dylib_command
定義了LC_PREBOUND_DYLIB
加載指令的屬性。對于預綁定的可執行文件鏈接到的每個庫,靜態鏈接器添加一個LC_PREBOUND_DYLIB
指令。
struct prebound_dylib_command {
uint32_t cmd;
uint32_t cmdsize;
union lc_str name;
uint32_t nmodules;
union lc_str linked_modules;
};
字段
cmd 每個加載指令結構中都有的字段。對于此結構,設置為LC_PERBOUND_DYLIB
。
cmdsize 每個加載指令結構中都有的字段。對于此結構,設置為sizeof(prebound_dylib_command)
加上name
和linked_moudules
字段指向數據的大小。
name 一個類型為lc_str
的數據結構。表示預綁定共享庫的名稱。
nmodules 一個整數。表示預綁定共享庫包含模塊的數量。linked_modules
字符串的大小是(modules / 8) + (nmodules % 8)
。
linked_modules 一個類型為lc_str
的數據結構。通常,這個數據結構定義了一個C字符串的偏移;這種用法中,它是一個變長位集,包含了每個module的位。沒個位代表了與之對應的module是否被連接到當前文件當中的module,1為yes,0位no。第一個module的位是第一個字節的低位。
thread_command
定義了LC_THREAD
和LC_UNIXTHREAD
加載指令的屬性。這個指令的數據對于每個架構都是特殊的,在thread_status.h
中顯示,位于架構目錄/usr/include/mach
中。
struct thread_command {
uint32_t cmd;
uint32_t cmdsize;
/* uint32_t flavor;*/
/* uint32_t count; */
/* struct cpu_thread_state state;*/
};
字段
cmd 所有加載指令結構都有的字段。對于此結構,設置為LC_THREAD
或者LC_UNIXTHREAD
。
cmdsize 設置為sizeof(thread_command)
加上flavor
和count
字段的大小再加上特定CPU線程狀態數據結構的大小。
flavor 指定線程狀態數據結構的特定樣式的整數。可以參考你目標架構中的thread_status.h
文件。
count 線程狀態數據的大小,以32位整數的數量為單位。線程狀態數據結構必須填充到32位對齊方式。
routines_command_64
定義了LC_ROUTINES_64
加載指令的屬性,被用在64位架構體系中。描述了共享庫初始化函數的位置,這是動態鏈接器在允許調用庫中的任何例行程序之前調用的函數。
struct routines_command_64 {
uint32_t cmd;
uint32_t cmdsize;
uint64_t init_address;
uint64_t init_module;
uint64_t reserved1;
uint64_t reserved2;
uint64_t reserved3;
uint64_t reserved4;
uint64_t reserved5;
uint64_t reserved6;
};
字段
cmd 所有指令結構都有的字段。此結構中,設置為LC_ROUTINES_64
cmdsize 所有的指令結構都有的字段。設置為sizeof(routines_command_64)
init_address 指定初始化函數的虛擬內存地址。
init_module 表示包含初始化函數模型的模型表中的索引值。
Symbol Table and Related Data Structure
兩個加載指令LC_SYMTAB
和LC_DYSYMTAB
,描述了符號表的大小和位置以及其他元數據。本節中列出的其他數據結構表示符號表本身。
symtab_command
定義了LC_SYMTAB
加載指令的屬性。描述了符號表數據結構的大小和位置。
struct symtab_command {
uint_32 cmd;
uint_32 cmdsize;
uint_32 symoff;
uint_32 nsyms;
uint_32 stroff;
uint_32 strsize;
};
字段
cmd 設置為LC_SYMTAB
cmdsize 設置為sizeof(symtab_command)
symoff 從文件開始到符號表入口位置的偏移量。符號表是nlist
數據結構的數組。
nsyms 符號表入口數量的值
stroff 從鏡像開始處到字符串表位置的偏移量。
strsize 字符串表的大小(用字節表示)
討論
LC_SYMTAB
應該同時存在于靜態鏈接和動態鏈接的文件類型中。
nlist_64
描述了64位架構的符號表中的入口
struct nlist_64 {
union {
uint32_t n_strx;
} n_un;
uint8_t n_type;
uint8_t n_sect;
uint16_t n_desc;
uint64_t n_value;
};
字段
n_un 將索引保存到字符串表中的聯合,用0表示空字符串("")
n_type 由使用四位掩碼訪問的數據組成的字節值:
-
N_STAB
(0xe0)--如果設置了這三位的任意一位,這個符號就代表符號調試表(stab)的條目。這個例子中,整個n_type
字段被解釋為stab
值。