[toc]
MachO文件概述
Mach-O
其实是Mach Object
文件格式的缩写,是mac
以及iOS
上可执行文件的格式, 类似于windows
上的PE格式 (Portable Executable
),linux
上的ELF
格式 (Executable and Linking Format
).
可执行文件
Mach)常见文件格式
- 目标文件.o
- 库文件.a .dylib Framework
- 可执行文件
- dyld
- .dsym
File
指令
- 通过
$file
文件路径 查看文件类型。
MatchO
有很多不同的类型,可以通过在Xcode
上指定。
通用二进制文件
- 苹果公司提出的一种程序代码。能同时适用多种架构的
二进制文件
- 同一个程序包中同时为
多种架构
提供最理想的性能。 - 因为需要储存多种代码,通用二进制应用程序通常比单一平台二进制的程序要大。
- 但是 由于两种架构有共通的非执行资源,所以并不会达到单一版本的两倍之多。
而且由于执行中只调用一部分代码,运行起来也不需要额外的内存。
`MachO``文包含的架构资源共享,但是代码部分不共享.
- 在
Xcode
编译可以指定生成哪些架构的Match-O
文件(Architectures和Valid Architectures交集)
-
lipo -info
查看machO
文件架构
使用
lipo -info
可以查看MachO文件包含的架构lipo MachO
文件–thin
架构–output
输出文件路径使用lipo -create
合并多种架构$lipo -create MachO1 MachO2 -output
输出文件路径
Mach文件结构
Header
作用:快速确认Mach-O文件的基本信息,如运行环境,Load Commands
概述。
数据结构:
64位架构
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
:确定Mach-O
文件运行框架,如64位/32位
cpu
:CPU类型,如arm
cpusubtype
:对应CPU类型的具体型号
filetype
:文件类型
ncmds
:加载命令条数
sizeofcmds
:所有加载命令的大小
flags
:保留字段
reserved
:标志位
- 使用
MachOView
工具查看Mach-O
文件,如下图:
Mach-O文件运行基本环境:
cpu
:x86,64位
文件类型:可执行二进制文件
LoadCommand
:55
个LoadCommand
,占用大小位1360
Load commands
load Commands
是由很多的LC_Type
组成的,而LC_Type
有很多种,可在文件loader.h
文件中查看
作用:
Load Commands
加载指令,告诉加载器如何处理二进制数据,处理对方分别为内核,动态链接器等。加载指令紧跟在Header
后的加载命令区。Load Commands
加载指令个数及大小在Header
中定义( commands
的大小总和即为 Header->sizeofcmds
字段,共有 Header->ncmds 条加载命令)。
数据结构:
struct load_command {
uint32_t cmd; /* type of load command */
uint32_t cmdsize; /* total size of command in bytes */
};
-
cmd
:指令类型 -
cmdsize
: 指令长度
Command
以LC
开头,不同的加载指令有不同的专有结构,但必包含cmd,cmdsize
两个字段,用来定义指令类型以及指令长度
指令类型:
LC_SEGMENT/LC_SEGMENNT_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_64 (固定)
cmdsize
:指令长度
segname
:段名,如_PAGEZERO(保留数据空间,4g),_TEXT(代码数据),_DATA(函数指针),_LINKDIT(链接数据)
vmaddr
:段的虚拟内存起始地址
vmsize
:段的虚拟内存大小
fileoff
:段在文件中的偏移量
filesize
:段在文件中的大小
maxprot
:表示页面所需要的最高内存保护
initprot
:表示页面初始的内存保护
nsects
:当前segment有多少个sections
flags
:表示段的标志信息
-
MachOView
实例:
段数据加载并映射到内存过程:从
file offset
处加载file size
大小到虚拟内存vm addr
处,并占用虚拟内存大小为vm size
,一般情况下段名_TEXT,
_DATA
的file size = vmsize
;段名_LINKDIT 的 file size < vmsize
(动态链接申请的内存控件要大于文件大小).
LC_SEGMENT_64(_PAGEZERO): 是一个不可读、不可写、不可执行的空间,能够在空指针访问时抛出异常。这个段的大小,32位上是0x4000
,64位上0000000100000000
也就是 4G,4GB 并不是文件的真实大小,但是规定了进程地址空间的前 4GB
被映射为 不可执行、不可写和不可读
,是从0(也是NULL指针的位置)开始的,这就是为什么当读写一个 NULL 指针或更小的值时会得到一个 EXC_BAD_ACCESS
错误。
从
C、OC
语言来解释,我们平时所操作的对象其实是一个指针,指针是指向另一块存储区域的变量。当向一个对象发送消息,指向这个对象的指针需要被使用,也就是你获得了指针指向的内存地址并且可以访问该内存块的值。当系统不再为你映射该内存块时,换句话说,该内存块已经不能够被你所使用,则不可以再次访问该内存块。 如果再次访问这块内存,发生这种情况时,内核会发送一个异常(EXC
),表明您的应用程序无法访问该内存块(BAD ACCESS
LC_SEGMENT_TEXT
这是程序的代码段,该段是可读可执行,但是不可写
- 作用:代码段,其中
_stub_helper
用于关联函数bind/rebind
LC_SEGMENT_DATA
可读/可写的数据段
- 作用:函数指针,其中
_la_symbol_ptr
动态函数个数,及相对动态符号表的偏移量
LC_SEGMENT_LINKEDIT
- 作用:动态链接加载指令,支持动态链接
dyld
,该段长度覆盖符号表等数据(计算链接时程序的基址),符号表,动态符号表,字符串表段中定义的offset
偏移量都是基于_LINKEDIT
的vm_add
- 结构:与
LC_SEGMENT
一致 -
MachOView
实例:
LC_DYLD_INFO_ONLY
dyld_info_command
LC_DYLD_INFO_ONLY
和LC_DYLD_INFO
是同一个结构
这个command
是dyld
在将二进制文件装载到内存链接的时候使用的
-
rebase
:由于Macho
被加载到内存的时候首地址不是固定的,是随机分配的,针对这个做修正的; -
bind
:在链接的时候对一些符号进行绑定的,比如我们用到了UIKIT框架的api
,但是二进制中又没有这个符号,此刻就是做这个对应的工作; -
lazy_bind
:就是一开始不必要立即绑定,后面用到的时候再绑定
LC_SYMTAB
- 作用:符号表信息,解析函数名
- 结构:
struct symtab_command {
uint32_t cmd; /* LC_SYMTAB */
uint32_t cmdsize; /* sizeof(struct symtab_command) */
uint32_t symoff; /* symbol table offset */
uint32_t nsyms; /* number of symbol table entries */
uint32_t stroff; /* string table offset */
uint32_t strsize; /* string table size in bytes */
};
- 主要字段说明:
symoff
:符号表偏移量,如一个函数对应一个符号
nsyms
:符号表中表个数
stroff
:字符串表偏移量,连续的内存空间用来存储函数名
strsize
:字符串表的大小
- 查看 ```
上面说到symbol
的偏移量为115298320
也就是0x6DF5010
LC_DYSYMTAB 这个 通过这个表的 LC_MAIN 指定了 LC_LOAN_DYLIB 描述了一些动态库相关的信息 LC_RPATH 在 LC_FUNCTION_STARTS 方法是从哪里开始的 对应 LC_CODE_SINGATURE 签名相关的信息 对应 字段解释 程序运行时,动态链接的 动态函数调用过程 程序初始化: 程序运行: 函数再次调用: 关联过程如下 寻址:处理器根据指令中给出的地址信息来寻找有效地址的方式 介绍关联过程前,简单介绍几个基础知识 作用:存储了对应 根据文件的 作用:连续的存储动态函数对应符号表的索引 作用:以 作用:存储 关联过程Symbol Table
装着都是结构nlist_64
或者nlist
可以,在
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
n_strx
: 在String Table
中的索引值;n_type
: 可选的值有N_STAB、N_PEXT、N_TYPE、N_EXT
;n_sect
: section
的类型,要么就是NO_SECT
;n_desc
:n_value
: 符号对应的地址, 5E3C
就是后面那个
_la_symbol_ptr
对应的cmd
可以换算出第一个动态函数对应动态符号表的初始地址,其次存储是连续,结构长度固定的,可以通过遍历获取所有动态函数的对应的符号表索引
struct symtab_command {
uint32_t cmd; /* LC_DYSYMTAB */
uint32_t cmdsize; /* sizeof(struct dysymtab_command) */
uint32_t indirectsymoff; /* file offset to the indirect symbol table */
uint32_t nindirectsyms; /* number of indirect symbol table entries */
.....
}
indirectsymoff
:动态符号表偏移量
nindirectsyms
:动态符号表中表个数
Indirect Symbols
Indirect Symbols
包含了所有和动态库相关的符号,包括__DATA,__la_symbol_ptr
、__DATA,__nl_symbol_ptr
、__DATA,__got
,这个表有以下用处:Symbol
可以找到在符号表Symbol Table
的位置,从而在字符串表String Table
中找到名称;
通过这个表的Indirect Address
可以在__DATA,__la_symbol_ptr、__DATA,__nl_symbol_ptr、__DATA,__got
中找到方法的地址main
函数的入口地址
struct dylib {
union lc_str name; /* library's path name */
uint32_t timestamp; /* library's build time stamp */
uint32_t current_version; /* library's current version number */
uint32_t compatibility_version; /* library's compatibility vers number*/
};
struct dylib_command {
uint32_t cmd; /* LC_ID_DYLIB, LC_LOAD_{,WEAK_}DYLIB,
LC_REEXPORT_DYLIB */
uint32_t cmdsize; /* includes pathname string */
struct dylib dylib; /* the library identification */
};
Runpath
的简写,程序运行链接路径xcode
中查看
section
section
对应SEGMENTD
的DATA
数据
Section
的数据结构
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
:当前section
的名字;segname
:位于哪个segment
;addr
:当前section
在内存中的地址;size
:当前的section
所占的内存大小;offse
t:当前section的偏移量;reloff
: 暂时没找到实际的用处nreloc
:这个就是表示上面reloff
的数量;flags
: 这个是当前section
的标志位,包括sectionType
和sectionAttribute
,一个section
可以有多个属性,但是只能有一个类型,这个很好理解了,可以通过位运算分别获取类型和属性,(section->flags & SECTION_TYPE、section->flags & SECTION_ATTRIBUTES
reserved1
:保留字段,它可以表示偏移量也可以用来表示索引,一般用来表示Indirect Symbol ndex
也就是间接索引表的位置,你可以在__got、__subs
等中可以查看;reserved3
:保留字段,一般表示数量的,比如在__subssection
中就表示subs
的个数;reserved3
:保留字段了,暂时没什么用处补充
LC_DYSYMTAB
(动态符号表)是LC_SYMTAB
(符号表)的子集,LC_DYSYMTAB
存储动态符号对应在LC_SYMTAB
的序号LC_SEGMENT(_TEXT)
:存储函数代码,stub_help
取得绑定信息(即函数地址与符号的对应关系)LC_SEGMENT(_DATA)
:函数地址存放在LC_SEGMENT(_DATA)
内的la_symbol_ptr
,第二次调用,直接通过la_symbol_ptr
找到函数地址,不在需要繁琐的获取函数地址过程。la_symbol_ptr
对应section
中的reserved1
字段指明相对应的indirect symbol table
起始的index
函数a
地址记录在LC_SEGMENT(_DATA )
的la_symbol_ptr
中。初始化时,程序只知道函数a
的符号名而不知道函数的实现地址;首次调用,程序通过LC_SEGMENT(_TEXT)
的stub_help
获取绑定信息;dyld_stub_binder
来更新la_symbol_ptr
中的符号实现地址;再次调用,直接通过la_symbol_ptr
获取函数实现
函数符号名:已存在,并以nlist
结构存储,但nlist->n_value=0
(函数地址没有值)
函数实现地址:已存在,存放在mach-o
文件cmd
加载指令(SegmentName=_DATA,SectionName=__la_symbol_ptr)
函数符号名与实现地址关联(未关联):即补全nlist信息
函数首次调用:
函数符号名与实现地址进行关联,nlist->n_value
赋值函数地址
通过关联信息,通过函数符号
直接获得函数实现地址
.
__DATA,__la_symbol_ptr
)MACH-O
文件所有的动态函数实现地址
,且以连续内存地址
存储
字段解析:
Szie
:指令大小,可通过换算动态函数个数,如Szie/Szieof(void*)
Reserved1
:相对动态符号表dlsymbol_Tab
偏移量,用来换算第一个动态函数对应动符号表的地址
.
首地址
加上符号的偏移地址
,我们可以得出符号在内存中的地址
Indirect Symbol Table
地址寻址:第一个动态符号寻址 = 动态符号基址(vm_add+slide
) + Reserved1
地址值:对应符号Symbol
的索引值index
Symbol Table
nlist
结构连续存储函数符号与地址
的关联关系,nlist
包含函数实现地址,字符串偏移地址(计算函数名)
地址寻址:符号表基址(vm_add+slide
)+ index
地址值:nlist
值
String Table
MACH-O
文件所有的函数名,char*
,每个函数名以\0分隔
地址寻址:字符串表基址(vm_add+slide
)+ 偏移量(nlist-> n_un->n_str
)
地址值:函数名
Mach-O
文件下的所有LoadCommand
,寻址目标cmd
,搜索条件(SegmetName=__DATA,SectionName=__la_symbol_ptr
),目标cmd
存储了动态函数个数,及第一个动态函数相对动态符号表偏移量cmd->Szie/Szieof(void*)
;
动态符号表偏移量=cmd->reserved1
_LC_ DYSYMTAB->vm_add+slide+ reserved1
;由于动态符号表连续存储动态函数符号,可遍历所有动态函数符号地址;地址值存储对应动态函数的符号表索引+ reserved1
--->动态符号表寻址--->符号表index--->nlist
信息补全