Программирование ARM ESP-IDF: формат файла образа приложения Thu, March 28 2024  

Поделиться

Нашли опечатку?

Пожалуйста, сообщите об этом - просто выделите ошибочное слово или фразу и нажмите Shift Enter.

ESP-IDF: формат файла образа приложения Печать
Добавил(а) microsin   

Двоичный образ приложения, который записывается в раздел OTA [2], состоит из следующих структур:

1. esp_image_header_t: структура, описывающая режим SPI flash и количество сегментов памяти.

Структура esp_image_header_t содержит 24 байта, и находится в самом начале (со смещением 0) файла образа. Пример содержимого структуры в файле образа приложения (дамп 24 первых байт двоичного образа прошивки):

esp image header t

E9 - магическое число, маркер начала структуры esp_image_header_t.

07 - 7 сегментов памяти.

02 - режим программирования DIO для SPI flash (02 соответствует ESP_IMAGE_SPI_MODE_DIO из перечисления esp_image_spi_mode_t).

22 - скорость SPI flash 20 МГц (2 соответствует ESP_IMAGE_SPI_SPEED_20M из перечисления esp_image_spi_freq_t), размер SPI flash 4 мегабайта (2 соответствует ESP_IMAGE_FLASH_SIZE_4MB из перечисления esp_image_flash_size_t).

02 04 38 40 - адрес входа в приложение 0x40380402.

EE - означает, что режим защиты памяти flash от записи запрещен.

00 00 00 - нагрузочная способность ножек обмена с памятью SPI.

05 00 - приложение для чипа ESP32-C3 (0x0005 соответствует ESP_CHIP_ID_ESP32C3 из перечисления esp_chip_id_t).

03 - минимально допустимая ревизия чипа.

00 00 00 00 00 00 00 00 - зарезервированные байты.

01 - после контрольной суммы добавлена цифровая подпись SHA256.

2. esp_image_segment_header_t: структура, описывающая каждый сегмент, его длину и размещение в памяти ESP32, за которой идут данные длиной data_len.

За структурой esp_image_header_t непосредственно следует структура esp_image_segment_header_t, состоящая из 8 байт, и за ней идут данные сегмента. Пример расшифровки дампа первого сегмента приложения (дамп первых 320 байт двоичного файла прошивки приложения):

esp image segment header t esp app desc t

20 00 10 3С - адрес загрузки первого сегмента 0x3C100020.

38 63 06 00 - размер первого сегмента 0x00066338 байт.

Далее идут байты 32 54 CD AB, по ним мы узнаем магическое число маркера ESP_APP_DESC_MAGIC_WORD (==0xABCD5432) начала структуры esp_app_desc_t с описанием приложения:

32 54 CD AB - константа 0xABCD5432 (ESP_APP_DESC_MAGIC_WORD).

00 00 00 00 - Secure version.

00 00 00 00 00 00 00 00 - два зарезервированных 32-битных слова, uint32_t reserv1[2].

66 61 62 33 39 37 32 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - версия приложения, строка "fab3972".

D0 90 D0 BB D0 B8 D1 81 D0 B0 0A 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - имя приложения, закодированная в UTF8 строка "Алиса".

31 32 3A 34 35 3A 31 31 00 00 00 00 00 00 00 00 - время компиляции "12:45:11".

41 70 72 20 20 33 20 32 30 32 33 00 00 00 00 00 - дата компиляции "Apr  3 2023".

76 34 2E 34 2E 31 2D 64 69 72 74 79 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - версия ESP-IDF "v4.4.1-dirty".

F7 65 9B 98 0D B9 F4 75 B9 43 CD 66 A9 D1 C0 54
2B E7 A3 D7 B9 96 44 E2 CD 5C 74 98 21 60 D7 B0 - 32 байта цифровой подписи SHA256.

00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 80 нулевых байт, представляющих uint32_t reserv2[20].

Структура esp_image_header_t:

/**
 * @brief Основной заголовок двоичного образа
 */
typedef struct
{
   uint8_t magic;              /*!< Магическая константа ESP_IMAGE_HEADER_MAGIC (0xE9) */
   uint8_t segment_count;      /*!< Количество сегментов памяти */
   uint8_t spi_mode;           /*!< Режим чтения flash (esp_image_spi_mode_t как uint8_t) */
   uint8_t spi_speed: 4;       /*!< Частота flash (esp_image_spi_freq_t как uint8_t) */
   uint8_t spi_size: 4;        /*!< Размер микросхемы flash (esp_image_flash_size_t как uint8_t) */
   uint32_t entry_addr;        /*!< Адрес входа */
   uint8_t wp_pin;             /*!< Ножка WP, когда выводы SPI установлены через efuse (считываются из ROM bootloader,
                                    IDF bootloader использует программное обеспечение для конфигурирования вывода WP,
                                    и устанавливает это поле в 0xEE=disabled) */
   uint8_t spi_pin_drv[3];     /*!< Нагрузочная способность выводов SPI flash (считывается из ROM bootloader) */
   esp_chip_id_t chip_id;      /*!< Идентификатор чипа */
   uint8_t min_chip_rev;       /*!< Минимальная ревизия чипа, поддерживаемая образом(1) */
   uint8_t reserved[8];        /*!< Зарезервированные байты в заголовке, в настоящее время не используются(2) */
   uint8_t hash_appended;      /*!< Если 1, то цифровая подпись SHA256 "simple hash" (всего образа) добавлена
                                    после контрольной суммы. Подпись включена в длину образа. Эта подпись
                                     отдельная для secure boot, и используется только для проверки целостности
                                    образа. Для подписанных образов secure boot эта подпись добавляется после
                                    этого (и simple hash добавлены в подписанные данные). */
} __attribute__((packed)) esp_image_header_t;

