Наш магазин на eBay Наш магазин на AliExpress Наш канал в telegram

Формат файлов *.hex

Многие наверняка замечали, что большинство файлов прошивок для различных контроллеров, процессоров, микросхем памяти и прочих подобных вещей хранится либо в бинарных файлах, в которых информация записана именно в том виде и в том порядке, в котором она записана в памяти железки (т.е. это тупо дамп памяти), либо в файлах с расширением hex. Вот в этой статье мы и расскажем о том, что же это за формат и зачем он вообще нужен.

Для начала разберёмся — чем же оказались неудобны бинарники? Во-первых, в таблице ASCII некоторым кодам соответствуют непечатные символы, соответственно, бинарный файл не может быть целиком просмотрен или распечатан в текстовом режиме. Во-вторых, прошивка редко занимает целиком всю память железки, а бинарник — это, как уже было сказано выше, дамп памяти целиком. И ладно, если бы вся полезная информация всегда располагалась, например, в начале файла, пустое окончание можно было бы от бинарника просто отрезать. Но нет, чаще всего информация в бинарнике расположена не одним куском, а находится в различных частях файла и между кусками с полезной информацией расположены пустые места, которые и хранить и распечатывать особого смысла нет.

Почесав репу над этими двумя недоразумениями, джедаи из Intel придумали формат hex или Intel-hex, ставший впоследствии стандартом де-факто для записи всяких разных прошивок. Мне больше нравится говорить Intel-hex, поскольку в этом случае не возникает путаницы и сразу понятно, что речь идёт об информации в файлах *.hex, а не просто о представлении данных в шестнадцатиричном виде. Ну ладно, вернёмся к проблемам Intel и к их решению.

Проблему с непечатными символами решили очень просто, — в Intel-hex формате двоичные данные, представленные в шестнадцатиричном виде, записываются символами ASCII. Например, число «00111111» в шестнадцатиричном виде равно «3F» и в формате Intel-hex будет записано двумя символами: «3» и «F».

Не на много сложнее оказалось и решение проблемы с пустыми местами. В *.hex файлы решили писать не всё подряд, а только полезные данные (т.е. пустые места бинарника решили не писать). Но в этом случае нужно было кроме самих данных ещё и как-то указывать адреса, по которым эти данные расположены. Окей, стали писать ещё и адреса.

Далее добавили ещё данные о точке входа, ну чтоб можно было записанную таким образом программку сразу и исполнять, и придумали разбивать всю информацию на специальные блоки, называемые «записями», чтоб отличить где что записано: где данные, где адреса, где точки входа. Вот, собственно, из этих записей и состоит весь *.hex файл.

Записи бывают следующих типов:

  • Data Record (данные); для всех форматов данных
  • End of File Record (конец файла); для всех форматов данных
  • Extended Segment Address Record (расширенный адрес сегмента); для 16- или 32-битного форматов данных
  • Start Segment Address Record (начальный адрес сегмента); для 16- или 32-битного форматов данных
  • Extended Linear Address Record (расширенный линейный адрес); только 32-битного формата данных
  • Start Linear Address Record (начальный линейный адрес); только для 32-битного формата данных

Все записи имеют следующий формат:

RECORD MARK
‘:’
RECLEN LOAD OFFSET RECTYPE INFO or DATA CHKSUM
  1 byte 2 bytes 1 byte n bytes 1 byte
1 ASCII 2 ASCII 4 ASCII 2 ASCII 2*n ASCII 2 ASCII

В данном случае:

  • RECORD MARK — метка начала записи, всегда ‘:’ (в шестнадцатиричном виде 3Ah)
  • RECLEN — число байт информации или данных, следующих за полем RECTYPE. Помните, что в Intel-hex формате один байт данных записывается двумя символами ASCII. Максимальное значение этого поля — ‘FF’ (в шестнадцатиричном виде 4646h)
  • LOAD OFFSET — 16-ти битное начальное смещение данных. Поскольку это поле используется только в записях данных, то в остальных записях оно кодируется как четыре ASCII символа нуля (‘0000’ или в шестнадцатиричном виде 30303030h)
  • RECTYPE — поле, определяющее тип записи. Может принимать следующие значения:
    • ’00’ — Data Record
    • ’01’ — End of File Record
    • ’02’ — Extended Segment Address Record
    • ’03’ — Start Segment Address Record
    • ’04’ — Extended Linear Address Record
    • ’05’ — Start Linear Address Record
  • INFO or DATA — поле, содержащее 0 или больше байт, закодированных парами шестнадцатиричных символов в формате ASCII (т.е. одна пара ASCII символов — это один байт, например, пара символов ‘B’ ‘3’ соответствует байту B3h ). Интерпретация содержимого этого поля зависит от типа записи (т.е. от значения поля RECTYPE).
  • CHKSUM — контрольная сумма, которая вычисляется как дополнение по модулю 256 до нуля суммы по модулю 256 всех байт, начиная с RECLEN (включительно) до последнего байта поля INFO/DATA (включительно).
    Тем, кто подзабыл логические операции, поясню немного по другому. Нужно последовательно сложить все байты, так, чтобы результат каждого сложения занимал так же один байт, при этом пеполнение не учитывается и просто отбрасывается, — это будет операция сложения по модулю 256. Далее нужно от 256 отнять полученный байт (или можно по другому — сделать инверсию полученного байта и увеличить результат инверсии на 1) — это будет вычисление дополнения по модулю 256 до нуля.
    Естественно, в Intel-hex файле CHKSUM записывается так же, как и все остальные байты с помощью двух символов ASCII.
    CHKSUM, вычисленная подобным образом, придаёт записи интересное свойство, — теперь сумма по модулю 256 всех байт, начиная с RECLEN (включительно)до CHKSUM (включительно) даёт в результате ноль. Так проверяется целостность и безошибочность записи при считывании.

