Багато хто напевно помічали, що більшість файлів прошивок для різних контролерів, процесорів, мікросхем пам’яті та інших подібних речей зберігається або в бінарних файлах, в яких інформація записана саме в тому вигляді і в тому порядку, в якому вона записана в пам’яті залізяки (тобто це тупо дамп пам’яті), або у файлах з розширенням 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» може бути розташована в будь-якому місці файлу, якщо ж такого запису немає, то завантажувач використовує адресу старту за замовчуванням.

Джерело: radiohlam.ru /