Mach-O

1、什麼是Mach-O文件?

Mach-OMach Object文件格式的縮寫,是mac以及iOS上可執行文件的格式。Mach-O文件對應有多種格式:xcode

  1. 目標文件.o
  2. 庫文件: .a靜態庫文件 .dylib動態庫文件 .framework系統級爲動態庫文件,本身建立的爲靜態庫文件
  3. 可執行文件及MDW.app內部的MDW文件(通用二進制文件)
  4. dyld動態連接器將依賴的動態庫加載到內存
  5. .dsym符號表

Xcode中咱們能夠直接建立.c文件,經過終端clang命令來對.c文件進行編譯或生成可執行文件,下面看一下clang怎樣使用的。bash

一、建立一個main.c文件以下:架構

#include <stdio.h>
int main(){
    printf("打印:yahibo\n");
    return 0;
}
複製代碼

二、編譯文件app

clang -c main.c
複製代碼

會生成main.o文件,該文件即爲mach-o文件,經過命令file main.o查看文件信息以下:ide

main.o: Mach-O 64-bit object x86_64
複製代碼

是一個object類型的文件稱爲目標文件,並非可執行文件函數

三、生成可執行文件ui

  • 命令clang main.o 會生成a.out文件,便可執行文件,經過ls查看
  • 命令clang -o main main.o 也會生成可執行文件main
  • 命令clang -o main main.c 直接根據源文件生成可執行文件main
  • 命令 size -x -l -m a.out 查看文件信息,以下:
Segment __PAGEZERO: 0x100000000 (vmaddr 0x0 fileoff 0)
Segment __TEXT: 0x1000 (vmaddr 0x100000000 fileoff 0)
	Section __text: 0x2a (addr 0x100000f50 offset 3920)
	Section __stubs: 0x6 (addr 0x100000f7a offset 3962)
	Section __stub_helper: 0x1a (addr 0x100000f80 offset 3968)
	Section __cstring: 0x11 (addr 0x100000f9a offset 3994)
	Section __unwind_info: 0x48 (addr 0x100000fac offset 4012)
	total 0xa3
Segment __DATA: 0x1000 (vmaddr 0x100001000 fileoff 4096)
	Section __nl_symbol_ptr: 0x10 (addr 0x100001000 offset 4096)
	Section __la_symbol_ptr: 0x8 (addr 0x100001010 offset 4112)
	total 0x18
Segment __LINKEDIT: 0x1000 (vmaddr 0x100002000 fileoff 8192)
total 0x100003000
複製代碼

四、執行文件this

./a.out 或 ./main
複製代碼

輸出:spa

打印:yahibo
複製代碼

以上步驟能夠用來寫c並編譯執行。操作系統

多個文件是如何編譯的呢?

開發中根據不一樣功能模塊咱們會分不少文件來實現,在clang中是能夠對多個文件進行一次性打包,生成一個可執行文件。以下:

一、新建一個功能文件

people.c

#include <stdio.h>
void sleep(){
    printf("正在睡覺\n");
}
複製代碼

二、在main.c中聲明sleep方法並調用

void sleep();//聲明方法
int main(){
    printf("打印:yahibo\n");
    sleep();//調用方法
    return 0;
}
複製代碼

三、編譯爲可執行文件

clang -o main main.c people.c
複製代碼

四、執行可執行文件

./main
複製代碼

運行以下:

打印:yahibo
正在睡覺
複製代碼

2、通用二進制文件(Universal binary)

iOS中不一樣手機對應着可能不一樣的架構,如arm6四、armv七、armv7s。爲了兼容不一樣架構的手機,蘋果推出了通用二進制文件,包含了應用程序經常使用的這些架構,所以通用二進制文件,比單一架構二進制文件要大不少。

架構選擇

arm.png

  • 注意以上標記的兩處取交集,來確認最終架構
  • 1處默認架構爲arm6四、armv7
  • 若是須要添加armv7s還須要在1處添加armv7s字符

經過以上配置真實編譯出來的是包含arm6四、armv7架構,由於工程中使用了第三方靜態庫不包含armv7s所以這裏配置爲標準架構模式。

通用二進制文件在哪呢?

xxx.app中的xxx黑色文件便是通用二進制文件,右鍵xxx.app顯示包內容便可得到。

