过去几天,我一直在尝试汇编,现在了解了汇编和机器代码之间的关系(在osx上通过nasm使用x86,阅读Intel docs)。
现在,我试图了解链接器如何工作的细节,并特别想了解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
,打印:example.out:
Mach header
magic cputype cpusubtype caps filetype ncmds sizeofcmds flags
0xfeedfacf 16777223 3 0x80 2 16 1296 0x00200085
研究
为了理解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)
马赫-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
输出的第一列显示“magic”为0xfeedfacf
。Apple Mach-O docs表示标题应该是
MH_MAGIC
或MH_CIGAM
(反向为“magic”)。所以在mach-o/loader.h中通过google找到了它们。因为我使用的是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
那太令人兴奋了!仍然不完全确定我对这些数字的结论是否正确,但希望如此。
下一步:查找cputype
现在开始让人困惑了。以下是需要拼凑的部分,以使其几乎有意义,但这正是我目前所处的位置:
otool
显示16777223
。This apple stackexchange question给出了一些关于如何理解这一点的提示。在mach/machine.h中发现了
CPU_TYPE_X86_64
,必须进行多次计算才能得出其值。以下是计算
CPU_TYPE_X86_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
中找到那个数字,但它不存在,因为它是十进制数。我刚刚在javascript中将其转换为hex,其中> (16777223).toString(16)
'1000007'
所以不确定这是否是生成十六进制数的正确方法,特别是与mach-o对象文件中的十六进制数匹配的十六进制数。
otool
也只有7个数字,所以不知道你是不是应该“垫”它什么的。不管怎样,你会看到这个数字
example.out
,就在这个神奇的数字之后:0700 0001
嗯,它们似乎有些关联:
0700 0001
1000007
似乎在
1000007
的结尾处添加了一个example.out
,并且它是相反的。问题
在这一点上我想问的问题,已经花了几个小时来达到这一点。mach-o头的结构如何映射到实际的mach-o对象文件?你能在上面的
0
文件中显示头的每个部分,并简要解释原因吗? 最佳答案
让你困惑的一部分是endianness。在这种情况下,报头以平台的本机格式存储。与英特尔兼容的平台是小端系统,这意味着多字节值的最低有效字节位于字节序列的第一位。
因此,字节序列07 00 00 01
在解释为一个小的32位结束值时,对应于0x01000007
。
解释这个结构还需要知道每个字段的大小。所有uint32_t
字段都非常简单。它们是32位无符号整数。cpu_type_t
和cpu_subtype_t
都是在machine.h中定义的,您链接到的machine.h等同于integer_t
。integer_t
被定义为等同于/usr/include/mach/i386/vm_类型中的int
。h.os x是一个lp64平台,这意味着long
s和指针对体系结构敏感(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
};
关于c - 如何从目标文件读取Mach-O header ?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/27669766/