“Mach-O探索(一)”我们已经分析过了
Mach-O
到底是什么,包括分析Mach-O
有什么用,以及分析Mach-O
常用工具。现在我们分析一下
Mach-O
文件的结构及它们的功能。
Mach-O的格式
对于苹果来说,Mach-O
是可执行文件,它其实包含以下几种文件类型
-
Executable
:可执行文件 -
Dylib
:动态链接库 -
Bundle
:无法被链接的动态库,只能在运行时使用dlopen加载 -
Image
:指的是Executable
、Dylib
和Bundle
的一种 -
Framework
:包含Dylib
、资源文件和头文件的集合
通过machoview我们可以得到其内部结构
归纳以下Mach-O文件组成如下图
总结:一个Mach-O的组成主要分为三大部分:Header Mach-O头部
、Load Commands 加载命令
、Data 数据
。它们的作用如下: -
Header Mach-O头部
:主要是Mach-O的cpu架构,文件类型以及加载命令等信息 -
Load Commands 加载命令
:描述了文件内部数据的具体组织结构,不同的数据类型使用不同的加载命令表示 -
Data 数据
:数据中的每个段(segment)的数据都保存在这里。每个段都有一个或多个部分,它们放置了具体的数据与代码,主要包含代码,数据,例如符号表,动态符号表等等
Header分析
Header源码分析如下
struct mach_header_64 {
// 0xfeedface(32位) 0xfeedfacf(64位),系统内核用来判断是否是mach-o格式
uint32_t magic; /* mach magic number identifier */
// CPU类型,比如ARM
cpu_type_t cputype; /* cpu specifier */
// CPU的具体类型,例如arm64、armv7
cpu_subtype_t cpusubtype; /* machine specifier */
// 由于可执行文件、目标文件、静态库和动态库等都是mach-o格式,filetype来说明mach-o文件是属于哪种文件
uint32_t filetype; /* type of file */
// 加载命令的条数(加载命令紧跟header之后)
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 */
};
CPU
通过读取Header
文件能快速获取到可执行文件支持的CPU
架构、系统类型、指令的条数。针对32
位和64
位架构的cpu,分别使用了mach_header
和mach_header_64
结构体。64位架构的mach_header_64
比32
位架构的mach_header
,仅仅多了一个reserved
保留字段。
Load Commands
Load Commands
主要是用于加载指令
,它的大小和数目在Header中已经被提供,Load Commands
源码分析如下
struct load_command {
// 命令的类型
uint32_t cmd; /* type of load command */
// 命令的大小
uint32_t cmdsize; /* total size of command in bytes */
};
Load commands记录了动态链接器的位置、程序的入口、依赖库的信息、代码的位置、符号表的位置
等等信息。Load commands内部组成部分功能如下:
LC_SEGMENT_64(_PAGEZERO)
:用于记录共享虚拟空间信息如位置和大小,一般用于放空指针
LC_SEGMENT_64(_TEXT)
:只读数据段,记录TEXT段的起始位置、大小、偏移信息。
LC_SEGMENT_64(_DATA)
:读写数据段,记录DATA段的起始位置、大小、偏移信息。
LC_SEGMENT_64(_LINKEDIT)
:链接使用段,记录dyld的位置信息。
LC_DYLD_INFO_ONLY
:记录dyld的重定向、懒加载、绑定等信息。
LC_SYMTAB
:符号表信息,记录符号表的位置、偏移量、数据个数等。
LC_DYSYMTAB
:符号表的一些额外信息。
LC_LOAD_DYLINKER
:记录dyld链接的cpu的内核信息,和位置信息。
LC_UUID
:唯一标识符。
LC_SOURCE_VERSION
:代码的版本信息。
LC_MAIN
:入口地址,dyld通过这里跳转程序主入口。
LC_ENCRYPTION_INFO_64
:加密标识,标记了是否被加密以及加密内容的偏移等。
LC_LOAD_DYLIB(Foudation)
:依赖库信息,标记了依赖库的位置和版本信息。
LC_RPATH
:路径信息。
LC_FUCTION_STARTS
:函数起始地址表。
LC_DATA_IN_CODE
: 代码段非指令表。
LC_CODE_SIGNATURE
:代码签名信息。
其中LC_SEGMENT_64
的类型segment_command_64
源码定义如下
// 段加载命令
struct segment_command_64 { /* for 64-bit architectures */
// 表示加载命令类型,
uint32_t cmd; /* LC_SEGMENT_64 */
// 表示加载命令大小(还包括了紧跟其后的nsects个section的大小)
uint32_t cmdsize; /* includes sizeof section_64 structs */
// 16个字节的段名字
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 */
// 段页面所需要的最高内存保护(4 = r,2 = w,1 = x)
vm_prot_t maxprot; /* maximum VM protection */
// 段页面初始的内存保护
vm_prot_t initprot; /* initial VM protection */
// 段中section数量
uint32_t nsects; /* number of sections in segment */
// 杂项标志位
uint32_t flags; /* flags */
};
DATA
data区域存储了代码信息,而且此区域可读可写。如方法、符号表、字符表、代码数据、连接器所需的数据(重定向、符号绑定等)。在data区域,section占用了很大的比例。Section
在Mach.h
中是以section_64
(在arm64架构下)表示,其源码定义如下
struct section_64 { /* for 64-bit architectures */
// 当前section的名称
char sectname[16]; /* name of this section */
// section所在的segment名称
char segname[16]; /* segment this section goes in */
// 内存中起始位置
uint64_t addr; /* memory address of this section */
// section大小
uint64_t size; /* size in bytes of this section */
// 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 */
// 标志,section的类型和属性
uint32_t flags; /* flags (section type and attributes)*/
// 保留(用于偏移量或索引)
uint32_t reserved1; /* reserved (for offset or index) */
// 保留(用于count或sizeof)
uint32_t reserved2; /* reserved (for count or sizeof) */
// 保留
uint32_t reserved3; /* reserved */
};
总结:大多数的Mach-O文件均包含以下三个段:
-
__TEXT 代码段
:只读,包括函数,和只读的字符串 -
__DATA 数据段
:读写,包括可读写的全局变量等 -
__LINKEDIT
: __LINKEDIT包含了方法和变量的元数据(位置,偏移量),以及代码签名等信息。
网友评论