Примечания:

(1) Начиная с момента, когда Major и Minor ревизии чипа были представлены в eFuse чипа, это поле больше не используется. Однако из соображений совместимости разработчики оставили это поле и данные в нем. Вместо этого поля используйте min_chip_rev_full. ПО интерпретирует это поле как Major-версия чипов, и как Minor-версию для ESP32-C3.
(2) В первых 4 байтах поля reserved могут находится поля для uint16_t min_chip_rev_full и uint16_t max_chip_rev_full. В них кодируются минимальная и максимальная версии чипа следующим образом: major * 100 + minor. В этом случае поле reserved[4] идет за полями min_chip_rev_full и max_chip_rev_full.

Идентификаторы чипа, сохраняемые в поле chip_id:

/**
 * @brief ESP chip ID
 */
typedef enum
{
   ESP_CHIP_ID_ESP32   = 0x0000, /*!< ESP32    */
   ESP_CHIP_ID_ESP32S2 = 0x0002, /*!< ESP32-S2 */
   ESP_CHIP_ID_ESP32C3 = 0x0005, /*!< ESP32-C3 */
   ESP_CHIP_ID_ESP32S3 = 0x0009, /*!< ESP32-S3 */
   ESP_CHIP_ID_ESP32H2 = 0x000A, /*!< ESP32-H2 */  // ESP32H2-TODO: IDF-3475
   ESP_CHIP_ID_INVALID = 0xFFFF  /*!< Недопустимый chip ID (это значение было определено для гарантии,
                                      что у esp_chip_id_t размер всегда будет равен 2 байтам) */
} __attribute__((packed)) esp_chip_id_t;

Структура esp_image_segment_header_t:

/**
 * @brief Заголовок сегмента двоичного образа
 */
typedef struct
{
   uint32_t load_addr;         /*!< Адрес сегмента */
   uint32_t data_len;          /*!< Длина данных сегмента */
} esp_image_segment_header_t;

Смещение данных для каждого сегмента в образе вычисляется следующим образом:

Смещение для сегмента 0 = sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t).
Смещение для сегмента 1 = Смещение для сегмента 0 + длина сегмента 0 + sizeof(esp_image_segment_header_t).
Смещение для сегмента 2 = Смещение для сегмента 1 + длина сегмента 1 + sizeof(esp_image_segment_header_t).
...

Количество сегментов определено в поле segment_count структуры esp_image_header_t. Оно не может быть больше константы ESP_IMAGE_MAX_SEGMENTS (в настоящее время 16).

Чтобы получить список сегментов вашего образа, запустите следующую команду:

$ esptool.py --chip esp32 image_info build/app.bin
 
esptool.py v2.3.1
Image version: 1
Entry point: 40080ea4
13 segments
 
Segment 1: len 0x13ce0 load 0x3f400020 file_offs 0x00000018 SOC_DROM
Segment 2: len 0x00000 load 0x3ff80000 file_offs 0x00013d00 SOC_RTC_DRAM
Segment 3: len 0x00000 load 0x3ff80000 file_offs 0x00013d08 SOC_RTC_DRAM
Segment 4: len 0x028e0 load 0x3ffb0000 file_offs 0x00013d10 DRAM
Segment 5: len 0x00000 load 0x3ffb28e0 file_offs 0x000165f8 DRAM
Segment 6: len 0x00400 load 0x40080000 file_offs 0x00016600 SOC_IRAM
Segment 7: len 0x09600 load 0x40080400 file_offs 0x00016a08 SOC_IRAM
Segment 8: len 0x62e4c load 0x400d0018 file_offs 0x00020010 SOC_IROM
Segment 9: len 0x06cec load 0x40089a00 file_offs 0x00082e64 SOC_IROM
Segment 10: len 0x00000 load 0x400c0000 file_offs 0x00089b58 SOC_RTC_IRAM
Segment 11: len 0x00004 load 0x50000000 file_offs 0x00089b60 SOC_RTC_DATA
Segment 12: len 0x00000 load 0x50000004 file_offs 0x00089b6c SOC_RTC_DATA
Segment 13: len 0x00000 load 0x50000004 file_offs 0x00089b74 SOC_RTC_DATA
Checksum: e8 (valid)
Validation Hash: 407089ca0eae2bbf83b4120979d3354b1c938a49cb7a0c997f240474ef2ec76b (valid)