lipo命令

經過lipo命令能夠查看、拆分及合併以上提出的架構,在作靜態庫時也會使用,來合併真機下和模擬器下的靜態庫,以適應不一樣的調試環境。

  • MDW.app中我獲取可執行文件MDW

一、查看架構信息

lipo -info MDW
複製代碼

打印以下:

Architectures in the fat file: MDW are: armv7 arm64
複製代碼

二、拆分armv七、arm64架構

lipo MDW -thin armv7 -output MDW_armv7
lipo MDW -thin arm64 -output MDW_arm64
複製代碼

查看armv7信息:

lipo -info MDW_armv7
複製代碼

打印以下:

Non-fat file: MDW_armv7 is architecture: armv7
複製代碼

查看arm64信息:

lipo -info MDW_arm64
複製代碼

打印以下:

Non-fat file: MDW_arm64 is architecture: arm64
複製代碼

三、合併架構

lipo -create MDW_armv7 MDW_arm64 -output MDW_ALL
複製代碼

查看合併後的信息

lipo -info MDW_ALL
複製代碼

打印以下:

Architectures in the fat file: MDW_ALL are: armv7 arm64
複製代碼

產生的可執行文件如圖:

mackos.png

3、Mach-O文件結構

官方圖解:

structure.png

文件分爲三個部分:

  • Header:包含Mach-O文件的基本信息,字節順序、架構類型、加載指令的數量等
  • Load commands:包含區域位置、符號表、動態符號表,加載Mach-O文件時使用這裏的數據肯定內存分佈
  • Data:數據段segement,包含具體代碼、常量、類、方法等,有多個segment,每一個segment有0到多個section,每一個段有一個虛擬地址映射到進程的地址空間

直接使用MachOView打開MDW可執行文件,以下:

macho.png

  • 胖二進制文件中包含了armv七、arm64架構
  • 經過MachOView便可查看可執行文件的全部信息

一、Header

除了以上直接查看header,還能夠經過otool命令查看header信息:

otool -f MDW
複製代碼

打印以下:

Fat headers
fat_magic 0xcafebabe
nfat_arch 2
architecture 0
    cputype 12
    cpusubtype 9
    capabilities 0x0
    offset 16384
    size 7587424
    align 2^14 (16384)
architecture 1
    cputype 16777228
    cpusubtype 0
    capabilities 0x0
    offset 7618560
    size 8748384
    align 2^14 (16384)
複製代碼

otool -h MDW
複製代碼

打印:

Mach header
      magic cputype cpusubtype  caps    filetype ncmds sizeofcmds      flags
 0xfeedface      12          9  0x00           2    48       5080 0x00210085
Mach header
      magic cputype cpusubtype  caps    filetype ncmds sizeofcmds      flags
 0xfeedfacf 16777228          0  0x00           2    48       5752 0x00210085
複製代碼
  • 以上打印的兩段分別是armv七、arm64架構下的header信息 在objc4源碼loader.h文件中有mach_header的結構體定義,以下:
struct mach_header {
 	uint32_t	magic;		/* mach magic number identifier */
 	cpu_type_t	cputype;	/* cpu specifier */
 	cpu_subtype_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 */
};
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;	/* 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 */
};
複製代碼
  • magic:魔數,肯定是64位仍是32位
  • cputype:cpu類型
  • cpusubtype:cpu子類型
  • filetype:Mach-O支持多種文件類型,使用filetype來標註具體文件類型
  • ncmds:加載命令的數量
  • sizeofcmds:命令區域(load commands)總的字節大小
  • flags:標識二進制文件所支持的功能,主要與系統的加載、連接有關

二、Load commands

Header以後是load commands段爲加載命令段,在header結構體中有對加載命令段相關信息的描述,用於解析加載命令。在objc4源碼loader.h中,有對loadcommand的定義:

