每个Mach-O文件由 Mach-O header,header 后跟 一系列的load commonds,然后接着1个或多个segement,且每一个segement由0到255个section组成。Mach-O使用REL 重定位格式来处理对符号的引用。在查找符号时,Mach-O使用两层名称空间,将每个符号编码成一个“对象/符号名称”对,然后通过对象和符号名称进行线性搜索。
Machg-O文件结构画图表示如下
首先是header,在dylib连接器源码中对Mach-O文件header结构描述如下:
/*
* The 32-bit mach header appears at the very beginning of the object file for
* 32-bit architectures.
*/
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 */
};
/* Constant for the magic field of the mach_header (32-bit architectures) */
#define MH_MAGIC 0xfeedface /* the mach magic number */
#define MH_CIGAM 0xcefaedfe /* NXSwapInt(MH_MAGIC) */
/*
* The 64-bit mach header appears at the very beginning of object files for
* 64-bit architectures.
*/
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 */
};
/* Constant for the magic field of the mach_header_64 (64-bit architectures) */
#define MH_MAGIC_64 0xfeedfacf /* the 64-bit mach magic number */
#define MH_CIGAM_64 0xcffaedfe /* NXSwapInt(MH_MAGIC_64) */
可以看到64位机器与32位机器头部基本一致,每种文件都有各自的magicNumber,用于标志文件类型。例如,.exe 文件的Magic number 是4D5A, .png 的文件的Magic number 是89 50 4E 47 0D 0A 1A 0A,pdf 的Magic number 是25 50 44 46等等。
cputype 表示cpu类型,包括X86,I386,X86_64,arm64d等
cpusubtype,机器子类型,有VAX780,VAX785,VAX750等
filetype 文件类型,文件部分类型定义如下:
#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 */
使用otool 工具查看xxxframeWork 的header内容
Mach header
magic 0xfeedface
cputype 12
cpusubtype 9
caps 0x00
filetype 6
ncmds 35
sizeofcmds 4124
flags 0x00100085
Mach header
magic 0xfeedfacf
cputype 16777228
cpusubtype 0
caps 0x00
filetype 6
ncmds 35
sizeofcmds 4672
flags 0x00100085
上面一个是armv7的,下面是arm64的。
同时header中还记录了load command 的个数,所有load Command的大小。load command 结构记录如下
struct load_command {
uint32_t cmd; /* type of load command */
uint32_t cmdsize; /* total size of command in bytes */
};
load command 直接跟随mach_header。总大小由mach_header中的sizeofcmds字段给出。所有load命令必须具有cmd和cmdsize的前两个字段。 cmd字段用 记录 load命令类型的常量填充。每个load命令类型都有它特殊的结构。这里给出部分load command 类型
#define LC_SEGMENT 0x1 /* segment of this file to be mapped */
#define LC_SYMTAB 0x2 /* link-edit stab symbol table info */
#define LC_SYMSEG 0x3 /* link-edit gdb symbol table info (obsolete) */
#define LC_THREAD 0x4 /* thread */
#define LC_UNIXTHREAD 0x5 /* unix thread (includes a stack) */
#define LC_LOADFVMLIB 0x6 /* load a specified fixed VM shared library */
#define LC_IDFVMLIB 0x7 /* fixed VM shared library identification */
#define LC_IDENT 0x8 /* object identification info (obsolete) */
#define LC_FVMFILE 0x9 /* fixed VM file inclusion (internal use) */
#define LC_PREPAGE 0xa /* prepage command (internal use) */
cmdsize字段是以字节为单位的, 特定的load命令结构,加上它后面所跟随的内容 组成完整load命令。
在terminal下使用otool 工具查看 一下某个framework的 load commands
otool -f xxxFoundationModule | open -f
得到结果如下:
Fat headers
fat_magic 0xcafebabe
nfat_arch 2
architecture 0
cputype 12
cpusubtype 9
capabilities 0x0
offset 16384
size 1022336
align 2^14 (16384)
architecture 1
cputype 16777228
cpusubtype 0
capabilities 0x0
offset 1048576
size 1150640
align 2^14 (16384)
Segment区段在dylib 源码中记录如下(32机器位举例):
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 */
uint32_t vmsize; /* memory size of this segment */
uint32_t fileoff; /* file offset of this segment */
uint32_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 */
};
其中nsects 字段记录了segment中包含的section个数。
源码中提到,一个segment由0或者多个section组成,MH_OBJECT格式下segment的所有section都在一个段中紧凑排列。没有填充指定的段边界和 mach_header和load命令不是segment的一部分。 部分名称相同名称的section,用sectname表示,进入相同的segement,用segname表示,由链接编辑器组合。
segment 类型主要有__TEXT segment,__DATA segment,__LINKED segment
__TEXT segment 包含了被执行的代码。它被以只读和可执行的方式映射。进程被允许执行这些代码,但是不能修改。这些代码也不能对自己做出修改,因此这些被映射的页从来不会被改变。
__DATA segment 以可读写和不可执行的方式映射。它包含了将会被更改的数据。
__LINKED segment 用于链接器链接的区段。
在 segment中,一般都会有多个 section。它们包含了可执行文件的不同部分。在 __TEXT segment 中,
__text section 包含了编译所得到的机器码。__stubs 和 __stub_helper 是给动态链接器 (dyld) 使用的。
通过这两个 section,在动态链接代码中,可以允许延迟链接。__const (在我们的代码中没有) 是常量,不可变的,
就像 __cstring (包含了可执行文件中的字符串常量 -- 在源码中被双引号包含的字符串) 常量一样。
__DATA segment 中包含了可读写数据。在我们的程序中只有 __nl_symbol_ptr 和 __la_symbol_ptr,
它们分别是 non-lazy 和 lazy 符号指针。延迟符号指针用于可执行文件中调用未定义的函数,例如不包含在可执行文件中的函数,
它们将会延迟加载。而针对非延迟符号指针,当可执行文件被加载同时,也会被加载。
在 _DATA segment 中的其它常见 section 包括 __const,在这里面会包含一些需要重定向的常量数据。
例如 char * const p = "foo"; -- p 指针指向的数据是可变的。__bss section 没有被初始化的静态变量,
例如 static int a; -- ANSI C 标准规定静态变量必须设置为 0。并且在运行时静态变量的值是可以修改的。
__common section 包含未初始化的外部全局变量,跟 static 变量类似。例如在函数外面定义的 int a;。
最后,__dyld 是一个 section 占位符,被用于动态链接器。
引自:Mach-O文件结构
Section 的结构记录如下:
struct section { /* for 32-bit architectures */
char sectname[16]; /* name of this section */
char segname[16]; /* segment this section goes in */
uint32_t addr; /* memory address of this section */
uint32_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) */
};
同样可以使用otool查看 segement 区段
otool -o xxxFoundationModule | open -f
-o 代码表输出 OC的 segment
从输出来看,-o 选项可以查看frameWork中的所有类变异后的虚拟地址,继承关系,公有,私有api的名字,值的返回类型。而且很清晰的表现了OC类在内存里的存储方式。
看到这里感觉新世界的大门打开了,知道了类名,方法列表,如果往ipa的包中加入自己的frameWork,然后对包再重签名,可怕的事情就会发生了。。。。