А теперь о некоторых типах записей подробнее:

Extended Linear Address Record
RECORD MARK RECLEN LOAD OFFSET RECTYPE ULBA CHKSUM
‘:’ ’02’ ‘0000’ ’04’ 2 bytes 1 byte

Эта запись используется в 32-битных прошивках для определения битов 16-31 линейного базового адреса (LBA), при этом биты 0-15 равны нулю. Сами биты 16-31 называются верхним базовым адресом (ULBA).

Абсолютное значение адреса байта данных в памяти получается добавлением LBA к смещению, вычисленному сложением поля LOAD OFFSET в последующих записях данных и индекса байта в поле DATA этих записей. Все суммирования делаются по модулю 4G, таким образом мы получаем циклический (от FFFFFFFFh происходит переход к 00000000h) 4-х гигабитный (4G=232) линейный адрес (Linear Address).

ByteAddr=(LBA+DRLO+DRI) mod 4G, где

DRLO — значение поля LOAD OFFSET в записи данных

DRI — индекс байта в поле DATA записи данных

Когда запись «Extended Linear Address» встречается в файле, — заданный с помощью неё линейный базовый адрес (LBA) действует для всех последующих записей данных, пока не встретится новая запись «Extended Linear Address». По умолчанию LBA=00000000h.

Extended Segment Address Record
RECORD MARK RECLEN LOAD OFFSET RECTYPE USBA CHKSUM
‘:’ ’02’ ‘0000’ ’02’ 2 bytes 1 byte

Эта запись используется для определения битов 4-19 базового адреса сегмента (SBA), при этом биты 0-3 равны нулю. Сами биты 4-19 называются верхним адресом сегмента (USBA).

Абсолютное значение адреса байта данных в памяти получается добавлением SBA к смещению, вычисленному сложением поля LOAD OFFSET в последующих записях данных и индекса байта в поле DATA этих записей. Сложение LOAD OFFSET и индекса выполняется по модулю 64K, таким образом мы получаем циклический (от смещения FFFFh происходит переход к 0000h) 64-х килобитный (64K=216) адрес в заданном сегменте.

ByteAddr=SBA+[(DRLO+DRI) mod 64K], где

DRLO — значение поля LOAD OFFSET в записи данных

DRI — индекс байта в поле DATA записи данных

Когда запись «Extended Segment Address» встречается в файле, — заданный с помощью неё базовый адрес сегмента (SBA) действует для всех последующих записей данных, пока не встретится новая запись «Extended Segment Address». По умолчанию базовый адрес сегмента (SBA) равен нулю.

Start Linear Address Record
RECORD MARK RECLEN LOAD OFFSET RECTYPE EIP CHKSUM
‘:’ ’04’ ‘0000’ ’05’ 4 bytes 1 byte

Эта запись используется для указания адреса, с которого начинается исполнение объектного файла. Значение поля EIP определяет адрес, который заносится в регистр EIP процессора. Отметим, что эта запись определяет только адрес точки старта кода в пределах 32-х битного линейного адресного пространства защищённого режима процессора 80386. В реальном режиме для определения точки старта должна использоваться запись Start Segment Address Record, поскольку она описывает содержимое пары регистров CS:IP, необходимое для реального режима.

Запись «Start Linear Address» может быть расположена в любом месте файла, если же такой записи нет, то загрузчик использует адрес старта по умолчанию.

Start Segment Address Record
RECORD MARK RECLEN LOAD OFFSET RECTYPE CS:IP CHKSUM
‘:’ ’04’ ‘0000’ ’03’ 4 bytes 1 byte

Эта запись используется для указания адреса, с которого начинается исполнение объектного файла. Значение поля CS:IP определяет 20-ти битный адрес, заносимый в регистры CS:IP процессора. Отметим, что эта запись определяет только адрес входа в 20-ти битном сегментированном адресном пространстве процессоров 8086/80186.

Запись «Start Segment Address» может быть расположена в любом месте файла, если же такой записи нет, то загрузчик использует адрес старта по умолчанию.

Добавить комментарий