Когда я впервые начал изучать форматы двоичных файлов, я столкнулся с полным отсутствием в Интернете понятных для человека объяснений. Все ресурсы, которые мне попадались, были полны незнакомых технических терминов, которые заставляли меня чувствовать себя так, будто я читаю учебник, написанный для человека с удвоенным IQ. Отсюда и эта статья. Это краткое изложение моего исследования, объясненное на английском языке, чтобы вы могли понять его без тех проблем, которые были у меня. А теперь, без дальнейших проволочек, давайте приступим к обучению!
Все эти запутанные технические термины
Как я уже говорил, в ходе исследования я столкнулся с рядом технических терминов, о которых раньше никогда не слышал. В то время они не имели для меня никакого смысла, и их изучение заняло еще больше времени. Вот список этих терминов, объясненных в дружеской манере:
- Двоичная система — двоичная система счисления с основанием
2
. Это означает, что в ней используются только цифры0
и1
. - Бит — наименьшая единица данных в компьютере, состоящая из одной двоичной цифры (
0
или1
). - Байт — байт является следующей единицей данных в компьютере. Байт состоит из
8
отдельных битов. - Знаковое целое число — знаковое целое число — это целое число (целое число), которое связано со знаком, определяющим, является ли это число положительным или отрицательным. По сути, это целое число со знаком
+
или-
. - Беззнаковое целое число — беззнаковое целое число — это целое число, которое не связано со знаком (
+
или-
) и всегда считается положительным значением.
Что такое файл?
Файл — это набор битов, который хранится в памяти компьютера. Файлы обычно разделяются на байты и измеряются количеством байт, которые они содержат (Кило*байт*, Мега*байт*, Гига*байт* и т.д.).
Типы данных
Прежде чем приступить к работе с двоичным кодированием файлов, необходимо понять, какие типы данных могут храниться в файле. В файле есть два основных типа данных: целые числа и строки.
Целые числа
Целые числа делятся на два подтипа, подписанные и беззнаковые, как я уже объяснял в начале статьи.
Беззнаковые целые числа, благодаря отсутствию знака, могут хранить число, максимальное значение которого примерно в два раза больше, чем у знакового целого числа.
Примечание: почти в любой ситуации программирования беззнаковое целое число сокращается как
Uint
.
Знаковые целые числа (Int
s) практически полностью аналогичны, за исключением уменьшенного максимального значения.
И Uint
, и Int
встречаются в двоичных файлах разных размеров и обычно называются в зависимости от того, сколько битов они используют для хранения своего значения. Например, Uint8
и Int8
оба используют 8
бит в файле, Uint16
и Int16
используют 16
бит, и так далее. Эти числа имеют различные пределы значений в зависимости от количества используемых ими битов:
Тип | Минимальное значение | Максимальное значение |
---|---|---|
Uint8 |
0 | 255 |
Int8 |
-128 | 127 |
Uint16 |
0 | 65,535 |
Int16 |
-32,768 | 32,767 |
Uint32 |
0 | 4,294,967,295 |
Int32 |
-2,147,483,648 | 2,147,483,647 |
Uint64 |
0 | 18,446,744,073,709,551,615 |
Int64 |
-9,223,372,036,854,775,808 | 9,223,372,036,854,775,807 |
Однако это не единственные размеры, которые могут быть использованы, и иногда вы можете столкнуться с некоторыми странными размерами. Вот несколько случаев, с которыми я столкнулся:
Тип | Минимальное значение | Максимальное значение |
---|---|---|
Uint4 |
0 | 15 |
Uint24 |
0 | 8,388,608 |
Строки
Строки, как вы, возможно, уже знаете, представляют собой упорядоченные наборы текстовых символов. Когда строка хранится в двоичном файле, она преобразуется в набор байтов, причем в каждом байте хранится один символ utf-8
в виде идентификатора символа Uint8
.
Endianness
Эндианальность — одна из наиболее сложных для понимания концепций, поэтому я собираюсь использовать здесь подход, основанный на примерах. Допустим, у меня есть переменная Uint16
со значением 255
. Для того чтобы сохранить эту переменную в двоичном файле, ее нужно сначала преобразовать в набор байтов. Поскольку наша переменная Uint16
занимает 16 бит пространства, для ее хранения в файле требуется два байта. Но … Какой из этих двух байтов стоит первым в файле? Именно здесь в игру вступает эндианальность.
Старшие и младшие байты
Давайте преобразуем нашу переменную Uint16
в двоичную, обязательно сохраняя 16 цифр: 0000000011111111
. Обратите внимание, что число равномерно разделено на 0
и 1
. Каждая из этих групп представляет собой один байт. Байт слева (0
s) является старшим байтом, то есть он идет первым в обычном порядке значений. Байт справа (1
s) называется младшим байтом. По мере того как вы проходите по байтам слева направо, байты переходят от более высокого к более низкому «порядку». Таким образом, в этом числе левый байт имеет более высокий порядок, чем правый.
Существует два типа «конечности», в которой может быть закодировано число: little endian и big endian. В числе, закодированном в формате little endian, байты упорядочены от старшего к младшему, то есть самый младший байт идет в конце. Числа, закодированные в формате big endian, упорядочены в обратном порядке по сравнению с числами little endian, то есть самый старший («bigest») байт идет в конце.
Возвращаясь к нашей переменной Uint16
, вот как будет выглядеть число в файле:
Endianness | Байт 0 | байт 1 |
---|---|---|
маленький | 00000000 | 11111111 |
большой | 11111111 | 00000000 |
Общий формат файла
Далее мы можем обсуждать только общую картину форматов двоичных файлов, поэтому имейте в виду, что любой формат файла, который вы видите, не обязательно должен соответствовать тому, что здесь обсуждается.
В большинстве двоичных файлов данные обычно разделены на два основных типа секций: заголовок файла и блоки двоичных данных.
Заголовки файлов
Заголовок в двоичном файле обычно представляет собой набор байтов, занимающих фиксированное положение в начале файла. Заголовок файла обычно содержит данные о версии формата файла, в котором он был закодирован, а также некоторые другие данные о содержимом файла. Однако заголовки не обязательно должны быть одинаковой длины в каждом файле одного типа. Иногда заголовок должен содержать данные переменной длины, например, строку, предоставляемую программой. Строки не всегда имеют одинаковую длину, поэтому в заголовках обычно указывается общая длина заголовка и постоянная позиция для начала строки. Любая программа, разбирающая файл, начинает читать строку с постоянного индекса и продолжает до тех пор, пока не прочитает всю длину заголовка.
Блоки данных
Остальная часть файла после заголовка обычно отводится под блоки данных. Каждый блок данных может быть фиксированного или переменного размера и обычно имеет свой собственный заголовок, который сообщает программе, разбирающей файл, как использовать данные внутри блока, а также длину блока, если он переменного размера.
Надеюсь, я успешно объяснил, как работает форматирование двоичных файлов. Если у вас возникли вопросы или вы увидели проблемы с какой-либо частью этой статьи, не стесняйтесь оставить комментарий ниже, и я исправлю это как можно скорее. Счастливого хакинга!
Чувствуете щедрость?
Я 17-летний веб-разработчик-самоучка, пытающийся заработать на жизнь, находясь под гнетом деспотичных родителей и пытаясь найти способ оплатить колледж, в то время как мне не разрешают иметь работу. Я буду благодарен за любые пожертвования.
Не собираетесь жертвовать?
Даже если вы не будете жертвовать, просто поставьте лайк этому посту или поделитесь им с теми, кому он может быть полезен, — это огромная помощь.