Что такое hex файл
Формат файлов *.hex
Многие наверняка замечали, что большинство файлов прошивок для различных контроллеров, процессоров, микросхем памяти и прочих подобных вещей хранится либо в бинарных файлах, в которых информация записана именно в том виде и в том порядке, в котором она записана в памяти железки (т.е. это тупо дамп памяти), либо в файлах с расширением hex. Вот в этой статье мы и расскажем о том, что же это за формат и зачем он вообще нужен.
Для начала разберёмся — чем же оказались неудобны бинарники? Во-первых, в таблице ASCII некоторым кодам соответствуют непечатные символы, соответственно, бинарный файл не может быть целиком просмотрен или распечатан в текстовом режиме. Во-вторых, прошивка редко занимает целиком всю память железки, а бинарник — это, как уже было сказано выше, дамп памяти целиком. И ладно, если бы вся полезная информация всегда располагалась, например, в начале файла, пустое окончание можно было бы от бинарника просто отрезать. Но нет, чаще всего информация в бинарнике расположена не одним куском, а находится в различных частях файла и между кусками с полезной информацией расположены пустые места, которые и хранить и распечатывать особого смысла нет.
Почесав репу над этими двумя недоразумениями, джедаи из Intel придумали формат hex или Intel-hex, ставший впоследствии стандартом де-факто для записи всяких разных прошивок. Мне больше нравится говорить Intel-hex, поскольку в этом случае не возникает путаницы и сразу понятно, что речь идёт об информации в файлах *.hex, а не просто о представлении данных в шестнадцатиричном виде. Ну ладно, вернёмся к проблемам Intel и к их решению.
Проблему с непечатными символами решили очень просто, — в Intel-hex формате двоичные данные, представленные в шестнадцатиричном виде, записываются символами ASCII. Например, число «00111111» в шестнадцатиричном виде равно «3F» и в формате Intel-hex будет записано двумя символами: «3» и «F».
Не на много сложнее оказалось и решение проблемы с пустыми местами. В *.hex файлы решили писать не всё подряд, а только полезные данные (т.е. пустые места бинарника решили не писать). Но в этом случае нужно было кроме самих данных ещё и как-то указывать адреса, по которым эти данные расположены. Окей, стали писать ещё и адреса.
Далее добавили ещё данные о точке входа, ну чтоб можно было записанную таким образом программку сразу и исполнять, и придумали разбивать всю информацию на специальные блоки, называемые «записями», чтоб отличить где что записано: где данные, где адреса, где точки входа. Вот, собственно, из этих записей и состоит весь *.hex файл.
Записи бывают следующих типов:
Все записи имеют следующий формат:
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 |
А теперь о некоторых типах записей подробнее:
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=2 32 ) линейный адрес (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=2 16 ) адрес в заданном сегменте.
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» может быть расположена в любом месте файла, если же такой записи нет, то загрузчик использует адрес старта по умолчанию.
Что такое hex файл
[Введение]
Этот документ описывает формат шестнадцатеричного объектного файла для 8-, 16- и 32-битных микропроцессоров Intel. Шестнадцатеричный формат подходит для входных данных программаторов PROM или аппаратных эмуляторов.
Примечание переводчика: поскольку формат Intel HEX давно стал стандартном де-факто, он применяется для хранения данных и кода почти для всех без исключения встраиваемых (embedded) архитектур микроконтроллеров и процессоров, не только для процессоров Intel x86.
Примечание переводчика: 8-, 16- и 32-битные форматы отличаются друг от друга только логической структурой, т. е. появлением в HEX-файле различных типов записей (типы записей см. далее). Общий формат HEX-файла и кодирование записей в нем при этом остается неизменными.
Шестнадцатеричное представление двоичного файла закодировано с помощью цифробуквенных символов ASCII. Например, 8-битное двоичное значение 00111111 соответствует шестнадцатеричному 3F. Чтобы закодировать это значение в ASCII, применяется 2 байта символов ASCII. Первый байт для нашего примера кодирования будет равен ASCII-символу ‘3’ (в двоичном виде это 00110011 или 033H) и второй байт будет равен ASCII-символу ‘F’ (01000110 или 046H). Для каждого значения байта порядок следования шестнадцатеричных цифр всегда такой, что старшая цифра идет первой. ASCII-представление двоичного кода всегда требует в 2 раза больше байт данных, чем двоичное представление данных.
Шестнадцатеричный объектный файл разбит на блоки записей (строки), каждая из которых содержит тип записи, длину, адрес загрузки в память и дополнительную контрольную сумму. Здесь заданы шесть (6) различных типов записей, однако не все типы записей имеют важное значение для каждого конкретного случая. Вот эти типы записей:
· Data Record, запись для данных (8-, 16- или 32-bit форматы)
· End of File Record, запись для сигнала о конце файла (8-, 16- или 32-bit форматы)
· Extended Segment Address Record, запись для адреса расширенного сегмента (16- или 32-bit форматы)
· Start Segment Address Record, запись для начала сегмента (16- или 32-bit форматы)
· Extended Linear Address Record, запись для расширенного линейного адреса (только 32-bit формат)
· Start Linear Address Record, запись для начала линейного адреса (только 32-bit формат)
На рисунке показан пример содержимого HEX-файла.
Примечание переводчика: для каждой отдельной архитектуры процессора могут понадобиться далеко не все записи. К примеру, для микроконтроллеров AVR реально используются только типы записей Data Record и End of File Record. Линкер GCC также добавляет в файл для AVR запись Start Segment Address Record (ближе к концу файла), но эта запись обычно никак не используется (не учитывается при прошивке микроконтроллера программатором).
[Общий формат записи (General Record Format)]
RECORD MARK ‘:’ | RECLEN | LOAD OFFSET | RECTYP | INFO или DATA | CHKSUM |
1 байт | 1 байт | 2 байта | 1 байт | n байт | 1 байт |
Каждая запись в HEX-файле (строка) начинается с поля маркера начала записи RECORD MARK, содержащего ASCII-код 03AH, символ двоеточия ‘:’.
Следующее поле каждой записи RECLEN, которое задает количество байт полезной информации, которая содержится в записи (эти байты идут за полем RECTYP). Помните, что один байт данных представлен двумя символами ASCII. Максимальное значение для поля RECLEN является шестнадцатеричное ‘FF’, т. е. полезных данных в записи может быть от 0 до 255 байт.
Следующее поле в каждой записи LOAD OFFSET, которое указывает 16-битный начальный адрес загрузки байт данных, так что это поле используется только для записей данных (Data Record). В других типах записей, где это поле не используется, оно должно быть закодировано четырьмя ASCII-символами нуля (‘0000’ или 030303030H).
Следующее поле в каждой записи RECTYP, которое обозначает тип этой записи. Поле RECTYP используется для интерпретации информации в записи. Вот как кодируются типы записей:
‘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 (запись адреса начала линейного адреса)
Примечание переводчика: для микроконтроллеров AVR в HEX-файле появляются только записи типа 00, 01 и 03. При этом запись типа 03 содержит абсолютный адрес старта программы в памяти, и не несет в себе никакого практического значения (эта запись обычно находится ближе к концу файла, перед записью 01 End of File Record).
Следующее поле в каждой записи переменной длины, поле INFO/DATA. Оно состоит из нулевого или большего количества байт (байт может быть от 0 до 255, в соответствии со значением поля RECLEN), где каждый байт закодирован как пара шестнадцатеричных цифр. Интерпретация этого поля зависит от значения поля RECTYP.
И наконец, каждая запись заканчивается полем CHKSUM, которое содержит шестнадцатеричное ASCII представление контрольной суммы (дополнение до двух, дополнительный код, two’s complement sum) от всех байт записи, начиная включительно от поля RECLEN, и кончая включительно последним байтом поля INFO/DATA. При этом контрольная сумма вычисляется не от самих символов ASCII, а от представления каждой пары HEX-символов ASCII как одного байта. Таким образом, сумма всех пар в записи после конвертирования каждой пары в двоичное представление, от поля RECLEN включительно до поля CHKSUM включительно, равна 0.
[Extended Linear Address Record (только для 32-битного формата)]
RECORD MARK ‘:’ | RECLEN ’02’ | LOAD OFFSET ‘0000’ | RECTYP ’04’ | ULBA | CHKSUM |
1 байт | 1 байт | 2 байта | 1 байт | 2 байта | 1 байт |
(LBA + DRLO + DRI) MOD 4G
Где DRLO равно полю LOAD OFFSET в записи Data Record, DRI равен индексу байта в записи Data Record.
Когда запись Extended Linear Address Record задает значение LBA, оно может появиться в любом месте 32-битного шестнадцатеричного объектного файла. Это значение остается эффективным, пока не появится другая запись Extended Linear Address Record. Значение по умолчанию для LBA равно нулю, пока не появится запись Extended Linear Address Record.
Примечание переводчика: в HEX-файлах AVR запись Extended Linear Address Record не используется.
В отдельных полях записи имеется следующее содержимое:
RECORD MARK
Это поле содержит байт 03AH, символ двоеточия, закодированный шестнадцатеричным символом ASCII (‘:’).
RECLEN
Это поле содержит два байта 03032H, кодирующие шестнадцатеричными символами ASCII ’02’, что означает длину в байтах информации данных ULBA, содержащихся в этой записи.
LOAD OFFSET
Это поле содержит байты 030303030H, кодирующие шестнадцатеричными символами ASCII ‘0000’, поскольку поле не используется в этой записи.
RECTYP
Это поле содержит байты 03034H, кодирующие шестнадцатеричными символами ASCII ’04’, что задает тип записи Extended Linear Address Record.
ULBA
Это поле содержит 4 шестнадцатеричные цифры ASCII, которые указывают 16-битное значение Upper Linear Base Address. Старший байт в этом адресе находится в паре 10 и 11 символов записи, младший байт в этом адресе находится в паре 12 и 13 символов записи.
CHKSUM
Это поле содержит контрольную сумму полей RECLEN, LOAD OFFSET, RECTYP и ULBA.
[Extended Segment Address Record (16- или 32-битный формат)]
RECORD MARK ‘:’ | RECLEN ’02’ | LOAD OFFSET ‘0000’ | RECTYP ’02’ | USBA | CHKSUM |
1 байт | 1 байт | 2 байта | 1 байт | 2 байта | 1 байт |
SBA + ([DRLO + DRI] MOD 64K)
Где DRLO равно полю LOAD OFFSET в Data Record, DRI равен индексу байта в Data Record.
Когда запись Extended Segment Address Record задает значение SBA, оно может появиться в любом месте 16-битного шестнадцатеричного объектного файла. Это значение остается эффективными, пока не появится другая запись Extended Segment Address Record. Значение SBA по умолчанию равно 0, пока не появится запись Extended Segment Address Record.
Примечание переводчика: в HEX-файлах AVR запись Extended Segment Address Record не используется.
В отдельных полях записи имеется следующее содержимое:
RECORD MARK
Это поле содержит байт 03AH, символ двоеточия, закодированный шестнадцатеричным символом ASCII (‘:’).
RECLEN
Это поле содержит два байта 03032H, кодирующие шестнадцатеричными символами ASCII ’02’, что означает длину в байтах информации данных USBA, содержащихся в этой записи.
LOAD OFFSET
Это поле содержит байты 030303030H, кодирующие шестнадцатеричными символами ASCII ‘0000’, поскольку поле не используется в этой записи.
RECTYP
Это поле содержит байты 03032H, кодирующие шестнадцатеричными символами ASCII ’02’, что задает тип записи Extended Segment Address Record.
USBA
Это поле содержит 4 шестнадцатеричные цифры ASCII, которые указывают 16-битное значение Upper Segment Base Address. Старший байт в этом адресе находится в паре 10 и 11 символов записи, младший байт в этом адресе находится в паре 12 и 13 символов записи.
CHKSUM
Это поле содержит контрольную сумму полей RECLEN, LOAD OFFSET, RECTYP и USBA.
[Data Record (8-, 16- или 32-битный формат)]
RECORD MARK ‘:’ | RECLEN | LOAD OFFSET | RECTYP ’00’ | DATA | CHKSUM |
1 байт | 1 байт | 2 байта | 1 байт | n байт | 1 байт |
Запись Data Record предоставляет набор шестнадцатеричных цифр, в которых находится ASCII-код байтов данных, которые содержатся в порции данных образа памяти. Метод, используемый для вычисления абсолютного адреса (линейного в случае 8 и 32 бит и сегментированного в случае 16 бит) каждого байта описан в разделах Extended Linear Address Record и Extended Segment Address Record.
В отдельных полях записи имеется следующее содержимое:
RECORD MARK
Это поле содержит байт 03AH, символ двоеточия, закодированный шестнадцатеричным символом ASCII (‘:’).
RECLEN
Это поле содержит две шестнадцатеричные цифры ASCII, которые задают количество байт данных, находящихся в записи. Максимальное значение равно ‘FF’ или 04646H (что соответствует десятичному значению 255).
LOAD OFFSET
Это поле содержит четыре шестнадцатеричные цифры ASCII, представляющие смещение от LBA (см. раздел Extended Linear Address Record) или SBA (см. раздел Extended Segment Address Record), что задает адрес, куда будет помещен первый байт записи.
RECTYP
Это поле содержит байты 03030H, кодирующие шестнадцатеричными символами ASCII ’00’, что задает тип записи Data Record.
DATA
Это поле содержит пары шестнадцатеричных цифр ASCII, где каждая пара кодирует один байт данных.
CHKSUM
Это поле содержит контрольную сумму полей RECLEN, LOAD OFFSET, RECTYP и DATA.
[Start Linear Address Record (только 32-битный формат)]
RECORD MARK ‘:’ | RECLEN ’04’ | LOAD OFFSET ‘0000’ | RECTYP ’05’ | EIP | CHKSUM |
1 байт | 1 байт | 2 байта | 1 байт | 4 байта | 1 байт |
Запись Start Linear Address Record используется для указания стартового адреса объектного файла, с которого начнется выполнение программы. Значение дает линейный 32-битный адрес для регистра EIP. Имейте в виду, что эта запись только задает адрес кода в пределах 32-битного линейного адресного пространства процессора 80386. Если начинается выполнение кода в реальном режиме (real mode) 80386, то вместо этого должна использоваться Start Segment Address Record, поскольку эта запись указывает содержимое обоих регистров CS и IP, что необходимо для real mode.
Запись Start Linear Address Record может появиться в любом месте 32-битного шестнадцатеричного объектного файла. Если такая запись отсутствует в шестнадцатеричном объектном файле, то загрузчик свободен в назначении адреса старта по умолчанию.
Примечание переводчика: в HEX-файлах AVR запись Start Linear Address Record не используется.
В отдельных полях записи имеется следующее содержимое:
RECORD MARK
Это поле содержит байт 03AH, символ двоеточия, закодированный шестнадцатеричным символом ASCII (‘:’).
RECLEN
Зто поле содержит 03034H, кодирующие шестнадцатеричными символами ASCII ’04’, что указывает длину, в байтах, содержимого регистра EIP в этой записи.
LOAD OFFSET
Это поле содержит 030303030H, кодирующие шестнадцатеричными символами ASCII ‘0000’, поскольку это поле не используется в записи.
RECTYP
Это поле содержит 03035H, кодирующие шестнадцатеричными символами ASCII ’05’, что задает тип записи Start Linear Address Record.
EIP
Это поле содержит восемь символов ASCII шестнадцатеричных цифр, указывающих содержимое 32-битного регистра EIP. Старший байт адреса находится в паре символов 10 и 11.
CHKSUM
Это поле содержит контрольную сумму полей RECLEN, LOAD OFFSET, RECTYP и EIP.
[Start Segment Address Record (16- или 32-битный формат)]
RECORD MARK ‘:’ | RECLEN ’04’ | LOAD OFFSET ‘0000’ | RECTYP ’03’ | CS/IP | CHKSUM |
1 байт | 1 байт | 2 байта | 1 байт | 4 байта | 1 байт |
Запись Start Segment Address Record используется для указания стартового адреса объектного файла, с которого начнется выполнение программы. Дается значение 20-битного сегментированного адреса для регистров CS и IP. Имейте в виду, что эта запись задает адрес кода только в пределах 20-битного сегментированного адресного пространства процессоров 8086/80186.
Запись Start Segment Address Record может появиться в любом месте 16-битного шестнадцатеричного объектного файла. Если такая запись отсутствует в шестнадцатеричном объектном файле, то загрузчик свободен в назначении адреса старта по умолчанию.
Примечание переводчика: в HEX-файлах AVR запись Start Segment Address Record используется, однако не несет в себе никакого практического значения. При этом значение CS равно 0, а значение IP равно байтовому адресу запуска программы firmware.
В отдельных полях записи имеется следующее содержимое:
RECORD MARK
Это поле содержит байт 03AH, символ двоеточия, закодированный шестнадцатеричным символом ASCII (‘:’).
RECLEN
Зто поле содержит 03034H, кодирующие шестнадцатеричными символами ASCII ’04’, что указывает длину, в байтах, содержимого регистров CS/IP в этой записи.
LOAD OFFSET
Это поле содержит 030303030H, кодирующие шестнадцатеричными символами ASCII ‘0000’, поскольку это поле не используется в записи.
RECTYP
Это поле содержит 03033H, кодирующие шестнадцатеричными символами ASCII ’03’, что задает тип записи Start Segment Address Record.
CS/IP
Это поле содержит восемь символов ASCII шестнадцатеричных цифр, указывающих содержимое 16-битного регистра CS и 16-битного регистра IP. Старший байт содержимого регистра CS находится в паре символов 10 и 11, младший в паре символов 12 и 13. Старший байт содержимого регистра IP находится в паре символов 14 и 15, младший в паре символов 16 и 17.
CHKSUM
Это поле содержит контрольную сумму полей RECLEN, LOAD OFFSET, RECTYP и CS/IP.
[End of File Record (8-, 16- или 32-битный формат)]
RECORD MARK ‘:’ | RECLEN ’00’ | LOAD OFFSET ‘0000’ | RECTYP ’01’ | CHKSUM ‘F’ |
1 байт | 1 байт | 2 байта | 1 байт | 1 байт |
Запись End of File Record указывает конец шестнадцатеричного объектного файла. Это строка, в которой содержатся символы ‘:00000001FF’.
Примечание переводчика: в HEX-файлах микроконтроллера AVR эта запись также используется для обозначения конца файла.
В отдельных полях записи имеется следующее содержимое:
RECORD MARK
Это поле содержит байт 03AH, символ двоеточия, закодированный шестнадцатеричным символом ASCII (‘:’).
RECLEN
то поле содержит 03030H, кодирующие шестнадцатеричными символами ASCII ’00’. Поскольку эта запись не содержит никаких данных INFO/DATA, то длина равна нулю.
LOAD OFFSET
Это поле содержит 030303030H, кодирующие шестнадцатеричными символами ASCII ‘0000’, поскольку это поле не используется в записи.
RECTYP
Это поле содержит 03031H, кодирующие шестнадцатеричными символами ASCII ’01’, что задает тип записи End of File Record.
CHKSUM
Это поле содержит контрольную сумму полей RECLEN, LOAD OFFSET и RECTYP. Поскольку все поля статические (их содержимое неизменно), то контрольную сумму также можно вычислить статически, и её значение 04646H, кодирующие шестнадцатеричными символами ASCII ‘FF’.
[Алгоритм подсчета контрольной суммы]
Байт контрольной суммы CHKSUM для строки HEX-файла вычисляется так, чтобы байтовая сумма всех полезных данных строки и самой контрольной суммы с отбрасыванием переполнений равнялась нулю. При этом складываются не сами символы ASCII, а только данные, что они представляют. Упрощенный алгоритм подсчета контрольной суммы на псевдокоде для записи Data Record:
Чтобы было совсем понятно, разберем простой пример. Вот типичная строка HEX-файла прошивки микроконтроллера AVR (для наглядности поля помечены разными цветами):
: 10 3800 00 5CC000008FC0000073C0000071C00000 E9
Итак, контрольная сумма CHKSUM у нас получится, если сложим (отбрасывая перенос) байты данных начиная от поля RECLEN ( 0x10 ) до последнего байта поля DATA[RECLEN-1] ( 0x00 ), и затем вычтем из нуля полученную сумму (также отбрасывая перенос):
Если сложить все байты начиная от RECLEN до CHKSUM включительно (отбрасывая перенос), то получим ноль: