Mach-O为Mach object文件格式的缩写,它是一种用于可执行文件、目标代码、动态库的文件格式,由多个源文件组成。作为a.out格式的替代,Mach-O提供了更强的扩展性。
常见文件:.a
,.dylib
,Framework
,可执行文件,dyld
,.dSYM(release)
这里值得注意的是nib
虽然用file
命令查看是data
文件,但是仍被构建成动态链接共享库的。其本质就是伪nib
,是一个.dylib
文件。
C文件的编译链接实践
1、创建C文件,并编写相关代码,然后进行编译,生成test.o
的目标文件。
查看目标文件,得到一个为
Mach-O
的object
文件。
file test.o
2、链接一下,生成a.out
可执行文件。
链接器或链接编辑器是计算机实用程序,它负责接收由编译器生成的一个或多个目标文件,并将它们组合成单个可执行文件,库文件或者另一个"对象"文件。
Clang test.o
生成为
test2
的可执行文件:
clang -o test2 test.o
通过执行可执行文件可得到打印结:
./a.out
//%只是一个结束符。
test===test===test===test===test%
探寻系统的dylib
库
通过命令:find /usr/lib -name "*.dylib"
查看系统的动态库,下面列出常见的有:
#libobjc:objc和runtime
/usr/lib/libobjc.A.dylib
/usr/lib/libSystem.dylib
libSystem
中常见的库有:
- libdispatch ( GCD )
- libsystem_c ( C语言库 )
- libsystem_blocks ( Block )
- libcommonCrypto ( 加密库,比如常用的 md5 函数 )
通过命令file /usr/lib/libobjc.dylib
查看动态库信息:
通过
cd /usr/lib
进入dyld
所在文件夹,file dyld
查看其具体信息。可知它是Mach-O
文件,但不是可执行文件,属于dynamic linker
,Mach-O
中的独立类型。
探寻dSYM
文件。
将工程置于release
模式下,Build
。
显示
dSYM
包内容,通过命令可知它也是属于Mach-O
文件
浅谈可执行文件架构
通用二进制文件(UNIVERSAL BINARY):
通用二进制代码有两种基本类型。一种类型就是简单提供两种独立的二进制代码,一个用来对应x86架构,一个用来对应PowerPC架构。但是对于不熟悉代码的普通软件使用者来说,在购买和使用的时候,可能搞不清二者区别。另外一种类型就是只编写一个架构的代码,当另外一种处理环境时让系统自动调用模拟器运行。这会导致运行速度下降,一般是作为“通用二进制”或者“特别连编二进制”出现之前暂时使用的折衷办法。(参见Rosetta)
因为需要储存多种代码,通用二进制应用程序通常比单一平台二进制的程序要大,但是由于两种架构有共通的非执行资源,所以并不会达到单一版本的两倍之多。而且由于执行中只调用一部分代码,运行起来也不需要额外的内存。
目前,苹果公司的Xcode是唯一一个可以编译通用二进制代码的GUI工具
$(ARCHS_STANDARD)
:代表armv7
和arm64
两种架构。
lipo命令=>拆分可执行文件
lipo命令=>合并可执行文件
使用lipo -info 可以查看MachO文件包含的架构
$lipo -info MachO文件
使用lipo –thin 拆分某种架构
$lipo MachO文件 –thin 架构 –output 输出文件路径
使用lipo -create 合并多种架构
$lipo -create MachO1 MachO2 -output 输出文件路径
分析Mach-O
的可执行文件
分三大模块:
Header
header
包含该二进制文件的一般信息
字节顺序、架构类型、加载指令的数量等。由系统内核负责读取。
使得可以快速确认一些信息,比如当前文件用于32位还是64位,对应的处理器是什么、文件类型是什么
Load Commands
一张包含了很多内容的表,内容包括区域的位置、符号表、动态符号表等。
Data
通常是对象文件中最大的部分,包含了Segement的具体数据
通过otool
查看可执行文件的一些信息,根据提示命令获取所需要的信息。
-f print the fat headers
-a print the archive header
-h print the mach header
-l print the load commands
-L print shared libraries used
-D print shared library id name
-t print the text section (disassemble with -v)
-p start dissassemble from routine name
-s print contents of se
查看Mach-O
的源码文件
- 定义的
Mach-O
文件:
#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 */
- 定义的
Header
struct mach_header_64 {
uint32_t magic; //魔数:快速定位属于64位还是32位
cpu_type_t cputype; //cpu 类型
cpu_subtype_t cpusubtype; //cpu的具体类型
uint32_t filetype; //文件类型,比如可执行文件。
uint32_t ncmds; //load commands 的数量
uint32_t sizeofcmds; //load command 的大小
uint32_t flags; //表示二进制文件所支持的一些功能,和系统加载有关系。描述文件在编译、链接等过程中的信息,示例中的 MH_NOUNDEFS 表示文件中不存在未定义的符号,MH_DYLDLINK 表示文件要交由 DYLD 进一步处理,MH_TWOLEVEL 表示文件使用两级命名空间,MH_PIE 表示启用地址空间布局随机化。
uint32_t reserved; //比32位多一个保留字段。
};
通过MachOView
查看文件
1、映射:告诉对方二进制文件会映射到手机内存中会占用多大,下一块是谁。
_PAGEZERO
:这个字段在文件中不存在的,在虚拟内存中是一块区域,不具备访问权限,专门来处理空指针。
VM Address
:虚拟内存地址,4个G,根据CPU架构而来。
File Offset
:文件偏移地址。
2、LC_DYLD_ INFO_ONLY
:动态链接的相关信息。
它的 ONLY 后缀表明这是程序运行所必须的,如果链接器不支持,那么加载过程就会终止。
struct dyld_info_command {
uint32_t cmd; /* LC_DYLD_INFO or LC_DYLD_INFO_ONLY */
uint32_t cmdsize; /* sizeof(struct dyld_info_command) */
uint32_t rebase_off; //重定向的偏移值。(ASLR)
uint32_t rebase_size; //重定向的大小。
uint32_t bind_off; //绑定。可执行文件读到内存中会绑定一些数据。weak绑定,lazy绑定。
uint32_t bind_size; /* size of binding info */
uint32_t weak_bind_off; /* file offset to weak binding info */
uint32_t weak_bind_size; /* size of weak binding info */
uint32_t lazy_bind_off; /* file offset to lazy binding info */
uint32_t lazy_bind_size; /* size of lazy binding infs */
uint32_t export_off; //对外开发的函数。
uint32_t export_size; /* size of lazy binding infs */
};
3、LC_SYMTAB
和LC_DYSYMTAB
,符号表地址和动态符号表地址。
记录了程序的符号表以及字符串表的偏移量及大小,符号表中记录了程序用到的函数以及全局变量的信息
/*
* Format of a symbol table entry of a Mach-O file for 32-bit architectures.
*/
struct nlist {
union {
#ifndef __LP64__
char *n_name; /* for use when in-core */
#endif
uint32_t n_strx; /* index into the string table */
} n_un;
uint8_t n_type; /* type flag, see below */
uint8_t n_sect; /* section number or NO_SECT */
int16_t n_desc; /* see */
uint32_t n_value; /* value of this symbol (or stab offset) */
};
/*
* This is the symbol table entry structure for 64-bit architectures.
*/
struct nlist_64 {
union {
uint32_t n_strx; /* index into the string table */
} n_un;
uint8_t n_type; /* type flag, see below */
uint8_t n_sect; /* section number or NO_SECT */
uint16_t n_desc; /* see */
uint64_t n_value; /* value of this symbol (or stab offset) */
};
数据结构中相关字段的含义都可以在 nlist.h 中找到,这里值得一说的是 n_un 字段,它用来记录符号的名字,但为什么是 uint32_t 类型呢?又为什么在注释中标明是 string table 的 索引呢?
这是因为在程序中,字符串的长度是不固定的,所以会将其放在 string table 中,然后存储它在 string table 中的偏移。如果其他部分想要引用某个字符串,那么他首先需要找到 string table 的起始地址,然后根据偏移量找到相应字符串的起始位置并向后读取字符,直到遇见 \0 才会停止读取过程,最后返回读到的字符串。
这也是 LC_SYMTAB 额外记录 string table 地址的原因,string table 通常用于记录 section 名、符号名等信息。
4、LC_LOAD_DYLINKER
:动态连接器:dyld
,在iPhone手机下的usr/lib/dyld
来加载的。调用这个方法之前都是系统内核进行调用的。
5、LC_UUID
:静态链接器为其生成的文件所提供的唯一标识符
6、LC_MAIN
:指定 main 函数的地址
7、LC_LOAD_DYLIB
:加载一些依赖库和三方库的地址。
补充:
-
DATA
段有懒加载表和非懒加载表。 -
symbol
:用于绑定的时候会用到这里的信息。 -
Mach-O
文件被内核加载,被DYLD
读取。
参考链接:
Mach-O文件格式参考
Mach-O文件格式分析