Как прочитать заголовок Mach-O из объектного файла?
я провел последние несколько дней, экспериментируя со сборкой, и теперь понимаю связь между сборкой и машинным кодом (используя x86 через NASM на OSX, читая Intel docs).
теперь я пытаюсь понять детали того, как работает компоновщик, и, в частности, хочу понять структуру объектных файлов Mach-O, начиная с заголовков Mach-O.
мой вопрос в том, Можете ли вы отобразить, как заголовки Mach-O ниже сопоставляются с otool
вывод команды (которая отображает заголовки, но они находятся в другом формате)?
некоторые причины для этого вопроса включает:
- это поможет мне увидеть, как документы по "структуре заголовков Mach-O" выглядят в реальных объектных файлах.
- это упростит путь к пониманию, поэтому мне и другим новичкам не придется тратить много часов или дней, задаваясь вопросом "они имеют в виду этой или этой" тип вещь. Трудно без предыдущего опыта мысленно перевести общую документацию Mach-O в реальный объектный файл в реальном мире.
ниже я показываю пример и процесс, который я прошел, чтобы попытаться декодировать заголовок Mach-O из реального объектного файла. Во всех описаниях ниже я пытаюсь показать намеки на все маленькие / тонкие вопросы, которые возникают. Надеюсь, это даст представление о том, как это может быть очень запутанным в новичок.
пример
начиная с базового файла C под названием example.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, я нашел эти ресурсы последние 3 из opensource.apple.com содержат все константы, такие как:
#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 */
};
учитывая эту информацию, целью было найти каждую из этих частей заголовка Mach-O в example.out
объектный файл.
во-первых: найти "волшебное" число
учитывая этот пример и исследования, я смог определить первую часть заголовка Mach-O, "магическое число". Что был прохладный.
- первая колонка
otool
вывод показывает, что "magic" должен быть0xfeedfacf
. - на Apple Mach-O docs сказать, что заголовок должен быть либо
MH_MAGIC
илиMH_CIGAM
("магия" в обратном). Так что нашел их через google в Mach-o/loader.h. Поскольку я использую 64-разрядную архитектуру и не 32-бит, пошел сMH_MAGIC_64
(0xfeedfacf
) иMH_CIGAM_64
(0xcffaedfe
). - посмотрел
example.out
файл и первые 8 шестнадцатеричных кодов былиcffa edfe
, что соответствуетMH_CIGAM_64
! Это в другом формате, который выбрасывает вас немного, но это 2 разных формата hex, которые достаточно близки, чтобы увидеть соединение. Они также обращены вспять.
вот 3 числа, которых было достаточно, чтобы понять, что такое магическое число есть:
0xcffaedfe // value from MH_CIGAM_64
0xfeedfacf // value from otool
cffa edfe // value in example.out
так это здорово! Все еще не совсем уверен, что я прихожу к правильному выводу об этих числах, но надеюсь на это.
далее: поиск cputype
теперь это начинает запутываться. Вот кусочки, которые нужно было собрать вместе, чтобы почти имеет смысл, но это то, где я застрял до сих пор:
-
otool
показывает16777223
. этот вопрос apple stackexchange дал несколько советов, как это понять. - нашел
CPU_TYPE_X86_64
на mach / машина.h, и пришлось сделать несколько вычислений, чтобы выяснить его значение.
вот соответствующие константы, чтобы сделать вычислить значение 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
соответствует тому, что отображается otool
, приятно!
далее, попытался найти это число в example.out
, но это не существовать, потому что это десятичное число. Я только что преобразовал это в hex в JavaScript, где
> (16777223).toString(16)
'1000007'
поэтому не уверен, что это правильно способ создания шестнадцатеричного числа, особенно того, который будет соответствовать шестнадцатеричным числам в объектном файле Mach-O. 1000007
is только 7 номеров тоже, поэтому не знаю, должны ли вы "прокладывать" его или что-то еще.
в любом случае, вы видите это число example.out
, сразу после магии номер:
0700 0001
Хм, они вроде несколько по теме:
0700 0001
1000007
похоже было!--36--> добавить в конец 1000007
, и что это было наоборот.
вопрос
в этот момент я хотел задать вопрос, уже потратил несколько часов, чтобы добраться до этой точки. Как структура заголовка Mach-O сопоставляется с фактическим файлом объекта Mach-O? Можете ли вы показать, как каждая часть заголовка появляется в example.out
файл выше, с кратким пояснением, почему?
2 ответов
часть того, что вас смущает, это endianness. В этом случае заголовок сохраняется в собственном формате для платформы. Intel-совместимые платформы-это системы с малым концом, то есть наименее значимый байт многобайтового значения является первым в байтовой последовательности.
Итак, последовательность байт 07 00 00 01
, при интерпретации как 32-разрядное значение с малым концом, соответствует 0x01000007
.
другая вещь, которую необходимо знать, чтобы интерпретировать структуру размер каждого поля. Все uint32_t
поля довольно просты. Это 32-разрядные целые числа без знака.
и cpu_type_t
и cpu_subtype_t
определены в машине.h, что вы связали, чтобы быть эквивалентным integer_t
. integer_t
определяется как эквивалентное int
in/usr/include/mach/i386 / vm_types.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
};
MAGIC
или CIGAM
дает вам подсказки о порядке байтов, используемых в файле. Когда вы читаете первые четыре байта как cffaedfe
это означает, что вы должны интерпретировать любые 4 байта в little endian. Это означает, что сначала вы пишете числа с единицами, затем десятыми и т. д. Итак, когда вы читаете 07000001
Он представляет собой число 01000007, которое именно то, что вы ждали (1000007), кроме ведущего 0. Могу ли я предложить вам прочитать о порядке байтов?