Перейти к содержанию

LMA и VMA

Глобальная переменная int x = 5 в прошивке микроконтроллера живёт в ОЗУ, но после включения питания ОЗУ пусто. Значит, пятёрка где-то хранится — во флеш-памяти — и перед main копируется на место. У такой секции два адреса, и компоновщик называет их LMA и VMA.

Обновлено Редакция GAW

LMA (load memory address) и VMA (virtual memory address) — два адреса, которые компоновщик GNU ld хранит для каждой выходной секции. VMA — адрес, по которому секция находится во время выполнения программы. LMA — адрес, по которому она лежит в загружаемом образе. Для большинства секций они совпадают (LD manual, разд. 3.1).

Пример: секция .data

СекцияГде хранится (LMA)Где работает (VMA)Кто переносит
.text — кодфлеш-памятьфлеш-памятьникто
.rodata — константыфлеш-памятьфлеш-памятьникто
.data — переменные с начальными значениямифлеш-памятьОЗУкод запуска копирует
.bss — нулевые переменныенигдеОЗУкод запуска обнуляет

Начальные значения инициализированных переменных хранятся во флеш-памяти (это LMA), а работают переменные в ОЗУ (это VMA). До вызова main код запуска копирует их из флеш-памяти в ОЗУ и обнуляет секцию .bss. Руководство ld приводит именно такой пример и цикл копирования на C.

Как задать в скрипте

VMA задаётся адресом или областью памяти выходной секции, LMA — ключевым словом AT(выражение) или AT>область (LD manual, разд. 3.6.8.2). Типичная запись для микроконтроллера — .data : { … } > RAM AT> FLASH. Если LMA не указан, компоновщик подбирает его сам: для секции с явным адресом LMA равен VMA. Счётчик адреса . внутри секции содержит VMA, поэтому символы вроде _sdata и _edata — это адреса в ОЗУ, а адрес-источник берут функцией LOADADDR().

Как проверить

  • objdump -h выводит для каждой секции оба адреса — колонки VMA и LMA.
  • Map-файл показывает, куда компоновщик положил каждую секцию: адрес выполнения и, если он отличается, адрес загрузки (load address).

Полный разбор скрипта компоновщика для STM32F103 и AVR — в статье о скриптах компоновщика, а форматы выходных файлов и то, какие адреса попадают в HEX, — в статье о файлах прошивки.

Частые вопросы

Как в коде узнать, откуда копировать .data?

Скрипт компоновщика определяет символ с LMA секции, обычно через функцию LOADADDR: в скриптах GD32 и STM32 это строка _sidata = LOADADDR(.data) (AN200, разд. 2.3). Код запуска копирует байты от этого адреса во флеш-памяти до VMA секции в ОЗУ, а размер берёт из разности символов начала и конца секции.

Как увидеть оба адреса .data — в ОЗУ и во флеш-памяти?

В map-файле GNU ld: в строке выходной секции стоит её адрес выполнения (VMA), а если LMA отличается, рядом указан и адрес загрузки — load address. Счётчик адреса и символы внутри секции содержат VMA, а в HEX-файл и образ прошивки попадает содержимое по LMA. Для .data адреса разные, для .text совпадают. Обе колонки выводит и objdump -h.

Где это нужно, кроме .data?

Везде, где код или данные работают не там, где хранятся: функции, которые выполняются из ОЗУ во время стирания флеш-памяти, код в быстрой памяти TCM — AN200 разбирает такую секцию для TCM SRAM (разд. 3.2). Для таких секций в скрипте пишут > RAM AT> FLASH и копируют их в коде запуска так же, как .data.

Источники

  1. GNU. LD manual, 3.1 Basic Linker Script Concepts — у каждой загружаемой или размещаемой выходной секции два адреса: VMA — адрес, который секция имеет при выполнении, и LMA — адрес, по которому она загружается; обычно они совпадают; пример различия — секция данных, загружаемая в ПЗУ и копируемая в ОЗУ при старте: адрес в ПЗУ — LMA, в ОЗУ — VMA; секции объектного файла показывает objdump -h
  2. GNU. LD manual, 3.6.8.2 Output Section LMA — VMA задаётся адресом выходной секции, LMA — ключевыми словами AT (выражение) или AT> (область памяти); без них компоновщик подбирает LMA эвристикой; счётчик адреса содержит VMA, а не LMA; пример с секцией .mdata, загружаемой после .text, и кодом копирования данных из ПЗУ в ОЗУ и обнуления .bss
  3. GigaDevice. AN200 GD32 Embedded Builder Linker Script Files Introduction for Arm Cortex-M Processor, Rev 1.0 (Dec. 2025) — разд. 2.3.1: VMA — адрес, по которому секция работает, LMA — физический адрес, по которому она загружается; способы задать LMA — совпадение с VMA, синтаксис AT, упрощённая запись, выравнивание LMA; разд. 2.3: _sidata = LOADADDR(.data) — адрес загрузки .data для кода запуска; разд. 3.2: секция .tcmsram с LOADADDR для данных в TCM SRAM, проверка по map-файлу

Нашли ошибку?