简介
MachO文件是mac平台上一类文件的简称,它的类型有以下种类,可以在#import <mach-o/loader.h>
文件中找到
1 | #define MH_OBJECT 0x1 /* relocatable object file */ |
列举一些常见的类型
文件类型 | 含义 |
---|---|
MH_OBJECT | 目标文件,平时.o结尾的文件 |
MH_EXECUTE | 可执行文件,我们平时编译后的包中的执行文件 |
MH_DYLIB | 一些动态库,该文件夹下很多/usr/lib/xxx.dylib |
MH_DSYM | 符号文件,编译成功后XXX.app.dSYM |
一、MachO的分类
这里准备了一些macho文件,分别通过MachOView
工具来看看,如果你的MachOView
会崩溃,点击下载这个
分别用MachOView
打开如下
二、MachO的组成
每个Macho文件都会有个Header
对这个Macho
进行整体描述,这个header
根据你打包的选择的架构又分为Fat Header
和 Mach Header
,先介绍下如何生成这2个文件类型
Architectures
和valid Architectures
的交集就是最后打的包的架构,Architectures
后面是标准的架构,我把项目设置为iOS8
选择真机,将build
改成release
,会编译出一开始的展示的MochO_arm_fat
文件放到MachOView
中如图
然后选择模拟器将build
改成debug
,build
会出现MochO_x86
,结果如图
2.1 fat_header
2.1.1 fat_header
结构
1 | struct fat_header { |
magic
: 描述文件类型 值分为2组分别为FAT_CIGAM(0xbebafeca)
、FAT_MAGIC(0xcafebabe)
和FAT_CIGAM_64(0xbfbafeca)
、FAT_MAGIC_64(0xcafebabf)
,值也就是大小端的区别;nfat_arch
:描述当前fat
有多少个架构。
2.1.2 fat_arch
结构
2.1.1
说到fat_header
的nfat_arch
会描述有多少个架构,其实架构的类型就是fat_arch
类型的,结构如下
1 | struct fat_arch { |
cputype
:说明CPU的类型一般有CPU_TYPE_X86
、CPU_TYPE_X86_64
、CPU_TYPE_ARM64
、CPU_TYPE_ARM
;cpusubtype
: 对cpu类型的具体划分一般有CPU_SUBTYPE_I386_ALL
、CPU_SUBTYPE_X86_ALL
、CPU_SUBTYPE_ARM_V7
、CPU_SUBTYPE_ARM_V7S
;offset
: 当前架构的偏移量;size
:当前架构的大小;align
:对齐大小。
通过MachOView
来看下MochO_arm_fat
的fat_header
也可以用otool
查看
otool -f /Users/fangshufeng/Desktop/thirdPart/macho/MochO/MochO/exccute/
1 | MochO_arm_fat |
上面可以看到
架构architecture 0
的偏移地址是16384
,也就是16进制的0x4000
;
架构architecture 1
的偏移地址是131072
,也就是16进制的0x20000
;
我们来看下是否正确
正好是要的值
2.2 mac_header
对于不是fat
的Macho
文件一开始的内容就是mac_header
2.2.1 结构
1 | struct mach_header { |
magic
、cputype
、cpusubtype
:同上;filetype
:Macho文件的类型,也就是文章一开始列举的类型;ncmds
:接下来load commands
的数量,后面会介绍;sizeofcmds
:接下来load commands
的大小,后面会介绍;flags
:文件的表示信息,值如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16/* Constants for the flags field of the mach_header */
截图如下
同样使用otool
也是可以的
1 | ➜ MochO otool -h exccute/MochO_x86 |
上面显示该文件的类型为MH_EXECUTE
,Load Commands
的数量为21个,数一下确实是21个。
也就是说mach_header
更多的是对load Commands
的描述
2.3 load Commands
load Commands
是由很多的LC_Type
组成的,而LC_Type
有很多种,可在文件loader.h
文件中查看,这边就列出前几种
1 | /* Constants for the cmd field of all load commands, the type */ |
而每个LC_Type
都会有一个头部load_command
结构如下
1 | struct load_command { |
是的就是对segment
的描述
cmd
: 当前load command的类型;cmdsize
:load command的大小。
也就是下面这张图
2.3.1 LC_SEGMENT
为了方便管理,程序在内存中是分段管理的,先来看看LC_Type
其中一种LC_SEGMENT
的结构
1 | struct segment_command { /* for 32-bit architectures */ |
cmd
和cmdsize
: 就是上面的load_command
类型;segname
:就是当前segment
的名称vmaddr
:在虚拟内存中的地址,这个很重要的,以后会介绍到vmsize
:在虚拟内存中所占用的大小;fileoff
:在文件中的偏移量;filesize
:在文件中的大小,注意和vmaddr
、vmsize
区别maxprot
:表示页面所需要的最高内存保护;initprot
:表示页面初始的内存保护;nsects
: 当前segment
有多少个sections
flags
:表示段的标志信息。
常见的LC_SEGMENT
有以下几种
1 |
2.3.1.1 __PAGEZERO
这是一个不可读、不可写、不可执行的空间,能够在空指针访问时抛出异常。这个段的大小,32位上是 0x4000,64位上0000000100000000
也就是 4G,4GB 并不是文件的真实大小,但是规定了进程地址空间的前 4GB 被映射为 不可执行、不可写和不可读,是从0(也是NULL指针的位置)开始的,这就是为什么当读写一个 NULL 指针或更小的值时会得到一个 EXC_BAD_ACCESS 错误。
内容如下
2.3.1.2 __TEXT
这是程序的代码段,该段是可读可执行,但是不可写。常见的section
如下
2.3.1.3 __DATA
数据段,包含了可读写数据。常见的section
如下
2.3.2 LC_DYLD_INFO_ONLY
LC_DYLD_INFO_ONLY
和LC_DYLD_INFO
是同一个结构
1 | struct dyld_info_command { |
这个command
是dyld
在将二进制文件装载到内存链接的时候使用的
- 前面2个不介绍了,
rebase
:由于Macho
被加载到内存的时候首地址不是固定的,是随机分配的,针对这个做修正的; bind
:在链接的时候对一些符号进行绑定的,比如我们用到了UIKIT
框架的api,但是二进制中又没有这个符号,此刻就是做这个对应的工作;lazy_bind
:就是一开始不必要立即绑定,后面用到的时候再绑定。
内容如下
可以通过偏移量找到对应的地方
2.3.3 LC_SYMTAB
这里面记录着所有的符号信息
1 | struct symtab_command { |
symoff
:符号表的偏移量;nsyms
:符号表的元素的数量;stroff
:符号的字符串的偏移量;strsize
:所占的字节数。
2.3.3.1 查看Symbol Table
上面说到symbol
的偏移量为21568
也就是0x0000 5440
选择如下
看到下图
Symbol Table
装着都是结构nlist_64
或者nlist
可以see <mach-o/nlist.h>
1 | struct nlist_64 { |
n_strx
: 在String Table
中的索引值;n_type
: 可选的值有N_STAB
、N_PEXT
、N_TYPE
、N_EXT
;n_sect
:section
的类型,要么就是NO_SECT
;n_desc
:n_value
: 符号对应的地址
这里以AppDelegate
的符号_OBJC_CLASS_$_AppDelegate
来演示
根据图得出以下信息:
n_sect
显示位于__DATA,__objct_data
;value
显示地址为0x100003F48
。
跳到对应的地址看到确实是我们要找的:
具体的数据如图
读出以下信息:
_OBJC_CLASS_$_AppDelegate
的isa是_OBJC_METACLASS_$_AppDelegate;- 父类是
UIResponder
- 此时的缓存是空
- 缓存的数量为0
- 当前类相关的信息在地址
0x100003DC0
;
到这是不是觉得特别熟悉呢,我们把AppDelegate
的代码用c++
看下
1 |
|
可以看到
1 | extern "C" __declspec(dllexport) struct _class_t OBJC_CLASS_$_AppDelegate __attribute__ ((used, section ("__DATA,__objc_data"))) = { |
和我们的工具看到的不谋而合
我们继续跳到0x100003DC0
看下
确实看到了我们要的信息,而右边的又是什么呢,由刚才的C++
代码可以知道0x100003DC0
就是_OBJC_CLASS_RO_$_AppDelegate
的地址
看下_class_ro_t
的结构
1 | struct _class_ro_t { |
这个结构也即是我们截图的内容真好匹配
1 |
|
所以到这里我们可以知道该类的所有信息了,比如我们想看看它得方法列表,由截图可以知道地址为0x0000000100003C60
每一个item对应的就是_objc_method
1 | struct _objc_method { |
比如我们现在想到拿到方法- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
的名称,看图可以知道方法的字符串地址为0000000100001C90
果然找到了,其它的信息可以自己尝试去找找。更多关于类方面的知识可以看下我之前的oc主题相关的文章,现在文章已经很长了,不说这个了。
2.3.3.2 查看String Table
2.3.4 LC_DYSYMTAB
这里记录着所有的动态链接时需要的符号信息
同样我们找到00005C10
还有很多没有截取了,比如这些_NSFullUserName
这些在链接的时候回去动态解析这些符号表
这个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
中找到方法的地址
fishook就用到了这个,后面我会单独来介绍这个库的实现原理
2.3.5 LC_MAIN
指定了main
函数的入口地址
加载到内存后增加头部地址就是函数的真正地址了
2.3.6 LC_LOAN_DYLIB
描述了一些动态库相关的信息
1 |
|
2.3.7 LC_RPATH
Runpath
的简写
程序运行链接路径
xcode中可以看到
2.3.8 LC_FUNCTION_STARTS
方法是从哪里开始的
和解析出来的顺序也是一致的
2.3.9 LC_CODE_SINGATURE
签名相关的信息
找到地方0x67E0
关于签名的后面打算单独写一篇
2.4 section
结构如下
1 | struct section_64 { /* for 64-bit architectures */ |
这里只列举了section64
位的,section
可以自己在#include <mach-o/loader.h>
查看
sectname
:当前section
的名字;segname
:位于哪个segment
;addr
:当前section
在内存中的地址;size
:当前的section
所占的内存大小;offset
:当前section
的偏移量;reloff
: 抱歉暂时没找到实际的用处,不做解释,以免误人子弟;nreloc
:这个就是表示上面reloff
的数量;flags
: 这个是当前section
的标志位,包括sectionType
和sectionAttribute
,一个section
可以有多个属性,但是只能有一个类型,这个很好理解了,可以通过位运算分别获取类型和属性,(section->flags & SECTION_TYPE
、section->flags & SECTION_ATTRIBUTES
reserved1
:这是个保留字段,它可以表示偏移量也可以用来表示索引,一般用来表示Indirect Symbol Index
也就是间接索引表的位置,你可以在__got
、__subs
等中可以查看;reserved3
:也是个保留字段,一般表示数量的,比如在__subs
section中就表示subs
的个数;reserved3
:这个真是个保留字段了,暂时没什么用处
随意截取一个section
看下结构吧
本篇完。