Я провел последние несколько дней, экспериментируя с сборкой, и теперь понимаю взаимосвязь между сборочным и машинным кодом (используя x86 через NASM на OSX, читая документы Intel).
Теперь я пытаюсь понять детали работы компоновщика и особенно хочу понять структуру объектных файлов 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, я нашел эти ресурсы полезными:
- 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
Те последние 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
( "magic" в обратном порядке), Таким образом, найти их через google в 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
Так что интересно! Все еще не совсем уверен, что я прихожу к правильному выводу об этих цифрах, но надеюсь на это.
Далее: поиск cputype
Теперь он начинает запутываться. Вот фрагменты, которые нужно было собрать, чтобы почти понять это, но это то место, где я застрял до сих пор:
-
otool
показывает16777223
. Этот вопрос о стеке apple дал несколько советов о том, как это понять. - Найден
CPU_TYPE_X86_64
в mach/machine.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
, nice!
Затем попытался найти это число в example.out
, но он не существует, потому что это десятичное число. Я просто преобразовал это в hex в JavaScript, где
> (16777223).toString(16)
'1000007'
Не уверен, что это правильный способ генерации шестнадцатеричного числа, особенно тот, который будет соответствовать шестнадцатеричным числам в объектном файле Mach-O. 1000007
- это только 7 чисел, поэтому не знаю, хотите ли вы "набить" его или что-то еще.
В любом случае, вы видите это число example.out
, сразу после магического числа:
0700 0001
Хм, они кажутся несколько связанными:
0700 0001
1000007
Похоже, что в конец 1000007
добавлен 0
и что он был отменен.
Вопрос
На этом этапе я хотел задать вопрос, уже потратив несколько часов, чтобы добраться до этого момента. Как структура заголовка Mach-O сопоставляется с фактическим файлом объекта Mach-O? Можете ли вы показать, как каждая часть заголовка отображается в файле example.out
выше, с кратким объяснением, почему?