一憎瘸、MachO文件概述
Mach-O
(Mach Object
)是mac
以及iOS
上的格式攻人, 類似于windows
上的PE
格式 (Portable Executable
)煮落,linux
上的elf
格式 (Executable and Linking Format
)膳灶。
Mach-O
是一種用于可執(zhí)行文件
、目標代碼
赃磨、動態(tài)庫
的文件格式筝家。作為a.out
格式的替代,Mach-O
提供了更強的擴展性邻辉。
1.1 MachO格式的常見文件
- 目標文件
.o
- 庫文件
.a
.dylib
Framework
- 可執(zhí)行文件
dyld
.dsym
1.2 格式驗證
1.2.1 .o
溪王、.out
、可執(zhí)行文件
新建test.c文件
值骇,內(nèi)容如下:
#include <stdio.h>
int main() {
printf("test\n");
return 0;
}
驗證.o
文件:
clang -c test.c
//clang -c test.c -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk
不指定-c
默認生成a.out
莹菱,如果報找不到'stdio.h' file not found
,則可以指定-isysroot
雷客。文章最后有具體的解決方案芒珠,
通過file
指令查看文件格式:
file test.o
test.o: Mach-O 64-bit object x86_64
驗證a.out
可執(zhí)行文件:
clang test.o
file a.out
a.out: Mach-O 64-bit executable x86_64
./a.out
test
驗證可執(zhí)行文件:
clang -o test2 test.c
file test2
test2: Mach-O 64-bit executable x86_64
./test2
test
至此再生成一個test3
可執(zhí)行文件:
clang -o test3 test.o
那么生成的a.out
、test2
搅裙、test3
一樣么皱卓?
可以看到生成的可執(zhí)行文件
md5
相同。
??原則上
test3
的md5
應該和test2
和a.out
相同部逮。源碼沒有變化娜汁,所以應該相同的。在指定-isysroot
后生成的可能不同兄朋,推測和CommandLineTools
有關(系統(tǒng)中一個掐禁,Xcode
中一個)。
再創(chuàng)建一個test1.c
文件颅和,內(nèi)容如下:
#include <stdio.h>
void test1Func() {
printf("test1 func \n");
}
修改test.c
:
#include <stdio.h>
void test1Func();
int main() {
test1Func();
printf("test\n");
return 0;
}
這個時候相當于有多個文件了傅事,編譯生成可執(zhí)行文件demo
、demo1
峡扩、demo2
:
clang -o demo test1.c test.c
clang -c test1.c test.c
clang -o demo1 test.o test1.o
clang -o demo2 test1.o test.o
這里
demo1
和demo2``md5
不同是因為test.o
和test1.o
順序不同蹭越。
objdump --macho -d demo
查看下macho
:
這也就解釋了
md5
不同的原因。這里很像Xcode
中Build Phases -> Compile Sources
中源文件的順序教届。
??源文件順序不同响鹃,編譯出來的二進制文件不同( 大小相同),二進制排列順序不同案训。
1.2.2.a
文件
直接創(chuàng)建一個library
庫查看:
//find /usr -name "*.a"
file libTestLibrary.a
libTestLibrary.a: current ar archive random library
1.2.3. .dylib
file /usr/lib/libprequelite.dylib
/usr/lib/libprequelite.dylib: Mach-O 64-bit dynamically linked shared library x86_64
1.2.4 dyld
cd /usr/lib
file dyld
dyld: Mach-O universal binary with 2 architectures: [x86_64:Mach-O 64-bit dynamic linker x86_64] [i386:Mach-O dynamic linker i386]
dyld (for architecture x86_64): Mach-O 64-bit dynamic linker x86_64
dyld (for architecture i386): Mach-O dynamic linker i386
這里需要注意的是dyld
不是可執(zhí)行文件买置,是一個dynamic linker
。系統(tǒng)內(nèi)核觸發(fā)强霎。
1.2.5 .dsym
file TestDsym.app.dSYM
TestDsym.app.dSYM: directory
cd TestDsym.app.dSYM/Contents/Resources/DWARF
file TestDsym
TestDsym: Mach-O 64-bit dSYM companion file arm64
二忿项、可執(zhí)行文件
創(chuàng)建一個工程,默認生成的文件就是可執(zhí)行文件城舞,查看對應的
MachO
:
file TestDsym
TestDsym: Mach-O 64-bit executable arm64
可以看到是一個單一架構的可執(zhí)行文件(??11
以上的系統(tǒng)都只支持64
位架構轩触,所以默認就沒有32
位的)。將Deployment Info
改為iOS 10
編譯再次查看MachO
:
file TestDsym
TestDsym: Mach-O universal binary with 2 architectures: [arm_v7:Mach-O executable arm_v7] [arm64:Mach-O 64-bit executable arm64]
TestDsym (for architecture armv7): Mach-O executable arm_v7
TestDsym (for architecture arm64): Mach-O 64-bit executable arm64
這個時候就有多個架構了椿争。
當然也可以在Xcode
中直觀的看到支持的架構:
Xcode
中架構設置在Build Settings -> Architectures
中:
-
Architectures
:支持的架構怕膛。 -
Build Active Architecture Only
:默認情況下debug
模式下只編譯當前設備架構,release
模式下需要根據(jù)支持的設備秦踪。 -
$(ARCHS_STANDARD)
:環(huán)境變量褐捻,代表當前支持的架構。
如果我們要修改架構直接在Architectures
中配置(增加armv7s
):
編譯再次查看
MachO
:
file TestDsym
TestDsym: Mach-O universal binary with 3 architectures: [arm_v7:Mach-O executable arm_v7] [arm_v7s:Mach-O executable arm_v7s] [arm64:Mach-O 64-bit executable arm64]
TestDsym (for architecture armv7): Mach-O executable arm_v7
TestDsym (for architecture armv7s): Mach-O executable arm_v7s
TestDsym (for architecture arm64): Mach-O 64-bit executable arm64
2.1通用二進制文件(Universal binary
)
- 蘋果公司提出的一種程序代碼椅邓,能同時適用多種架構的二進制文件柠逞。
- 同一個程序包中同時為多種架構提供最理想的性能。
- 因為需要儲存多種代碼景馁,通用二進制應用程序通常比單一平臺二進制的程序要大板壮。
- 由于多種架構有共同的非執(zhí)行資源(代碼以外的),所以并不會達到單一版本的多倍之多(特殊情況下合住,只有少量代碼文件的情況下有可能會大于多倍)绰精。
- 由于執(zhí)行中只調(diào)用一部分代碼撒璧,運行起來不需要額外的內(nèi)存。
當我們將通用二進制文件拖入Hopper
時笨使,能夠看到讓我們選擇對應的架構:
2.2lipo命令
lipo
是管理Fat File
的工具卿樱,可以查看cpu
架構,,提取特定架構硫椰,整合和拆分庫文件繁调。
使用lipo -info
可以查看MachO
文件包含的架構
lipo -info MachO文件
lipo -info TestDsym
Architectures in the fat file: TestDsym are: armv7 armv7s arm64
使用lifo –thin
拆分某種架構
lipo MachO文件 –thin 架構 –output 輸出文件路徑
lipo TestDsym -thin armv7 -output macho_armv7
lipo TestDsym -thin arm64 -output macho_arm64
file macho_armv7
macho_armv7: Mach-O executable arm_v7
file macho_arm64
macho_arm64: Mach-O 64-bit executable arm64
使用lipo -create
合并多種架構
lipo -create MachO1 MachO2 -output 輸出文件路徑
lipo -create macho_armv7 macho_arm64 -output macho_v7_64
file macho_v7_64
macho_v7_64: Mach-O universal binary with 2 architectures: [arm_v7:Mach-O executable arm_v7] [arm64:Mach-O 64-bit executable arm64]
macho_v7_64 (for architecture armv7): Mach-O executable arm_v7
macho_v7_64 (for architecture arm64): Mach-O 64-bit executable arm64
三、MachO文件結構
Mach-O 的組成結構如圖所示:
-
Header
:包含該二進制文件的一般信息靶草。- 字節(jié)順序蹄胰、架構類型、加載指令的數(shù)量等奕翔。
- 快速確認一些信息裕寨,比如當前文件用于
32
位還是64
位,對應的處理器是什么糠悯、文件類型是什么帮坚。
-
Load Commands
:一張包含很多內(nèi)容的表。- 內(nèi)容包括區(qū)域的位置互艾、符號表试和、動態(tài)符號表等。
-
Data
:通常是對象文件中最大的部分纫普。- 包含
Segement
的具體數(shù)據(jù)
- 包含
通用二進制文件就是包含多個這種結構阅悍。
otool -f MachO文件
查看Header
信息:
otool -f TestDsym
Fat headers
fat_magic 0xcafebabe
nfat_arch 3
architecture 0
cputype 12
cpusubtype 9
capabilities 0x0
offset 16384
size 79040
align 2^14 (16384)
architecture 1
cputype 12
cpusubtype 11
capabilities 0x0
offset 98304
size 79040
align 2^14 (16384)
architecture 2
cputype 16777228
cpusubtype 0
capabilities 0x0
offset 180224
size 79760
align 2^14 (16384)
分析MachO
最好的工具就是 MachOView
了:
與
otool
的內(nèi)容相同,對于多架構MachO
會有一個Fat Header
其中包含了CPU
類型和架構昨稼。Offset
和Size
代表了每一個每一個架構在二進制文件中的偏移和大小节视。
這里有個問題是16384+79040 = 95424 < 98304
,98304 - 16384 = 81920
假栓。81920 / 4096 / 4 = 5
寻行,可以驗證這里是以頁對齊的。(iOS
中一頁16K
匾荆,MachO
中都是以頁為單位對齊的拌蜘,這也就是為什么能在Load Commands
中插入LC_LOAD_DYLIB
的原因。)牙丽。
MachO
對應結構如下:
3.1Header
Header
數(shù)據(jù)結構:
對應dyld
的定義如下(loader.h
):
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
:魔數(shù)简卧,快速定位屬于64
位還是32
位。 -
cputype
:CPU
類型烤芦,比如ARM
举娩。 -
cpusubtype
:CPU
具體類型,arm64
,armv7
铜涉。 -
filetype
:文件類型智玻,比如可執(zhí)行文件,具體包含類型如下:
#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. */
-
ncmds
:Number of Load Commands
骄噪,Load Commands
條數(shù)尚困。 -
sizeofcmds
:Size of Load Commands
蠢箩,Load Commands
大小链蕊。 -
flags
:標識二進制文件支持的功能,主要是和系統(tǒng)加載谬泌、鏈接有關滔韵。 -
reserved
:arm64
特有,保留字段掌实。
3.2 LoadCommands
Load Commands
指示dyld
如何加載二進制文件陪蜻。
一個基本的Load Comands
如下:
-
__PAGEZERO
:
空指針陷阱,目的是為了和32
位指令完全分開贱鼻。(32
位地址在4G
以下宴卖,64
位地址大于4G
0xffffffff = 4G
)。
__PAGEZERO
不占用數(shù)據(jù)(file size
為0
)邻悬,唯一有的是VM Size
(arm64
4G
症昏,armv7
比較小)父丰。
VM Addr
: 虛擬內(nèi)存地址
VM Size
: 虛擬內(nèi)存大小肝谭。運行時刻在內(nèi)存中的大小,一般情況下和File size
相同蛾扇,__PAGEZERO
例外攘烛。
File offset
:數(shù)據(jù)在文件中偏移量。
File size
: 數(shù)據(jù)在文件中的大小镀首。
我們定位是看VM Addr
+ASLR
坟漱。
__TEXT
、__DATA
更哄、__LINKEDIT
:將文件中(32
位/64
位)的段映射到進程地址空間中芋齿。
分為三大塊,分別對應DATA
中的Section
(__TEXT
+__DATA
)竖瘾、__LINKEDIT
沟突。告訴dyld
占用多大空間。-
LC_DYLD_INFO_ONLY
:動態(tài)鏈接相關信息捕传。
Rebase
:重定向(ASLR
)偏移地址和大小惠拭。從Rebase Info Offset
+ASLR
開始加載336
個字節(jié)數(shù)據(jù)。
Binding
:綁定外部符號。
Weak Binding
:弱綁定职辅。
Lazy Binding
:懶綁定棒呛,用到的時候再綁定。
Export info
:對外開放的函數(shù)域携。 -
LC_SYMTAB
:符號表地址簇秒。
-
LC_DSYMTAB
:動態(tài)符號表地址。
-
LC_LOAD_DYLINKER
:使用何種動態(tài)加載器秀鞭。iOS
使用的是dyld
趋观。
-
LC_UUID
:文件的UUID
,當前MachO
文件的唯一識別標識锋边。
-
LC_VERSION_MIN_IPHONES
:支持最低的操作系統(tǒng)版本皱坛。
-
LC_SOURCE_VERSION
:源代碼版本。
-
LC_MAIN
:程序主程序的入口地址和棧大小豆巨。
逆向找不到切入點的時候一般找main
入口切入剩辟。(做了防護,運行就閃退的情況下)往扔。 -
LC_ENCRYPTION_INFO_64
:加密信息贩猎。
LC_LOAD_DYLIB
:依賴的三方庫。-
LC_RPATH
:@rpath
萍膛。Frameworks
庫的路徑吭服。
LC_FUNCTION_DYLIB
:函數(shù)起始地址表。LC_DATA_IN_CODE
:定義在代碼段內(nèi)的非指令的表卦羡。LC_DATA_SIGNATURE
:代碼簽名噪馏。
3.3Data
Data
包含Section
(__TEXT
+ __DATA
)、__LINKEDIT
绿饵。
3.3.1__TEXT
__TEXT
代碼段欠肾,就是我們的代碼。
-
__text
:主程序代碼拟赊。開始是代碼起始位置刺桃,和Compile Sources
中文件順序有關。
-
__stubs
&__stub_helper
:用于符號綁定吸祟。
這里
65a0
就是325a0
瑟慈,這里是循環(huán)做符號綁定。
__objc_methname
:方法名稱__objc_classname
:類名稱__objc_methtype
:方法類型__cstring
:字符串常量
3.3.2__DATA
__DATA
數(shù)據(jù)段屋匕。
-
__got
&__la_symbol_ptr
:外部符號有兩張表Non-Lazy
和Lazy
葛碧。
調(diào)用外部的函數(shù),只有運行的時候才能找到过吻。
Non-Lazy
啟動時刻就綁定:
Lazy
懶加載表进泼,表中的指針一開始都指向 __stub_helper
:
__cfstring
:程序中使用的Core Foundation
字符串(CFStringRefs
)蔗衡。__objc_classlist
:類列表。__objc_protolist
: 原型乳绕。__objc_imageinfo
:鏡像信息
__objc_selrefs
:self
引用__objc_classrefs
:類引用__objc_superrefs
:超類引用__data
:初始化過的可變數(shù)據(jù)绞惦。
3.3.3 __LINKEDIT
Dynamic Loader Info
:動態(tài)加載信息Function Starts
:入口函數(shù)Symbol Table
:符號表Dynamic Symbol Table
:動態(tài)庫符號表String Table
:字符串表Code Signature
:代碼簽名
總結
-
MachO
屬于一種文件格式。- 包含:可執(zhí)行文件洋措、靜態(tài)庫济蝉、動態(tài)庫、dyld等菠发。
- 可執(zhí)行文件:
- 通用二進制文件(Fat):集成了多種架構王滤。
-
lipo
命令:-thin
拆分架構,-creat
合并架構雷酪。
-
MachO
結構:-
Header
:快速確定該文件的CPU
類型淑仆,文件類型等。 -
Load Commands
:知識加載器(dyld
)如何設置并加載二進制數(shù)據(jù)哥力。 -
Data
:存放數(shù)據(jù),代碼墩弯、數(shù)據(jù)吩跋、字符串常量、類渔工、方法等锌钮。
-
問題處理
1.fatal error: 'stdio.h' file not found
方案一:
export CPATH=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include
https://developer.apple.com/forums/thread/123997
導入后警告處理,可以在終端加入以下符號去除警告
-Wno-nullability-completeness
,-Wno-expansion-to-defined
方案二:
傳遞參數(shù)-isysroot
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk
方案三(推薦):
拷貝頭文件到/usr/local/include/
目錄:
sudo ln -s /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/* /usr/local/include/
這種方式?jīng)]有警告引矩,推薦這么處理梁丘。如果/usr/local/include/
目錄中有文件可以先刪除(記的備份)。
有時候代碼格式化會配置
clang-format
的路徑旺韭,可以使用which clang
查看使用的是哪個clang
檢查路徑是否正確氛谜。/usr/bin/clang
https://stackoverflow.com/questions/58278260/cant-compile-a-c-program-on-a-mac-after-upgrading-to-catalina-10-15
https://akrabat.com/installing-pillow-on-macos-10-15-calatalina/