现在我想了解链接器的工作原理,特别是要了解Mach-O对象文件的结构,从Mach-O头开始.
我的问题是,你可以绘制下面的Mach-O头如何映射到otool命令输出(显示头,但它们是不同的格式)?
这个问题的一些原因包括:
>这将帮助我了解“Mach-O头的结构”上的文档如何查看真实世界的对象文件.
>这将简化理解的路径,所以我和其他新手不必花费很多时间或几天想知道“他们是这个意思,还是这个”类型的东西.在没有经验的情况下,将通用Mach-O文档转化为现实世界中的实际目标文件是很困难的.
下面我将展示一个例子和过程,我试图从一个真实的对象文件中解码Mach-O头.在下面的描述中,我尝试显示出所有出现的所有小问题或微妙问题的提示.希望这将提供一个感觉,这可以是一个新来的人很混乱.
例
从一个名为example.c的基本C文件开始:
- #include <stdio.h>
- int
- main() {
- printf("hello world");
- return 0;
- }
使用gcc example.c -o example.out编译它,它给出:
- cffa edfe 0700 0001 0300 0080 0200 0000
- 1000 0000 1005 0000 8500 2000 0000 0000
- 1900 0000 4800 0000 5f5f 5041 4745 5a45
- 524f 0000 0000 0000 0000 0000 0000 0000
- 0000 0000 0100 0000 0000 0000 0000 0000
- 0000 0000 0000 0000 0000 0000 0000 0000
- 0000 0000 0000 0000 1900 0000 2802 0000
- 5f5f 5445 5854 0000 0000 0000 0000 0000
- 0000 0000 0100 0000 0010 0000 0000 0000
- 0000 0000 0000 0000 0010 0000 0000 0000
- 0700 0000 0500 0000 0600 0000 0000 0000
- 5f5f 7465 7874 0000 0000 0000 0000 0000
- 5f5f 5445 5854 0000 0000 0000 0000 0000
- 400f 0000 0100 0000 2d00 0000 0000 0000
- 400f 0000 0400 0000 0000 0000 0000 0000
- 0004 0080 0000 0000 0000 0000 0000 0000
- 5f5f 7374 7562 7300 0000 0000 0000 0000
- 5f5f 5445 5854 0000 0000 0000 0000 0000
- 6e0f 0000 0100 0000 0600 0000 0000 0000
- 6e0f 0000 0100 0000 0000 0000 0000 0000
- 0804 0080 0000 0000 0600 0000 0000 0000
- 5f5f 7374 7562 5f68 656c 7065 7200 0000
- ... 531 total lines of this
运行otool -h example.out,打印:
研究
要了解Mach-O文件格式,我发现这些资源有帮助:
> https://developer.apple.com/library/mac/documentation/DeveloperTools/Conceptual/MachORuntime/index.html#//apple_ref/doc/uid/TP40000895
> https://developer.apple.com/library/mac/documentation/DeveloperTools/Conceptual/MachORuntime/index.html
> https://www.mikeash.com/pyblog/friday-qa-2012-11-30-lets-build-a-mach-o-executable.html
> http://www.opensource.apple.com/source/xnu/xnu-1456.1.26/EXTERNAL_HEADERS/mach-o/loader.h
> http://www.opensource.apple.com/source/dtrace/dtrace-78/head/arch.h
> http://www.opensource.apple.com/source/xnu/xnu-792.13.8/osfmk/mach/machine.h
来自opensource.apple.com的最后3位包含所有常量,例如:
- #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头的每一个.
第一:找到“魔法”号码
考虑到这个例子和研究,我能够确定Mach-O头部的第一部分,即“魔术数字”.那很酷
但这不是一个简单的过程.以下是必须收集的信息数据.
> otool输出的第一列显示“魔术”为0xFeedfacf.
> Apple Mach-O docs说标题应该是MH_MAGIC或MH_CIGAM(“魔术”相反).所以通过谷歌在mach-o/loader.h发现.由于我使用64位架构而不是32位,去与MH_MAGIC_64(0xFeedfacf)和MH_CIGAM_64(0xcffaedfe).
>通过example.out文件查看,前8个十六进制代码是cffa edfe,与MH_CIGAM_64匹配!它是一种不同的格式,它会让你失望一点,但它们是两个不同的十六进制格式,足够接近观看连接.他们也被扭转.
这里有3个数字,足以让人弄清楚魔术数字是多少?
- 0xcffaedfe // value from MH_CIGAM_64
- 0xFeedfacf // value from otool
- cffa edfe // value in example.out
所以这是令人兴奋的!仍然不完全确定我是否得出关于这些数字的正确结论,但希望如此.
现在开始变得混乱了这是需要整理在一起的几件事情,但这是我到目前为止的地方:
> otool显示16777223. This apple stackexchange question给了一些提示如何理解这一点.
>在mach/machine.h发现cpu_TYPE_X86_64,并且不得不做几个计算来弄清楚它的价值.
这里是相关的常量来计算cpu_TYPE_X86_64的值:
所以基本上
那个数字16777223匹配otool显示的,很好!
接下来,尝试在example.out中找到该数字,但它不存在,因为它是一个十进制数.我只是把它转换成十六进制的JavaScript,在哪里
- > (16777223).toString(16)
- '1000007'
所以不知道这是否是生成十六进制数字的正确方式,特别是匹配Mach-O对象文件中的十六进制数字的方法. 1000007也只有7个数字,所以不知道你应该“垫”它还是什么.
无论如何,你看到这个数字example.out,就在魔术数字之后:
- 0700 0001
嗯,他们似乎有些相关:
- 0700 0001
- 1000007
看起来,在1000007的末尾添加了一个0,并且它被颠倒了.
题
在这一点上,我想问这个问题,已经花了几个小时才能到达这一点. Mach-O标题的结构如何映射到实际的Mach-O对象文件?你可以在上面的example.out文件中显示标题的每个部分,并简要解释一下为什么?
解决方法
所以,字节序列07 00 00 01当被解释为小端32位值时,对应于0x01000007.
您需要知道的其他解释结构是每个字段的大小.所有的uint32_t字段都很简单.它们是32位无符号整数.
在您连接的machine.h中定义了cpu_type_t和cpu_subtype_t等于integer_t. integer_t被定义为与/usr/include/mach/i386/vm_types.h中的int相当. OS X是一个LP64平台,这意味着longs和指针对架构(32-对64位)敏感,但int不是.它总是32位.
所以,所有的字段大小是32位或4字节.由于有8个字段,共32个字节.
从你的原始hexdump,这是与标题对应的部分:
- 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
- };