struct load_command {
 	uint32_t cmd;		/* type of load command */
 	uint32_t cmdsize;	/* total size of command in bytes */
};
複製代碼
  • cmd:命令類型,針對不一樣架構有不一樣的結構(32位、64位)
  • cmdsize:命令所佔字節大小(32位size必須爲4字節的倍數,64位size必須爲8字節的倍數) 在文件中有兩個結構體segment_commandsegment_command_64針對不一樣架構的結構體,內部設置字段相同。以segment_command_64爲例:
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;		/* memory address of this segment */
 	uint64_t	vmsize;		/* memory size of this segment */
 	uint64_t	fileoff;	/* file offset of this segment */
 	uint64_t	filesize;	/* amount to map from the file */
 	vm_prot_t	maxprot;	/* maximum VM protection */
 	vm_prot_t	initprot;	/* initial VM protection */
 	uint32_t	nsects;		/* number of sections in segment */
 	uint32_t	flags;		/* flags */
};
複製代碼
  • cmd:加載命令類型
  • LC_SEGMENT:表示這好似一個段加載命令,須要將它加載到對應的進程空間上
  • LC_LOAD_DYLIB:這是一個須要動態加載的連接庫,它使用dylib_command結構體表示
  • LC_MAIN:記錄了可執行文件的主函數main()的位置,它使用entry_point_command結構體表示
  • LC_CODE_SIGNATURE:代碼簽名的加載命令,描述了Mach-O的代碼簽名信息,它屬於連接信息,使用linkedit_data_command結構體表示
  • cmdsize:加載命令所佔內存大小
  • segname:存放16字節大小的段名字,當前是__PAGEZERO
  • vmaddr:段的虛擬內存起始地址
  • vmsize:段的虛擬內存大小
  • fileoff:段在文件中偏移量
  • filesize:段在文件大小
  • maxprot:段頁面所須要的最高內存保護(4=r,2=w,1=x)
  • initprot:段頁面初始的內存保護
  • nsects:段中包含section的數量
  • flags:其餘雜項標誌位 在看MachOView中的loadcommands字段:

loadcommands.png

以上爲是應用程序全部加載命令,經過上面流程可以看到對系統庫的加載順序。對比項目中引入的庫文件,順序是一致的,以下圖:

xcode.png

以上加載命令含義以下:

  • LC_SEGMENT_64:將文件中的段映射到進程地址空間中
  • LC_DYLD_INFO_ONLY:動態連接相關信息
  • LC_SYMTAB:符號表信息,位置、偏移、數據個數,供dyld使用
  • LC_DYSYMTAB:動態符號表信息,供dyld使用
  • LC_LOAD_DYLINKER:連接器信息,記錄使用那些連接器完成內核後序的加載工做
  • LC_UUID:Mach-O文件的惟一標識
  • LC_VERSION_MIN_MACOSX:支持最低操做系統版本
  • LC_SOURCE_VERSION:源代碼的版本號
  • LC_MAIN:設置主線程的入口即棧大小
  • LC_LOAD_DYLIB:依賴庫信息,dyld經過該命令去加載依賴庫
  • LC_FUNCTION_STARTS:函數的起始地址表
  • LC_CODE_SIGNATURE:代碼簽名

三、Data

Data區域由Segment段和Section節組成:

segment.png

  • segment主要有__TEXT__DATA組成
  • __text:是主程序代碼
  • __stubs、__stub_helper:是動態連接的樁
  • __cstring:程序中c語言字符串
  • __const:常量

Section含義:

  • Section64(__TEXT,__objc_methname):OC類名
  • Section64(__DATA,__objc_classlist):OC類列表
  • Section64(__DATA,__objc_protollist):OC原型列表
  • Section64(__DATA,__objc_imageinfo):OC鏡像信息
  • Section64(__DATA,__objc_selfrefs):OC類自引用
  • Section64(__DATA,__objc_superrefs):OC類超類的引用
  • Section64(__DATA,__ivar):OC類成員變量

等等,都是經過section來對OC中的具體類別作加載的。segment段分32位和64位,字段相同,以64爲例以下:

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:__text ,就是主程序代碼
  • segname:該section所屬的segment名,第一個是__TEXT
  • addr:當前section在內存中的起始位置
  • size:當前section所佔內存大小
  • offset:當前section的文件偏移
  • align:字節大小對齊
  • reloff:重定位入口的文件偏移,0
  • nreloc:須要重定位的入口數量,0
  • flags:包含sectiontypeattributes
  • reserved一、reserved2預留字段
相關文章
相關標籤/搜索