(七)你好,Mach-O
1 你好,Mach-O
Mach-O
是在任何苹果操作系统上运行的编译程序所使用的文件格式。格式知识对于调试和逆向工程都很重要。因为Mach-O
的布局决定了如何将可执行文件存储在磁盘上,以及如何将可执行文件加载到内存中。
了解指令所引用的内存区域在逆向工程方面很有用,但在探索Mach-O
时,在调试方面有许多有用的隐藏功能。例如:
- 可以在运行时插入外部函数调用。
- 可以快速找到对单例内存地址的引用,而无需中断断点。
- 可以在自己的应用程序或其他框架中检查和修改变量
- 可以执行安全审核,并确保没有以字符串或方法的形式将内部的机密消息发送到生产环境中。
1.1 专业用语
在查看所有不同的C结构之前,我们先看看Mach-O
的布局。
这是每个编译可执行文件的布局。每个主程序,每个框架,每个内核扩展,所有在苹果平台上编译的东西都是这样。
Mach-O布局在每个编译过的苹果程序的开头都有一个Mach-O头,它给出了这个程序可以什么样的CPU上运行,它是什么类型的可执行文件(一个框架?一个独立的程序?)以及后面有多少加载命令。
加载命令是关于如何加载程序的指令。由C结构组成,其大小取决于加载命令的类型。
一些加载命令提供了有关如何加载segment的说明。将segment视为具有特定类型内存保护的内存区域。例如,可执行代码应该只有读取和执行权限;它不需要写入权限。
程序的其他部分,如全局变量或单例,需要读写权限,但不需要可执行权限。这意味着可执行代码和全局变量的地址将位于不同的segment中。
segment可以有0个或多个子组件,称为section。这些是由父segment提供的相同内存保护绑定的更细粒度区域。再看看上面的图表。segment命令1,指向可执行文件中包含四个segment命令的偏移量。segment命令2,指向包含0个段命令的偏移量。最后,segment命令3没有指向可执行文件中的任何偏移量。
开发人员和逆向工程人员正是对这些部分有着浓厚的兴趣。因为它们对程序都有着独特的用途。例如,有一个特定的section
存储硬编码的UTF-8
字符串,有一个特定的section
存储对静态定义的变量的引用,等等。
1.2 Mach-O header
在每个已编译的Apple可执行文件的开头都有一个特殊的struct
,表明它是否是Mach-O
可执行文件。这个struct
可以在mach-o/loader.h
中找到。这个struct
有两种变体:一种用于32位操作系统(mach_header
),另一种用于64位操作系统(mach_header_64
)。我们默认情况下讨论的都是64位系统。
让我们看看struct mach_header_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
是一个硬编码的32位无符号整数,表示这是Mach-O头
的开头。这个神奇的数字是多少?在mach-o/loader.h
头文件中再往下一点,我们会发现:
/* 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位Mach-O
可执行文件都将以0xfeedfacf
或0xcffaedfe
开头。在32位系统上,magic
为0xfeedface
,如果字节交换,则为0xcefaedfe
。这个值可以让我们快速确定文件是Mach-O
可执行文件,是32位还是64位架构编译的。
在magic
之后是cputype
和cpusubtype
,这表示该Mach-O
可执行文件允许在哪种类型的cpu上运行。filetype
告诉我们正在处理哪种类型的可执行文件。同样,查询mach-o/loader.h
会向我们展示以下定义。
#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 */
...
因此,对于主可执行文件(不是framework
),文件类型将是MH_EXECUTE
。在filetype
之后,header
的下一个最意思的地方是ncmds
和sizeofcmds
。加载命令表明了属性以及如何将可执行文件加载到内存中。
grep命令的Mach-O header
打开终端窗口,输入
xxd -l 32 /usr/bin/grep
这个命令将输出grep
可执行文件的前32个原始字节。为什么是32字节?因为在struct mach header_64声明中,有8个变量,每个变量4字节长。
00000000: cffa edfe 0700 0001 0300 0080 0200 0000 ................
00000010: 1400 0000 9807 0000 8500 2000 0000 0000 .......... .....
x86_64 Intel
系统使用一个小端架构。这意味着字节是反向的。
尽管x86_64 Intel架构是小端的,但苹果可以以大端或小端的格式存储Mach-O信息。这部分是由于历史原因,可以追溯到PPC体系结构。但是iOS不会这样做,所以每个iOS文件的Mach-O头在磁盘和内存中都是小端的。相比之下,macOS上的Mach-O header顺序可以在任何一种格式中找到,但在内存中都是小端的。后面,我们将看到macOS的CoreFoundation模块,其Mach-O头以大端格式存储。
所以前4比特
//原始
cffa edfe
//拆分为比特
cf fa ed fe
//小端读取结果
fe ed fa cf
MH_MAGIC_64
,也就是0xfeedfacf
magic
变量,这表明它是64位系统编译的。
幸运的是,xxd
命令对于小端架构有一个特殊的选项-e
。将-e
选项添加到上一个终端命令中。
00000000: feedfacf 01000007 80000003 00000002 ................
00000010: 00000014 00000798 00200085 00000000 .......... .....
让我们将所有这些值放入struct mach_header_64
:
struct mach_header_64 {
uint32_t magic = 0xfeedfacf
cpu_type_t cputype = 0x01000007
cpu_subtype_t cpusubtype = 0x80000003
uint32_t filetype = 0x00000002
uint32_t ncmds = 0x00000014
uint32_t sizeofcmds = 0x00000798
uint32_t flags = 0x00200085
uint32_t reserved = 0x00000000
};
可以清楚地看到magic
是0xfeedfacf
。在0xfeedfacf
之后,有一个0x01000007
。要计算出这个值,必须查看mach/machine.h
。
#define CPU_ARCH_ABI64 0x01000000 /* 64 bit ABI */
...
#define CPU_TYPE_X86 ((cpu_type_t) 7)
机器类型是CPU_ARCH_ABI64
与CPU_TYPE_X86
一起产生十六进制的0x01000007
。
cpusubtype
值0x80000003
由同一头文件中的CPU_SUBTYPE_LIB64
和CPU_SUBTYPE_X86_64_ALL
确定。
filetype
的值为0x00000002
,即MH_EXECUTE
。有20个加载命令(0x00000014为十六进制,大小为0x00200085)。
filetype
值0x00200085
包含一系列的选项,在mach-o/loader.h
中。
#define MH_NOUNDEFS 0x1 /* the object file has no undefined references */
#define MH_INCRLINK 0x2 /* the object file is the output of an incremental link against a base file and can't be link edited again */
#define MH_TWOLEVEL 0x80 /* the image is using two-level name space bindings */
#define MH_PIE 0x200000 /* When this bit is set, the OS will load the main executable at a random address. Only used in MH_EXECUTE filetypes. */
最后,还有一个保留值,在这里没有任何意义。
胖header
有些可执行文件实际上是一组由一个或多个可执行文件“粘合”在一起的文件。例如,许多应用程序同时编译32位和64位可执行文件,并将它们放入“胖”可执行文件中。
多个可执行文件的“粘合在一起”由一个胖header表示,它还有一个独特的magic
值,将其与Mach-O头区分开来。
紧跟在胖header之后的是structs
,它表示CPU类型和文件中胖header存储位置的偏移量。
查看mach-o/fat.h
可以得到以下结构:
#define FAT_MAGIC 0xcafebabe
#define FAT_CIGAM 0xbebafeca /* NXSwapLong(FAT_MAGIC) */
struct fat_header {
uint32_t magic; /* FAT_MAGIC or FAT_MAGIC_64 */
uint32_t nfat_arch; /* number of structs that follow */
};
...
#define FAT_MAGIC_64 0xcafebabf
#define FAT_CIGAM_64 0xbfbafeca /* NXSwapLong(FAT_MAGIC_64) */
尽管有64位的胖header,32位仍然广泛应用于64位系统。只有当可执行片的偏移量大于4MB时,才真正使用64位的胖header。nfat_arch
表明了有多少个胖结构的struct
。
下面是64位和32位版本的fat架构:
// 64
struct fat_arch_64 {
cpu_type_t cputype; /* cpu specifier (int) */
cpu_subtype_t cpusubtype; /* machine specifier (int) */
uint64_t offset; /* file offset to this object file */
uint64_t size; /* size of this object file */
uint32_t align; /* alignment as a power of 2 */
uint32_t reserved; /* reserved */
};
// 32
struct fat_arch {
cpu_type_t cputype; /* cpu specifier (int) */
cpu_subtype_t cpusubtype; /* machine specifier (int) */
uint32_t offset; /* file offset to this object file */
uint32_t size; /* size of this object file */
uint32_t align; /* alignment as a power of 2 */
};
magic
值表明了使用32位或64位结构中的哪一个。我们来看看macOS的CoreFoundation
框架。
~> file /System/Library/Frameworks/CoreFoundation.framework/CoreFoundation
/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation: Mach-O universal binary with 2 architectures: [x86_64:Mach-O 64-bit dynamically linked shared library x86_64] [x86_64h]
/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation (for architecture x86_64): Mach-O 64-bit dynamically linked shared library x86_64
/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation (for architecture x86_64h): Mach-O 64-bit dynamically linked shared library x86_64h
这说明CoreFoundation
由2个被分割的架构粘合在一起组成:x86_64
、x86_64h
。x86_64h
代表Haswell
,2013年10月引入Macbook Pro的x86_64
变体。可以通过在加载核心基础模块的程序上使用LLDB来查看加载了哪个体系结构。
~> lldb /usr/bin/plutil
(lldb) target create "/usr/bin/plutil"
Current executable set to '/usr/bin/plutil' (x86_64).
(lldb) run
Process 29834 launched: '/usr/bin/plutil' (x86_64)
No files specified.
Process 29834 exited with status = 1 (0x00000001)
(lldb) image list -h CoreFoundation
[ 0] 0x00007fff2ea5b000
(lldb) x/8wx 0x00007fff2ea5b000
0x7fff2ea5b000: 0xfeedfacf 0x01000007 0x00000008 0x00000006
0x7fff2ea5b010: 0x00000017 0x00001278 0x02000085 0x00000000
我们可以看到cpusubtype
为0x00000008
对应mach/machine.h
文件中的
#define CPU_SUBTYPE_X86_64_H ((cpu_subtype_t)8) /* Haswell feature subset */
所以CoreFoundation
的Haswell/x86_64h架构已加载到我的进程中。
我们接着看CoreFoundation
。
~> xxd -l 48 -e /System/Library/Frameworks/CoreFoundation.framework/CoreFoundation
00000000: bebafeca 02000000 07000001 03000000 ................
00000010: 00100000 20997300 0c000000 07000001 .....s. ........
00000020: 08000000 00b07300 60987300 0c000000 .....s...s.`....
0xbebafeca
(FAT_CIGAM
)是字节交换的(大端)格式。这意味着不需要-e
。为什么一个长度要用48个字节?我们来计算一下。
- 在开头有一个
struct fat_header
,包含两个4字节的成员,magic
和nfat_arch
。nfat_arch
的值为0x02000000
,但由于我们知道它是字节交换的,所以实际值为0x00000002
。总字节数为8字节。 - 紧跟在
fat_header
后面的是2个struct
fat_arch
(因为nfat_arch
是2)。fat_arch
是32位的struct
,包含5个4字节的成员。这意味着有一个额外的40字节(0字节×2),这使得总字节数达到48。
将-e
替换为字节大小参数-g
,即按4字节分组显示输出,会更容易阅读。
~> xxd -l 48 -g 4 /System/Library/Frameworks/CoreFoundation.framework/CoreFoundation
00000000: cafebabe 00000002 01000007 00000003 ................
00000010: 00001000 00739920 0000000c 01000007 .....s. ........
00000020: 00000008 0073b000 00739860 0000000c .....s...s.`....
注意:如果胖header是64位的,-g 4效果就不那么好了。因为struct fat_arch_64中有两个8字节的变量与4字节的变量混合在一起。
前两个值(0xcafebabe
和0x00000002
)是struct fat_header
,其余字节属于2个struct fat_arch
。检查第一个struct fat_arch
,我们可以看到它是针对x86_64
的,因为前面看到的cputype
0x01000007
和cpusubtype
0x00000003
。到x86_64
扇区开始的偏移量为0x00001000(4096)
,其大小为0x00739920
。
要证明x86_64
扇区在文件开头的偏移4096
处,请使用xxd
的-s
选项。
~> xxd -l 32 -e -s 4096 /System/Library/Frameworks/CoreFoundation.framework/CoreFoundation
00001000: feedfacf 01000007 00000003 00000006 ................
00001010: 00000017 00001278 02000085 00000000 ....x...........
1.3 加载命令
紧跟在Mach-O header之后的是加载命令,指明了如何将可执行文件加载到内存中的指令,以及其他细节。每个加载命令都由一系列struct
组成,每个struct
的大小和参数都不同。
但对于每个加载命令struct
,前两个变量总是一致的,即cmd
和cmdsize
。cmd
将指示加载命令的类型,cmdsize
将为您提供结构的大小。这使您可以迭代加载命令,然后按适当的cmdsize
跳转。
Mach-O的作者预见到了这种情况,并提供了一个名为struct load_command
的通用加载命令结构。
struct load_command {
uint32_t cmd; /* type of load command */
uint32_t cmdsize; /* total size of command in bytes */
};
这使我们可以将每个加载命令用这个通用结构加载命令启动。一旦知道了cmd
值,就可以将内存地址转换成适当的struct
。那么cmd
有哪些值呢?我们可以在mach-o/loader.h
中查看。
...
#define LC_SEGMENT_64 0x19 /* 64-bit segment of this file to be mapped */
#define LC_ROUTINES_64 0x1a /* 64-bit image routines */
#define LC_UUID 0x1b /* the uuid */
...
如果看到一个以LC
开头的常量,那么这就是一个加载命令。有64位和32位等效加载命令,因此请确保使用适当的命令。64位加载命令将以名称中的64
结尾。也就是说,64位系统仍然可以使用32位加载命令。例如,LC_UUID
命令在名称中不包含64,但包含在所有可执行文件中。
LC_UUID
是一个简单的加载命令。LC_UUID
提供通用唯一标识符来标识可执行文件的特定版本。这个加载命令不提供任何特定的segment
信息,因为它都包含在LC_UUID struct
中。
实际上,LC_UUID
加载命令的结构是mach-o/loader.h
中的struct uuid_command
。
/*
* The uuid load command contains a single 128-bit unique random number that
* identifies an object produced by the static link editor.
*/
struct uuid_command {
uint32_t cmd; /* LC_UUID */
uint32_t cmdsize; /* sizeof(struct uuid_command) */
uint8_t uuid[16]; /* the 128-bit uuid */
};
我们用otool -l
来看看grep
命令的UUID
。
~> otool -l /usr/bin/grep | grep LC_UUID -A2
cmd LC_UUID
cmdsize 24
uuid 8093FC14-ACCC-343F-B925-CBCD7C5FBC8C
otool
已将cmd
从0x1b
转换为LC_UUID
,将cmdsize
显示为sizeof(struct uuid_command)
(24字节),并以一种漂亮的格式显示UUID
值。
1.4 Segments段
LC_UUID
是一个简单的加载命令。因为它是自包含的,并且不向可执行文件的segment
/section
提供偏移量。下面我们来看看段segment
。
segment
是一组具有特定权限的内存。一个段可以有0个或多个名为section
的子组件。
在进入为segment
提供指令的加载命令结构之前,让我们先讨论一些通常在程序中找到的segment
。
-
__PAGEZERO segment
是内存中的一个部分,本质上是一个“无人区”。该段包含0个部分。此内存区域没有读、写或执行权限,并且在64位进程中占用较低的32位。这在开发人员使用指针出错或解引用空指针时很有用。如果发生这种情况,程序将崩溃。因为该内存区域没有读取权限(位0到2^32)。只有主可执行文件(注意不是框架)将包含一个__PAGEZERO
加载命令。 -
__TEXT segment
存储可读和可执行内存。这是应用程序代码所在的位置。如果想在内存中存储一些不应该更改的内容(如可执行代码或硬编码字符串)。那么它们将会被存放在这里。通常,文本段将有多个部分用于存储各种不可变数据。 -
__DATA segment
存储可读写内存。这是大多数OC数据(因为语言是动态的,可以在运行时更改)以及内存中可变变量的位置。通常,数据段将有多个部分用于存储各种可变数据。 -
__LINKEDIT Segment
只有可读权限。此段存储符号表、授权文件(如果不在模拟器上)、代码签名信息和其他使程序能够运行的基本信息。即使这个段有很多重要的数据打包在里面,它也没有section
。
让我们通过一个真实的例子来分析这些信息。使用LLDB并附加到模拟器的SpringBoard
为例。
~> lldb -n SpringBoard
(lldb) process attach --name "SpringBoard"
Process 16988 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = signal SIGSTOP
frame #0: 0x00007fff523b625a libsystem_kernel.dylib`mach_msg_trap + 10
libsystem_kernel.dylib`mach_msg_trap:
-> 0x7fff523b625a <+10>: retq
0x7fff523b625b <+11>: nop
libsystem_kernel.dylib`mach_msg_overwrite_trap:
0x7fff523b625c <+0>: movq %rcx, %r10
0x7fff523b625f <+3>: movl $0x1000020, %eax ; imm = 0x1000020
Target 0: (SpringBoard) stopped.
Executable module set to "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/CoreServices/SpringBoard.app/SpringBoard".
Architecture set to: x86_64h-apple-ios-.
(lldb) image dump sections SpringBoard
Sections for '/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/CoreServices/SpringBoard.app/SpringBoard' (x86_64):
SectID Type Load Address Perm File Off. File Size Flags Section Name
---------- ---------------- --------------------------------------- ---- ---------- ---------- ---------- ----------------------------
0x00000100 container [0x0000000000000000-0x0000000100000000)* --- 0x00000000 0x00000000 0x00000000 SpringBoard.__PAGEZERO
0x00000200 container [0x000000010e442000-0x000000010e448000) r-x 0x00000000 0x00006000 0x00000000 SpringBoard.__TEXT
0x00000001 code [0x000000010e442d40-0x000000010e442d4a) r-x 0x00000d40 0x0000000a 0x80000400 SpringBoard.__TEXT.__text
...
1.5 以编程方式查找segments和sections
我们创建一个命令行工具工程。打开Xcode,选择macOS
、Command Line Tool
,名字叫MachOSegments
,语言选择Swift
。
import Foundation
import MachO // 1
for i in 0..<_dyld_image_count() { // 2
let imagePath = String(validatingUTF8: _dyld_get_image_name(i))!// 3
let imageName = (imagePath as NSString).lastPathComponent
let header = _dyld_get_image_header(i)! // 4
print("\(i) \(imageName) \(header)")
}
CFRunLoopRun() // 5
- 尽管
Foundation
会间接导入MachO
模块,但为了安全和代码清晰起见,我们显式导入MachO
模块。我们马上将使用mach-o/loader.h
中找到的几个结构。 -
_dyld_image_count
函数将返回进程中所有加载模块的数量。 -
_dyld_get_image_name
函数将返回图像的完整路径。 -
_dyld_get_image_header
将返回该当前模块的Mach-O header
(mach_header
或mach_header_64
)的加载地址。 -
CFRunLoopRun
将阻止应用程序退出。因为输出完成后,我们想继续使用LLDB检查进程。
运行程序。我们将看到一个模块及其加载地址的列表显示在控制台上。这些加载地址是特定Mach-O header
在该模块内存中的位置。这几乎与在LLDB
中执行image list -b -h
完全相同。例如:
7 CoreFoundation 0x00007fff2ea5b000
(lldb) x/8wx 0x00007fff2ea5b000
0x7fff2ea5b000: 0xfeedfacf 0x01000007 0x00000008 0x00000006
0x7fff2ea5b010: 0x00000015 0x00001258 0xc2000085 0x00000000
在for
循环的最后加入以下代码
var curLoadCommandIterator = Int(bitPattern: header) + MemoryLayout<mach_header_64>.size // 1
for _ in 0..<header.pointee.ncmds {
let loadCommand = UnsafePointer<load_command>(bitPattern: curLoadCommandIterator)!.pointee // 2
if loadCommand.cmd == LC_SEGMENT_64 {
let segmentCommand = UnsafePointer<segment_command_64>(bitPattern: curLoadCommandIterator)!.pointee // 3
print("\t\(segmentCommand.segname)")
}
curLoadCommandIterator = curLoadCommandIterator + Int(loadCommand.cmdsize) // 4
}
- 在
Mach-O header
之后是加载命令。因此header
地址和mach_header_64
的大小相加,以确定加载命令的开始位置。我们这里使用的是64位设备,所以没有检测是否是32位。 - 使用Swift的
UnsafePointer
将加载命令强制转换为前面看到的“通用”load_command
结构。如果此结构包含正确的cmd
值,则将此内存地址强制转换为适当的segment_command_64 struct
。 - 这里我们知道
load_command struct
实际上应该是segment_command_64 struct
,所以我们再次使用Swift的UnsafePointer
对象进行转换。 - 在每个循环结束时,我们需要将
curLoadCommandIterator
变量增加当前loadCommand
的大小。该大小由其cmdsize
变量决定。
注意:当看到值
LC_segment_64
时,我们是如何知道要如何转换segment_command_64 struct
的?在mach-o/loader.h头中,搜索对LC_SEGMENT_64
的所有项目。有一个segment_command_64 struct
,它的cmd
对应的是LC_SEGMENT_64
。查找对加载命令的所有参考将为我们提供对应的C结构体。
运行一下。
0 MachOSegments 0x0000000100000000
(95, 95, 80, 65, 71, 69, 90, 69, 82, 79, 0, 0, 0, 0, 0, 0)
(95, 95, 84, 69, 88, 84, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
(95, 95, 68, 65, 84, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
(95, 95, 76, 73, 78, 75, 69, 68, 73, 84, 0, 0, 0, 0, 0, 0)
这是因为Swift在使用C. segmentCommand.segname
时非常糟糕。它被声明为Int8
的Swift元组,这意味着我们需要构建一个helper
函数来将这些值转换为实际可读的Swift字符串。
func convertIntTupleToString(name : Any) -> String {
var returnString = ""
let mirror = Mirror(reflecting: name)
for child in mirror.children {
guard let val = child.value as? Int8,
val != 0 else { break }
returnString.append(Character(UnicodeScalar(UInt8(val))))
}
return returnString
}
利用Mirror
对象,我们可以接受任意大小的元组并对其进行迭代。它比用16个Int8
对元祖类型的参数进行硬编码要好得多。
替换print("\t\(segmentCommand.segname)")
。
let segName = convertIntTupleToString(name: segmentCommand.segname)
print("\t\(segName)")
运行一下。这下好多了。
0 MachOSegments 0x0000000100000000
__PAGEZERO
__TEXT
__DATA
__LINKEDIT
1 libBacktraceRecording.dylib 0x0000000100112000
__TEXT
__DATA
__LINKEDIT
2 libMainThreadChecker.dylib 0x0000000100124000
__TEXT
__DATA
__LINKEDIT
...
在刚刚的print
代码下马加入以下代码。
let sectionOffset = curLoadCommandIterator + MemoryLayout<segment_command_64>.size // 1
for j in 0..<segmentCommand.nsects { // 2
let offset = MemoryLayout<section_64>.size * Int(j) // 3
let sectionCommand = UnsafePointer<section_64>(bitPattern: sectionOffset + offset)!.pointee
let sectionName = convertIntTupleToString(name: sectionCommand.sectname) // 4
print("\t\t\(sectionName)")
}
- 获取内存中第一个
struct section_64
的基址。 - 在每个
struct segment_command_64
中,都有一个成员指明了跟在它后面的section_64
命令的数量。我们用它进行遍历。 -
for
循环中,用j
乘以的struct section_64
的大小得到偏移量。计算sectionOffset + offset
就可以得到正确的section_64
地址。 -
struct section_64
还有一个sectname
变量,它是Int8
组成的另一个元组。我们将使用之前创建的同一个函数,从中获取字符串。
运行一下。
0 MachOSegments 0x0000000100000000
__PAGEZERO
__TEXT
__text
__stubs
__stub_helper
__cstring
__objc_methname
__const
__swift5_typeref
__swift5_builtin
__swift5_reflstr
__swift5_fieldmd
__swift5_types
__unwind_info
__eh_frame
__DATA
__nl_symbol_ptr
__got
__la_symbol_ptr
__mod_init_func
__const
__objc_imageinfo
__objc_selrefs
__data
__swift_hooks
__bss
__LINKEDIT
1 libBacktraceRecording.dylib 0x0000000100114000
__TEXT
__text
__stubs
__stub_helper
__cstring
__const
__info_plist
__unwind_info
__DATA
__nl_symbol_ptr
__got
__la_symbol_ptr
...
如我们所看到的,只有主可执行文件才有__PAGEZERO segment
,它有0个部分。里面有很多section
包含swift5
。因为Swift在苹果平台上没有OC就无法生存,所以在数据段中有很多OC相关的部分。