Вы также можете увидеть информацию о сегментах в логе ESP-IDF, когда загружается ваше приложение:

Для дополнительной информации о типах сегментов памяти и их диапазонах адресов см. руководство по используемому чипу (например, для ESP32-C3 это PDF-документ "ESP32-C3 Technical Reference Manual", раздел System and Memory -> Embedded Memory).

3. В образе есть один байт контрольной суммы, который находится после последнего сегмента. Этот байт записывается на границу памяти, выровненную на 16 байт, поэтому образу приложения может понадобиться дополнение.

4. Если установлено в 1 поле hash_appended структуры esp_image_header_t, то добавляется контрольная сумма SHA256. Значение хэша SHA256 вычисляется по диапазону от первого байта и данных SHA256. Длина данных SHA256 составляет 32 байта.

5. Если опция CONFIG_SECURE_SIGNED_APPS_SCHEME установлена в ECDSA, то у образа имеется 68 дополнительных байт для сигнатуры ECDSA, которые включают version word (4 байта) и signature data (64 байта).

6. Если опция CONFIG_SECURE_SIGNED_APPS_SCHEME установлена в RSA или ECDSA (V2), то у образа приложения будет дополнительный сектор сигнатуры размером 4 килобайта. Для дополнительной информации по этому сектору сигнатуры см. документацию [3].

[Описание приложения]

Сегмент DROM бинарника приложения начинается со структуры esp_app_desc_t, которая содержит специальные поля, описывающие приложение:

/**
 * @brief Описание приложения.
 */
typedef struct
{
   uint32_t magic_word;        /*!< Магическое слово ESP_APP_DESC_MAGIC_WORD (==0xABCD5432) */
   uint32_t secure_version;    /*!< Secure version */
   uint32_t reserv1[2];        /*!< reserv1 */
   char version[32];           /*!< Версия приложения */
   char project_name[32];      /*!< Имя проекта */
   char time[16];              /*!< Время компиляции */
   char date[16];              /*!< Дата компиляции */
   char idf_ver[32];           /*!< Версия IDF */
   uint8_t app_elf_sha256[32]; /*!< sha256 elf-файла */
   uint32_t reserv2[20];       /*!< reserv2 */
} esp_app_desc_t;

magic_word - 32-битная константа, маркер начала структуры описания приложения.
secure_version - см. описание технологии Anti-rollback [4].
version - см. App version в статье [5](*).
project_name - это имя приложения, заполненное из PROJECT_NAME(*).
time, date - время и дата момента компиляции.
idf_ver - версия ESP-IDF(*).
app_elf_sha256 - содержит хэш sha256 для ELF-файла приложения.

Примечание (*): ASCIIZ-строка, максимальная длина которой 32 символа, включая null-символ терминатора. Например, если длина PROJECT_NAME превышает 31 символ, то лишние символы отбрасываются.

Эта структура полезна для идентификации образов, загруженных по радиоканалу (Over-the-Air, OTA [4]), и у неё фиксированное смещение = sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t). Как только устройство примет первый фрагмент образа, содержащий эту структуру, у него уже будет вся информация об образе приложения, на основе которой может быть принято решение, должно ли обновление продолжиться, или нет.

Чтобы получить структуру esp_app_desc_t для текущего работающего приложения, используйте esp_app_get_description(). Чтобы получить структуру esp_app_desc_t для другого раздела OTA, используйте esp_ota_get_partition_description().

[Добавление пользовательской структуры в приложение]

Пользователи также имеют возможность применить аналогичную структуру с фиксированным смещением относительно начала образа. Для добавления пользовательской структуры к образу можно использовать следующий шаблон:

const __attribute__((section(".rodata_custom_desc"))) esp_custom_app_desc_t custom_app_desc = { ... }

Смещение для пользовательской структуры будет равно sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t) + sizeof(esp_app_desc_t).

Для гарантии, что пользовательская структура находится в образе, даже если она не используется, необходимо в файл CMakeLists.txt добавить target_link_libraries(${COMPONENT_TARGET} "-u custom_app_desc").

В заголовочном файле components/bootloader_support/include/esp_app_format.h описаны все типы данных, используемые для определения формата образа приложения. Для дополнительной информации см. документацию [1].

[Ссылки]

1. ESP32-C3 App Image Format site:espressif.com.
2. ESP32: таблицы разделов.
3. ESP-IDF Signature Block Format site:espressif.com.
4. ESP32: обновление по радиоканалу (OTA).
5. ESP32-C3 Miscellaneous System API.

 

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


Защитный код
Обновить

Top of Page