HEX-format. HEX-files. HEX-файли. Шифрування та дешифрування HEX-формату
Частина 1. Опис HEX-формата
Частина 2. Практична програмна реалізація (Borland Builder C++) для аналізу HEX-формата і формування HEX-записів
ЧАСТИНА 1
Формат HEX-файлів є обов’язковим для дотримання, якщо він використовується програмним забезпеченням. Цей формат був надзвичайно корисним при використанні носіїв інформації, які не відзначалися особливою надійністю. Згодом компанія Intel додала до цього формату нові типи записів, а з часом потрібність HEX-формату зменшилась, хоча він досі іноді використовується.
На відміну від BIN-формату, HEX-формат забезпечує розміщення інформації в пам’яті контролера саме за тими адресами, які вказані в інформаційному HEX-файлі, а не “від початку і скільки вийде”, як це робиться в BIN-файлі.
Те, що ви читаєте – це майже переклад документа компанії Intel “Hexadecimal Object File Format Specification” – опис широко поширеного текстового формату файлу для зберігання двійкових даних (формат Intel HEX). У тексті часто зустрічається термін “Шістнадцятковий об’єктний файл”, який найчастіше є прошивкою мікроконтролера.
Цей документ описує формат шістнадцяткового об’єктного файлу для 8-, 16- та 32-бітних мікропроцесорів. Описано в досить доступній формі, я зрозумів – і ви зрозумієте.
Примітка: формат Intel HEX чудово підходить і застосовується для зберігання даних майже для всіх без винятку вбудованих архітектур мікроконтролерів і процесорів, а не лише для процесорів Intel x86.
Шістнадцятковий формат об’єктного файлу – спосіб подання абсолютного фіксованого об’єктного файлу в символах ASCII. Оскільки файл кодується лише символами ASCII (American Standard Code for Information Interchange) замість двійкових байт, то стає можливим зберігати файл на різних носіях, навіть таких застарілих, як паперова перфострічка (paper-tape), перфокарти (punch cards) та інше. HEX-файл можна також легко відобразити на екрані терміналу, надрукувати на принтері.
8-бітний шістнадцятковий формат об’єктного файлу дозволяє розміщення коду і даних в межах лінійного 16-бітного адресного простору 8-бітових процесорів. 20-бітний шістнадцятковий формат дозволяє використовувати 20-бітовий сегментований адресний простір 16-бітових процесорів, наприклад, Intel.
32-бітний формат дозволяє застосовувати лінійний 32-бітовий адресний простір будь-яких 32-бітових процесорів.
Шістнадцяткове представлення двійкового файлу закодовано за допомогою цифробуквених символів ASCII. Наприклад, 8-бітове двійкове значення 00111111 (можемо написати 0011 1111) відповідає шістнадцятковому 3F. Щоб закодувати це значення в ASCII, застосовується 2 байти символів ASCII. Перший байт для нашого прикладу кодування буде дорівнювати ASCII-символу ‘3’ (у двійковому вигляді це 0011011 або 033H) і другий байт буде дорівнює ASCII-символу ‘F’ (01000110 або 046H). Для кожного значення байта порядок проходження шістнадцяткових цифр завжди такий, що старша цифра йде першою. ASCII-представлення двійкового коду завжди вимагає в 2 рази більше байтів даних, ніж двійкове подання даних.
Вважаємо, що байт складається з двох тетрад, старшої і молодшої, в кожній тетраді 4 біти.
Кодування тетрад:
Біти ASCII-код
0000 – 0
0001 – 1
0010 – 2
0011 – 3
0100 – 4
0101 – 5
0110 – 6
0111 – 7
1000 – 8
1001 – 9
1010 – A
1011 – B
1100 – C
1101 – D
1110 – E
1111 – F
Кожен байт в HEX-форматі представлений двома тетрадами, спочатку старша тетрада, потім молодша.
Шістнадцятковий об’єктний файл розбитий на блоки записів (рядки), кожен з яких містить:
– тип запису,
– довжину,
– адресу завантаження в пам’ять
– змінну частину, яка залежить від типу запису,
– контрольну суму.
В форматі задані шість різних типів записів, однак не всі типи записів мають важливе значення для кожного конкретного випадку. Ось ці типи записів:
· Data Record, запис даних (8-bit, 16-bit або 32-bit формати)
· End of File Record, запис про кінець файлу (8-bit, 16-bit або 32-bit формати)
· Extended Segment Address Record, запис для адреси розширеного сегмента (16-bit або 32-bit формати)
· Start Segment Address Record, запис для початку сегмента (16-bit або 32-bit формати)
· Extended Linear Address Record, запис для розширеної лінійної адреси (лише 32-bit формат)
· Start Linear Address Record, запис для початку лінійної адреси (лише 32-bit формат)
Примітка: для кожної окремої архітектури процесора можуть знадобитися далеко не всі записи. Наприклад, для мікроконтролерів AVR реально використовуються лише типи записів Data Record і End of File Record. Лінкер GCC також додає в файл для AVR запис Start Segment Address Record (ближче до кінця файлу), але цей запис є лише адресою запуску мікропрограми, і зазвичай ніяк не використовується (не враховується при прошивці мікроконтролера программатором).
При роботі, наприклад, з процесорами ARM Cortex-M3, Cortex-M4 корисним є 32-бітний HEX-формат, бо традиційна початкова адреса для мікропрограми такого контролера – 0x08000000 (адреса написана в шістнадцятковій формі).
Розглянемо кожен запис формата більш детально
[Загальний вигляд запису (General Record Format)]
RECORD MARK ‘:’ 1 байт
RECLEN 1 байт
LOAD OFFSET ‘0000’ 2 байти
RECTYP 1 байт
INFO або DATA 2 байти
CHKSUM 1 байт
Кожен запис в HEX-файлі (кожен рядок) починається з поля маркера початку запису RECORD MARK, що містить ASCII-код 3AH, символ двокрапки ‘:’
Наступне поле кожного запису 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, хоча він ніяк не використовується програматором. Для мікроконтролерів з 32-бітною адресацією є необхідним також запис типу 04.
При цьому запис типу 03 може містити абсолютну адресу старту програми в пам’яті, і не несе в собі ніякого практичного значення. Цей запис зазвичай знаходиться ближче до кінця файлу, перед записом 01 (End of File Record)..
Наступне поле в кожному записі змінної довжини, поле INFO/DATA. Воно складається з нульової або відмінної від нуля кількості байт (кількість байт може бути від 0 до 255, у відповідності зі значенням поля RECLEN), де кожен байт закодований як пара шістнадцяткових цифр. Інтерпретація цього поля залежить від значення поля RECTYP.
І нарешті, кожен запис закінчується полем CHKSUM, яке містить шістнадцяткове ASCII-представлення контрольної суми (використовуються терміни “додаток до двох”, “додатковий код”, “комплементарна сума”) від усіх байт запису, починаючи з поля RECLEN, і закінчуючи останнім байтом поля INFO/DATA. При цьому контрольна сума обчислюється не від самих символів ASCII, а від представлення кожної пари HEX-символів ASCII як одного байта. Таким чином, сума всіх двійкових байт у записі після конвертації кожної пари ASCII в двійковий вигляд, від поля RECLEN до поля CHKSUM включно, дорівнює 0. До всіх попередніх байт, починаючи з RECLEN, додаємо CHKSUM, і в молодшому байті результата отримуємо 0.
Важливо ! За кожним записом можуть йти байти 0DH, 0AH, в десятковому вигляді це 13, 10. Для текстових файлів це символи переходу на початок нового рядка (популярне позначення CR,LF). В HTML — це тег <br>. Справа в тому, що HEX-файл можна розглядати як текстовий файл, це є корисною властивістю формата.
[Extended Linear Address Record (лише для 32-бітного формату)]
RECORD MARK ‘:’ 1 байт
RECLEN ’02’ 1 байт
LOAD OFFSET ‘0000’ 2 байти
RECTYP ’04’ 1 байт
ULBA 2 байти
CHKSUM 1 байт
Запис 32-bit Extended Linear Address Record використовується для вказівки бітів 16..31 від Linear Base Address (лінійна базова адреса, LBA), де біти 0..15 адреси LBA рівні 0. Біти 16..31 LBA називаються Upper Linear Base Address (верхня лінійна базова адреса, ULBA). Абсолютну адресу пам’яті для даних Data Record отримуємо шляхом додавання LBA як старших 16 біт адреси (біти 31…16) до молодших 16 біт поля LOAD OFFSET (біти 15…0), що вказані в Data Record. Це додавання робиться по “модулю 4G” (32 біта).
Коли запис Extended Linear Address Record задає значення LBA, воно може з’явитися в будь-якому місці 32-бітного шістнадцяткового об’єктного файлу. Це значення залишається ефективним, поки не з’явиться інший запис Extended Linear Address Record. Значення за замовчуванням для LBA дорівнює нулю, поки не з’явиться запис Extended Linear Address Record.
В HEX-файлах для 16-бітної адресації запис Extended Linear Address Record не потрібен.
В окремих полях запису є наступне:
RECORD MARK
Це поле містить байт 3AH, символ двокрапки, закодований символом ASCII (‘:’).
RECLEN
Це поле містить два байти (наприклад, 3032H кодує значення ’02’), що означає довжину в байтах інформації даних ULBA, що містяться в цьому записі.
LOAD OFFSET
Це поле містить байти 30303030H, що кодується шістнадцятковими символами ASCII ‘0000’, оскільки поле НЕ ВИКОРИСТОВУЄТЬСЯ в цьому записі.
RECTYP
Це поле містить байти 3034H, що кодується шістнадцятковими символами ASCII ’04’, що задає тип запису Extended Linear Address Record.
ULBA
Це поле містить 4 шістнадцяткові цифри ASCII, які вказують 16-бітове значення Upper Linear Base Address. Старший байт йде першим, потім молодший.
CHKSUM
Це поле містить контрольну суму полів RECLEN, LOAD OFFSET, RECTYP і ULBA.
[Extended Segment Address Record (16– або 32-бітний формат)]
RECORD MARK ‘:’ 1 байт
RECLEN ’02’ 1 байт
LOAD OFFSET ‘0000’ 2 байти
RECTYP ’02’ 1 байт
USBA 2 байти
CHKSUM 1 байт
16-бітний запис Extended Segment Address Record використовується, щоб вказати біти 4..19 базової адреси сегмента Segment Base Address (SBA), де біти 0-3 адреси SBA дорівнюють 0. Біти 4..19 SBA є адресою Upper Segment Base Address (USBA). Абсолютна адреса пам’яті, що містить кожен конкретний байт в записах Data Record, виходить звичайним додаванням SBA до зміщення, обчислюється шляхом додавання поля LOAD OFFSET в запису Data Record з індексом байта в Data Record (0, 1, 2, … n). Таке додавання-зсув робиться по модулю 64K (16-біт), з ігноруванням переносу, так що в результаті завантаження зміщення огинає по циклу (від 0FFFFH до 00000H) від початку до кінця сегмента 64K, заданого в SBA. Адреса, за якою завантажується кожен окремий байт, при цьому обчислюється за формулою:
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
Це поле містить байт 3AH, символ двокрапки, закодований символом ASCII (‘:’).
RECLEN
Це поле містить два байти 3032H, що кодується шістнадцятковими символами ASCII ’02’, це означає довжину в байтах інформації даних USBA, що містяться в цьому записі.
LOAD OFFSET
Це поле містить байти 30303030H, що кодується шістнадцятковими символами ASCII ‘0000’, оскільки поле НЕ ВИКОРИСТОВУЄТЬСЯ в цьому записі.
RECTYP
Це поле містить байти 3032H, що кодується шістнадцятковими символами 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 ‘:’ 1 байт
RECLEN 1 байт
LOAD OFFSET ‘0000’ 2 байти
RECTYP ’00’ 1 байт
DATA N байт
CHKSUM 1 байт
Запис Data Record надає набір шістнадцяткових цифр, в яких знаходиться ASCII-код байтів даних, які містяться в порції даних. Метод, використовуваний для обчислення абсолютної адреси кожного байта описаний в розділах Extended Linear Address Record і Extended Segment Address Record.
Примітка: цей запис – найбільш частий для HEX-файлу. Зазвичай в одному такому запису (рядку) закодовано 16 байт коду програми (у полі RECLEN міститься значення шістнадцяткове ’10’). В останньому рядку Data Record файлу HEX може міститися менше 16 байт (у полі RECLEN з’явиться відповідне значення). Обмеження довжини не більше 16 байт не є обов’язковим, просто популярним.
В окремих полях запису є наступне:
RECORD MARK
Це поле містить байт 3AH, символ двокрапки, закодований символом ASCII (‘:’).
RECLEN
Це поле містить дві шістнадцяткові цифри ASCII, які задають кількість байт даних, що знаходяться в запису. Максимальне значення одно ‘FF’ або 04646H (що відповідає десятковому значенню 255).
LOAD OFFSET
Це поле містить чотири шістнадцяткові цифри ASCII, що представляють зсув від LBA (див. Розділ Extended Linear Address Record) або SBA (див. Розділ Extended Segment Address Record), що задає адресу, куди буде поміщений перший байт запису.
RECTYP
Це поле містить байти 3030H, що кодується шістнадцятковими символами ASCII ’00’, що задає тип запису Data Record.
DATA
Це поле містить пари шістнадцяткових цифр ASCII, де кожна пара кодує один байт даних.
CHKSUM
Це поле містить контрольну суму полів RECLEN, LOAD OFFSET, RECTYP і DATA.
[Start Linear Address Record (лише 32-бітний формат)]
RECORD MARK ‘:’ 1 байт
RECLEN ’04’ 1 байт
LOAD OFFSET ‘0000’ 2 байти
RECTYP ’05’ 1 байт
EIP 4 байти
CHKSUM 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
Це поле містить байт 3AH, символ двокрапки, закодований символом ASCII (‘:’).
RECLEN
Зто поле містить 3034H, що кодується шістнадцятковими символами ASCII ’04’, що вказує довжину, в байтах, вмісту регістра EIP в цьому записі.
LOAD OFFSET
Це поле містить 30303030H, що кодується шістнадцятковими символами ASCII ‘0000’, оскільки це поле не використовується в записі.
RECTYP
Це поле містить 3035H, що кодується шістнадцятковими символами 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 ‘:’ 1 байт
RECLEN ’04’ 1 байт
LOAD OFFSET ‘0000’ 2 байти
RECTYP ’03’ 1 байт
CS/IP 4 байти
CHKSUM 1 байт
Запис Start Segment Address Record використовується для вказівки стартового адреси об’єктного файлу, з якого почнеться виконання програми. Дається значення 20-бітної сегментованої адреси для регістрів CS і IP. Майте на увазі, що цей запис задає адресу коду лише в межах 20-бітної сегментованої адреси процесорів 8086/80186.
Запис Start Segment Address Record може з’явитися в будь-якому місці 16-бітного шістнадцяткового об’єктного файлу. Якщо такий запис відсутній в шістнадцятковому об’єктному файлі, то завантажувач вільний у призначенні адреси старту за замовчуванням.
В HEX-файлах запис Start Segment Address Record використовується, проте часто не несе в собі ніякого практичного значення.
В окремих полях запису є наступне:
RECORD MARK
Це поле містить байт 3AH, символ двокрапки, закодований символом ASCII (‘:’).
RECLEN
Зто поле містить 3034H, що кодується шістнадцятковими символами ASCII ’04’, що вказує довжину, в байтах, вмісту регістрів CS/IP в цьому записі.
LOAD OFFSET
Це поле містить 30303030H, що кодується шістнадцятковими символами ASCII ‘0000’, оскільки це поле не використовується в записі.
RECTYP
Це поле містить 3033H, що кодується шістнадцятковими символами 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 ‘:’ 1 байт
RECLEN ’00’ 1 байт
LOAD OFFSET ‘0000’ 2 байти
RECTYP ’01’ 1 байт
CHKSUM ‘F’ 1 байт
Запис End of File Record вказує на закінчення шістнадцяткового об’єктного файлу. Це рядок, в якому містяться символи:
:00000001FF
В окремих полях запису є наступне:
RECORD MARK
Це поле містить байт 3AH, символ двокрапки, закодований символом ASCII (‘:’).
RECLEN
Це поле містить 3030H, що кодується шістнадцятковими символами ASCII ’00’. Оскільки цей запис не містить ніяких даних INFO/DATA, то довжина дорівнює нулю.
LOAD OFFSET
Це поле містить 30303030H, що кодується шістнадцятковими символами ASCII ‘0000’, оскільки це поле не використовується в записі.
RECTYP
Це поле містить 3031H, що кодується шістнадцятковими символами ASCII ’01’, що задає тип запису End of File Record.
CHKSUM
Це поле містить контрольну суму полів RECLEN, LOAD OFFSET і RECTYP. Оскільки всі поля статичні (їх вміст незмінний), то контрольну суму також можна обчислити статично, і її значення 04646H, це символи ASCII ‘FF’.
[Алгоритм підрахунку контрольної суми]
Байт контрольної суми CHKSUM для рядка HEX-файлу обчислюється так, щоб байтова сума всіх корисних даних рядка і самої контрольної суми з відкиданням переповнень дорівнювала нулю. При цьому складаються не самі символи ASCII, а лише дані, які вони представляють. Спрощений алгоритм підрахунку контрольної суми на псевдокоді для запису Data Record:
byte crcacc = 0;
crcacc + = RECLEN;
crcacc + = HIGHBYTE (LOAD OFFSET);
crcacc + = LOWBYTE (LOAD OFFSET);
crcacc + = RECTYP;
crcacc + = DATA [0];
crcacc + = DATA [1];
..
crcacc + = DATA [RECLEN-1];
CHKSUM = (byte) (0x100-crcacc);
Щоб було зовсім зрозуміло, розберемо простий приклад. Ось типовий рядок HEX-файлу прошивки мікроконтролера AVR:
:103800005CC000008FC0000073C0000071C00000E9
Цей рядок вказує на запис в FLASH-пам’ять мікроконтролера, починаючи з адреси 0x3800 (адреса вказана в полі LOAD OFFSET), байти 0x5C, 0xC0, 0x00, 0x00, 0x8F, 0xC0, 0x00, 0x00, 0x73, 0xC0, 0x00, 0x00, 0x71, 0xC0, 0x00, 0x00 (байти вказані в полі DATA), всього 16 байт (в полі RECLEN вказано значення 0x10). Байт 0x00 RECTYP позначає тип рядка (запису) Data Record. При цьому контрольна сума CHKSUM (сума байт полів RECLEN, LOAD OFFSET, RECTYP, DATA) дорівнює 0xE9. Сума байтів всіх полів RECLEN, LOAD OFFSET, RECTYP, DATA, CHKSUM повинна бути рівна 0.
Отже, контрольна сума CHKSUM у нас вийде, якщо додамо (відкидаючи перенос) байти даних починаючи від поля RECLEN (0x10) до останнього байта поля DATA [RECLEN-1] (0x00), і потім віднімемо з нуля отриману суму (також відкидаючи перенесення) :
0xE9 = 0 – (0x10+0x38+0x00+0x00+0x5C+0xC0+0x00+0x00+0x8F+0xC0+0x00+0x00+0x73+0xC0+0x00+0x00+0x71+0xC0+0x00+0x00)
Якщо скласти всі байти починаючи від RECLEN до CHKSUM включно (відкидаючи перенос), то отримаємо нуль:
0x10+0x38+0x00+0x00+0x5C+0xC0+0x00+0x00+0x8F+0xC0+0x00+0x00+0x73+0xC0+0x00+0x00+0x71+0xC0+0x00+0x00+0xE9 = 0
ЧАСТИНА 2. Практична програмна реалізація.
//
// Підпрограми взяті із збірника Znoviak Software
//
#define DOVZINA_MASIVA 200000; // якийсь розмір масиву пам’яті для зберігання даних
void ToHex1(char *buf, int dan);
void ToHex2(char *buf, int dan);
void ToHex4(char *buf, int dan);
int AnalHex4 (BYTE *buf);
BYTE AnalHex2 (BYTE *buf);
BYTE AnalHex1 (BYTE *buf);
AnsiString stribu; // просто для повідомлень
BYTE Prombuf[100]; // просто буфер пам’яті
char masko1[260]; // просто буфер пам’яті
BYTE Zagbuf[DOVZINA_MASIVA]; // тут зберігаємо дані
BYTE sinterror; // признак синтаксичної помилки
int Start_adr; // початкова інформаційна адреса
int dovzinfil; // інформаційна довжина
//
// підпрограма для прочитування і аналізу HEX-файла
//
void __fastcall TForm1::Button2Click(TObject *Sender)
{
//
// Уявна кнопка для прочитування з HEX-файла
//
int iFileHandle;
int iBytesRead;
int i1,offse,addza,ulba,adrzapam, starad;
BYTE dovzi,typzap,ksum, dann, sumfil;
if (OpenDialog1->Execute())
{
//
// відкриваємо уявний вибраний файл
//
iFileHandle = FileOpen(OpenDialog1->FileName, fmOpenRead);
if(iFileHandle<0){
stribu=”Не можу відкрити файл, помилка ” + IntToStr(iFileHandle);
Application->MessageBox(stribu.c_str(), “Файлова помилка”);
//
// файл залишився невідкритим
//
goto ENN;};
Label6 -> Caption = “Читаю файл”; // просто діагностичне повідомлення
//
// Вважаємо, що у нас виділений масив Zagbuf для зберігання
// даних, прочитаних з HEX-файла
// Інформація зберігається, починаючи з нульової адреси.
// При бажанні алгоритм можна доробити, якщо потрібно, щоб для
// 32-бітного формату інформація зберігалась не з нульової,
// а якоїсь іншої адреси, щоб не тримати в програмі гігантський масив.
//
memset(Zagbuf,0xff,DOVZINA_MASIVA); // заповнили наш масив символами FF
//
// Читаємо файл .hex
//
K05: i1=0;addza=0;
K11: i1++;
iBytesRead = FileRead(iFileHandle, Prombuf, 1); // взяли з файла один байт
if(iBytesRead==0)goto ALLES; // файл закінчився
if(iBytesRead<0)goto POMYL; // помилка з прочитуванням файла
//
// ці байти нам нецікаві
//
if((Prombuf[0]==0xd) (Prombuf[0]==0xa) (Prombuf[0]==’ ‘))goto K11;
if(Prombuf[0]!=’:’)goto POMFORM; // неправильний початок запису
//
// читаємо RECLEN
//
i1=i1+2; iBytesRead = FileRead(iFileHandle, Prombuf, 2);
if(iBytesRead<0)goto POMYL;
if(iBytesRead<2)goto POMFORM;
//
// аналізуємо RECLEN
//
sinterror=0;dovzi=AnalHex2(Prombuf);
if(sinterror!=0)goto POMFORM;
//
// читаємо OFFSET
//
i1=i1+4; iBytesRead = FileRead(iFileHandle, Prombuf, 4);
if(iBytesRead<0)goto POMYL;
if(iBytesRead<4)goto POMFORM;
//
// аналізуємо OFFSEN
//
sinterror=0;offse=AnalHex4(Prombuf);
if(sinterror!=0)goto POMFORM;
//
// читаємо RECTYP
//
i1=i1+2; iBytesRead = FileRead(iFileHandle, Prombuf, 2);
if(iBytesRead<0)goto POMYL;
if(iBytesRead<2)goto POMFORM;
//
// аналізуємо RECTYP
//
sinterror=0;typzap=AnalHex2(Prombuf);
if(sinterror!=0)goto POMFORM;
ksum=dovzi+typzap+(offse & 0xff)+((offse >> 8) & 0xff);
if(typzap==4)goto K12; // тут ULBA
if(typzap==5)goto K25; // тут адреса запуску прошивки
if(typzap==1)goto K13; // тут кінець HEX-записів
if(typzap==0)goto K14; // тут запис даних
goto POMFORM; // невідомий тип запису
//
// тут власне дані
//
K14: if(dovzi==0)goto POMFORM; // ненормально, що нульова довжина
K15: i1=i1+2; iBytesRead = FileRead(iFileHandle, Prombuf, 2);
if(iBytesRead<0)goto POMYL;
if(iBytesRead<2)goto POMFORM;
//
// аналізуємо дані
//
sinterror=0;dann=AnalHex2(Prombuf);
if(sinterror!=0)goto POMFORM;
ksum=ksum+dann;
adrzapam=((ulba << 16) + offse);
if(adrzapam->DOVZINA_MASIVA)goto NEAD;
dovzinfil=adrzapam+1;
Zagbuf[adrzapam]=dann; // зформований байт записали в наш масив
offse++;dovzi–;if(dovzi!=0)goto K15;
NASUM: i1=i1+2; iBytesRead = FileRead(iFileHandle, Prombuf, 2);
if(iBytesRead<0)goto POMYL;
if(iBytesRead<2)goto POMFORM;
//
// аналізуємо контрольну суму
//
sinterror=0;sumfil=AnalHex2(Prombuf);
if(sinterror!=0)goto POMFORM;
if(((sumfil + ksum) & 0xff)!=0)goto NERIVSUM;
goto K11;
//
// аналізуємо кінець HEX-записів
//
K13: i1=i1+2; iBytesRead = FileRead(iFileHandle, Prombuf, 2);
if(iBytesRead<0)goto POMYL;
if(iBytesRead<2)goto POMFORM;
//
// аналізуємо контрольну суму в запису закінчення HEX
//
sinterror=0;sumfil=AnalHex2(Prombuf);
if(sinterror!=0)goto POMFORM;
if(((sumfil + ksum) & 0xff)==0)goto ALLES;
goto NERIVSUM;
//
// читаємо ULBA
//
K12: i1=i1+4; iBytesRead = FileRead(iFileHandle, Prombuf, 4);
if(iBytesRead<0)goto POMYL;
if(iBytesRead<4)goto POMFORM;
//
// аналізуємо ULBA
//
sinterror=0;ulba=AnalHex4(Prombuf);
if(sinterror!=0)goto POMFORM;
ksum=ksum+(ulba & 0xff) + ((ulba >> 8) & 0xff);
goto NASUM;
//
// читаємо стартову адресу
//
K25: i1=i1+8; iBytesRead = FileRead(iFileHandle, Prombuf, 8);
if(iBytesRead<0)goto POMYL;
if(iBytesRead<8)goto POMFORM;
//
// аналізуємо стартову адресу
//
sinterror=0;starad=AnalHex4(Prombuf);
if(sinterror!=0)goto POMFORM;
ksum=ksum+(starad & 0xff) + ((starad >> 8) & 0xff);
starad=AnalHex4(&Prombuf[4]);
if(sinterror!=0)goto POMFORM;
ksum=ksum+(starad & 0xff) + ((starad >> 8) & 0xff);
goto NASUM;
POMFORM: Label5 -> Caption = “Помилка в HEX-форматі, байт “+IntToStr(i1);
indpomil=1; // це лише індикатор, що були помилки
goto ALL_CLO;
NERIVSUM: Label5 -> Caption = “Помилка в контр. сумі, байт “+IntToStr(i1);
indpomil=1;
goto ALL_CLO;
NEAD: Label5 -> Caption = “Неправильна адреса запису, байт “+IntToStr(i1);
indpomil=1;
goto ALL_CLO;
//
// все культурно закінчено
//
ALLES: Label2 -> Caption = “Розмір прошивки = “+IntToStr(dovzinfil);
goto ALL_CLO;
//
// помилка читання самого файла
//
POMYL: stribu=”Не прочитав, помилка ” + IntToStr(iBytesRead);
Application->MessageBox(stribu.c_str(), “Файлова помилка”);
FileClose(iFileHandle); goto ENN;
ALL_CLO: FileClose(iFileHandle); goto ENN;
ENN: Label6 -> Caption = ” “; // просто стираємо діагностичне повідомлення
}
}
//—————————————————————————
//
// підпрограма для формування і запису файла в HEX-форматі
// (запис робимо від умовної адреси Start_adr, дані – в масиві Zagbuf)
// параметр dovzinfil – це ІНФОРМАЦІЙНА байтова довжина того HEX-файла,
// який ми записуємо, справжня довжина файла буде більшою
//
// отже, з нашого масиву Zagbuf вибираємо адресну зону з початком Start_adr,
// і пишемо інформаційний матеріал довжиною dovzinfil, тобто
// початкова адреса Start_adr, а кінцева – dovzinfil-1
//
// Формуємо для запису блоки даних з інформаційною довжиною 16 байт. Це популярна довжина
//
void __fastcall TForm1::Button3Click(TObject *Sender)
{
//
// Кнопка запису в HEX-файл
//
int iFileHandle;
int iBytesRead;
char pszBuffer[3];
int i1,i2,i3, i4, ksum;
BYTE dann;
if (SaveDialog1->Execute())
{
//
// створюємо чи відкриваємо файл
//
iFileHandle = FileCreate(SaveDialog1->FileName);
if(iFileHandle<0){
stribu=”Не можу відкрити файл, помилка ” + IntToStr(iFileHandle);
Application->MessageBox(stribu.c_str(), “Файлова помилка”);
//
// файл залишився невідкритим
//
goto ENN;};
Label6 -> Caption = “Записую в файл”; // просто діагностичне повідомлення
i1=Start_adr;
i3=0;
//
// i1, i2 – початкова і кінцева адреса адресної зони
// i3 – адресне зміщення біт 31…16
//
K01: i2=i1+15;if((i1 & 0xffff0000)==(i2 & 0xffff0000))goto K02;
i2=(i1 & 0xffff0000)+0xffff;
//
// визначені початкова і кінцева адреса
//
K02: masko1[0]=’:’;
if((i1 & 0xffff0000)==i3)goto K03; // не треба нове зміщення
masko1[1]=’0′;masko1[2]=’2′;
masko1[3]=masko1[4]=masko1[5]=masko1[6]=’0′;
masko1[7]=’0′;masko1[8]=’4′;
ToHex4(&masko1[9],((i1 >> 16) & 0xffff));
ksum=(0x100-(6+((i1 >> 24) & 0xff) + ((i1 >> 16) & 0xff))) & 0xff;
ToHex2(&masko1[13],ksum);
masko1[15]=0xd;masko1[16]=0xa;i3=i1 & 0xffff0000;
//
// записати цей блок зміщення адреса, його довжина 17 байт
//
iBytesRead = FileWrite(iFileHandle, masko1, 17);
if(iBytesRead!=17){
stribu=”Не записав, помилка ” + IntToStr(iFileHandle);
goto POMYL;};
K03: ksum=i2-i1+1;
masko1[0]=’:’;ToHex2(&masko1[1],ksum);
ToHex4(&masko1[3],(i1 & 0xffff));
ksum=ksum+((i1 >> 8) & 0xff) + (i1 & 0xff);
ToHex2(&masko1[7],0);i4=9;
K04: dann=Zagbuf[i1];
ksum=ksum+dann;ToHex2(&masko1[i4],dann);i4=i4+2;
if(i1<i2){i1++;goto K04;};
ksum=0x100-(ksum & 0xff);
ToHex2(&masko1[i4],ksum);i4=i4+2;
masko1[i4]=0xd;masko1[i4+1]=0xa;i4=i4+2;
//
// записати в файл блок довжиною i4 байт
//
iBytesRead = FileWrite(iFileHandle, masko1, i4);
if(iBytesRead!=i4){
stribu=”Не записав, помилка ” + IntToStr(iFileHandle);
goto POMYL;};
i2++;i1=i2;i2=i1+15;
if((i1+1) > dovzinfil)goto K05;
if((i2+1) > dovzinfil)i2=dovzinfil-1;
if(i2<i1)goto K05;
if((i1 & 0xffff0000)==(i2 & 0xffff0000))goto K02;
i2=(i1 & 0xffff0000)+0xffff;goto K02;
K05: masko1[0]=’:’;ToHex4(&masko1[1],0);ToHex4(&masko1[5],1);
ToHex2(&masko1[9],0xff);
masko1[11]=0xd;masko1[12]=0xa;
//
// записати в файл блок закінчення довжиною 13 байт
//
iBytesRead = FileWrite(iFileHandle, masko1, 13);
if(iBytesRead!=13){
stribu=”Не записав, помилка ” + IntToStr(iFileHandle);
goto POMYL;};
goto CLOF;
POMYL: Application->MessageBox(stribu.c_str(), “Файлова помилка”);
FileClose(iFileHandle); goto ENN;
//
// Закінчився файл
//
CLOF: FileClose(iFileHandle); goto ENN;
ENN: Label6 -> Caption = ” “; // просто стираємо діагностичне повідомлення
}
}
//—————————————————————————
//
// далі – наші обслуговуючі підпрограми
//
void ToHex4(char *buf, int dan)
{
ToHex2(&buf[0],((dan >> 8) & 0xff));
ToHex2(&buf[2],(dan & 0xff));
}
//—————————————————————————
void ToHex2(char *buf, int dan)
{
ToHex1(&buf[0],((dan >> 4) & 0xf));
ToHex1(&buf[1],(dan & 0xf));
}
void ToHex1(char *buf, int dan)
{
if(dan<10){buf[0]=dan + ‘0’;}
else {buf[0]=(dan-10) + ‘A’;};
}
int AnalHex4 (BYTE *buf)
{
BYTE dan1,dan2;
int dan3;
dan1=AnalHex2(buf);
dan2=AnalHex2(&buf[2]);
dan3=dan1;
dan3=(dan3 << 8) + dan2;
return(dan3);
}
BYTE AnalHex2 (BYTE *buf)
{
BYTE dan1,dan2;
dan1=AnalHex1(buf);
dan2=AnalHex1(&buf[1]);
dan1= (dan1 << 4) + dan2;
return(dan1);
}
BYTE AnalHex1(BYTE *buf)
{
BYTE dann;
if((buf[0]>=’0′) & (buf[0]<=’9′)){dann=buf[0] & 0xf; goto K1;};
if((buf[0]>=’A’) & (buf[0]<=’F’)){dann=buf[0]-‘A’+10;goto K1;};
if((buf[0]>=’a’) & (buf[0]<=’f’)){dann=buf[0]-‘a’+10;goto K1;};
sinterror=1;
K1: return(dann);
}
Весь список статей нашого сайту, корисних для програмістів, знаходиться ось тут