如何从对象文件中读取Mach-O头文件?
在过去的几天里,我一直在尝试汇编,现在我已经了解了汇编和机器代码之间的关系(通过OSX上的NASM使用x86,阅读) 现在我试图了解链接器如何工作的细节,特别是想了解Mach-O对象文件的结构,从Mach-O头开始 我的问题是,您是否可以将下面的Mach-O标题映射到如何从对象文件中读取Mach-O头文件?,c,macos,executable,C,Macos,Executable,在过去的几天里,我一直在尝试汇编,现在我已经了解了汇编和机器代码之间的关系(通过OSX上的NASM使用x86,阅读) 现在我试图了解链接器如何工作的细节,特别是想了解Mach-O对象文件的结构,从Mach-O头开始 我的问题是,您是否可以将下面的Mach-O标题映射到otool命令输出(显示标题,但格式不同) 产生这个问题的一些原因包括: 它将帮助我了解“Mach-O头结构”上的文档在真实对象文件中的外观 这将简化理解的过程,所以我和其他新来者不必花很多时间或几天去想“他们是指这个还是这个”类
otool
命令输出(显示标题,但格式不同)
产生这个问题的一些原因包括:
- 它将帮助我了解“Mach-O头结构”上的文档在真实对象文件中的外观
- 这将简化理解的过程,所以我和其他新来者不必花很多时间或几天去想“他们是指这个还是这个”类型的事情。如果没有以前的经验,很难在现实世界中将一般的Mach-O文档转换为实际的对象文件
例子 从名为
example.C
的基本C文件开始:
#include <stdio.h>
int
main() {
printf("hello world");
return 0;
}
运行otool-h example.out
,它将打印:
example.out:
Mach header
magic cputype cpusubtype caps filetype ncmds sizeofcmds flags
0xfeedfacf 16777223 3 0x80 2 16 1296 0x00200085
研究 为了理解Mach-O文件格式,我发现以下资源非常有用:
#define MH_MAGIC_64 0xfeedfacf /* the 64-bit mach magic number */
#define MH_CIGAM_64 0xcffaedfe /* NXSwapInt(MH_MAGIC_64) */
...
#define CPU_TYPE_MC680x0 ((cpu_type_t) 6)
#define CPU_TYPE_X86 ((cpu_type_t) 7)
#define CPU_TYPE_I386 CPU_TYPE_X86 /* compatibility */
#define CPU_TYPE_X86_64 (CPU_TYPE_X86 | CPU_ARCH_ABI64)
Mach-O割台的结构如图所示:
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 */
};
根据这些信息,我们的目标是在example.out
对象文件中找到Mach-O头的每个部分
第一:找到“神奇”数字 根据这个例子和研究,我能够识别马赫-O头的第一部分,即“幻数”。那很酷 但这不是一个简单的过程。下面是为了弄清楚这一点必须收集的一些信息
输出的第一列显示“magic”为otool
0xfeedfacf
- 标题应该是
或MH_MAGIC
(“MAGIC”相反)。因此,在中通过谷歌找到了这些。因为我使用的是64位体系结构而不是32位,所以我选择了MH_CIGAM
(MH_MAGIC_64
)和0xfeedfacf
(MH_CIGAM_64
)0xcffaedfe
- 查看
文件和前8个十六进制代码是示例。out
,与cffa edfe
匹配!这是一个不同的格式,这让你有点不舒服,但他们是两个不同的十六进制格式,接近足以看到连接。它们也是相反的MH_CIGAM_64
0xcffaedfe // value from MH_CIGAM_64
0xfeedfacf // value from otool
cffa edfe // value in example.out
那太令人兴奋了!对于这些数字,我仍然不能完全确定我是否得出了正确的结论,但希望如此
下一步:查找cputype 现在它开始变得令人困惑。以下是需要拼凑起来才能理解的部分,但这正是我迄今为止遇到的问题:
显示otool
。就如何理解这一点给出了一些提示16777223
- 在中找到了
,并进行了多次计算以确定其值CPU\u TYPE\u X86\u 64
CPU\u TYPE\u X86\u 64的值的相关常数:
#define CPU_ARCH_ABI64 0x01000000 /* 64 bit ABI */
#define CPU_TYPE_X86 ((cpu_type_t) 7)
#define CPU_TYPE_I386 CPU_TYPE_X86 /* compatibility */
#define CPU_TYPE_X86_64 (CPU_TYPE_X86 | CPU_ARCH_ABI64)
所以基本上:
CPU_TYPE_X86_64 = 7 BITWISEOR 0x01000000 // 16777223
这个数字16777223
与otool
显示的数字匹配,很好
接下来,尝试在示例.out
中查找该数字,但该数字不存在,因为它是十进制数字。我刚刚在JavaScript中将其转换为十六进制,其中
> (16777223).toString(16)
'1000007'
因此,不确定这是否是生成十六进制数的正确方法,尤其是与Mach-O对象文件中的十六进制数匹配的方法1000007
也只有7个数字,所以不知道是应该“填充”它还是什么
无论如何,您会看到这个数字示例。out
,就在魔法数字之后:
0700 0001
嗯,他们似乎有些关联:
看起来在1000007
的末尾添加了一个0
,并且它被颠倒了
问题:
在这一点上,我想问一个问题,已经花了几个小时来达到这一点。Mach-O头文件的结构如何映射到实际的Mach-O对象文件?您能否在上面的示例.out
文件中显示标题的每个部分,并简要说明原因?MAGIC
或CIGAM
为您提供文件中使用的字节顺序提示。当您将前四个字节读取为cffaedfe
时,这意味着您应该解释little endian中的任何4个字节。这意味着你先写数字,然后写第十个,以此类推。因此,当你读07000001
时,它代表的是数字01000007,这正是你所等待的(1000007),除了前导的0。我可以建议您阅读有关字节排序的内容吗?部分让您感到困惑的是。在这种情况下,报头以平台的本机格式存储。与英特尔兼容的平台是little endian系统,这意味着多字节值的最低有效字节位于字节序列的第一位
因此,字节序列07 00 00 01
,当被解释为一个小的尾端32位值时,对应于0x01000007
要解释结构,您需要知道的另一件事是每个字段的大小。所有的uint32\t
字段都非常简单。它们是32位无符号整数
cpu\u类型\u t
和cpu\u子类型\u t
ar
0700 0001
0700 0001
1000007
cffa edfe 0700 0001 0300 0080 0200 0000
1000 0000 1005 0000 8500 2000 0000 0000
struct mach_header_64 {
uint32_t magic; cf fa ed fe -> 0xfeedfacf
cpu_type_t cputype; 07 00 00 01 -> 0x01000007
cpu_subtype_t cpusubtype; 03 00 00 80 -> 0x80000003
uint32_t filetype; 02 00 00 00 -> 0x00000002
uint32_t ncmds; 10 00 00 00 -> 0x00000010
uint32_t sizeofcmds; 10 05 00 00 -> 0x00000510
uint32_t flags; 85 00 20 00 -> 0x00200085
uint32_t reserved; 00 00 00 00 -> 0x00000000
};