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

Скрипт компоновщика (linker script)

Компилятор превращает каждый файл в набор секций, но не знает, по каким адресам их положить. Это решает компоновщик, а инструкции ему даёт скрипт компоновщика: какая память есть у микроконтроллера, какие секции куда идут и какие символы нужны коду запуска.

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

Скрипт компоновщика (linker script, файл .ld) — текстовый файл с инструкциями для компоновщика GNU ld: какая память есть у микроконтроллера, по каким адресам разместить код, константы, переменные и стек и какие символы передать программе. Компоновщик берёт секции, которые сделал компилятор, и раскладывает их по этим правилам в итоговый файл прошивки.

Основные команды

КомандаНазначение
MEMORYобласти памяти: имя, атрибуты, начало (ORIGIN) и размер (LENGTH)
SECTIONSкакие входные секции в какую выходную секцию и в какую область
ENTRYточка входа программы
присваиваниясимволы для кода: границы секций, вершина стека
ASSERTпроверка условия при сборке

Пример: STM32F103xB

В шаблоне ST из пакета CMSIS для STM32F103xB:

  • MEMORY: FLASH (rx) с адреса 0x08000000 длиной 128K и RAM (xrw) с 0x20000000 длиной 20K;
  • ENTRY(Reset_Handler) — точка входа;
  • _estack = 0x20004FFF — вершина стека у конца ОЗУ. Сама ОЗУ кончается на 0x20005000 (0x20000000 + 20 КБ), и значение из шаблона ST не кратно 8, хотя AAPCS требует, чтобы на границах вызовов указатель стека был выровнен по 8 байтам. При установленном бите STKALIGN ядро выравнивает стек при входе в исключение, но в своём скрипте вершину лучше задать как ORIGIN(RAM) + LENGTH(RAM);
  • _Min_Heap_Size = 0x200 и _Min_Stack_Size = 0x400 — минимальный запас под кучу и стек, который скрипт проверяет при сборке;
  • таблица векторов, код и константы — в >FLASH, неинициализированные переменные .bss — в >RAM.

Инициализированные переменные .data записаны как >RAM AT> FLASH: их адрес при работе — в ОЗУ, а начальные значения хранятся во флеш-памяти. Разница этих адресов описана в статье о LMA и VMA; копирование .data и обнуление секции .bss выполняет код запуска, используя символы, которые определил скрипт.

Как подключить

Ключ -T файл.ld заменяет скрипт по умолчанию. Для AVR в binutils есть готовые скрипты под каждое семейство, и avr-gcc выбирает нужный сам; для Cortex-M скрипт почти всегда лежит в проекте. Результат размещения — адреса и размеры секций и символов — смотрят в map-файле. Подробный разбор каждой строки, своих секций и типичных ошибок — в статье о скриптах компоновщика.

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

Где взять скрипт компоновщика для своего микроконтроллера?

Для STM32 он лежит в шаблонах пакета CMSIS от ST для каждого кристалла, для AVR скрипты входят в binutils и выбираются автоматически по ключу -mmcu. Писать с нуля его почти никогда не нужно — берут готовый и правят под задачу: добавляют секцию для данных, сохраняемых при перезапуске, или смещают начало программы под загрузчик.

Как сдвинуть прошивку, чтобы освободить место под загрузчик?

Изменить ORIGIN и LENGTH области FLASH в команде MEMORY: например, начать программу на 16 КБ дальше и на столько же уменьшить длину. Вместе с этим нужно перенести таблицу векторов — на Cortex-M3 и M4 записать новый адрес в регистр VTOR (у Cortex-M0 этого регистра нет, и перенос таблицы зависит от средств конкретного микроконтроллера), — иначе прерывания уйдут в обработчики загрузчика.

Почему программа собирается, но не помещается в память?

Если секции не влезают в области MEMORY, компоновщик сообщает о переполнении области (region overflowed) и называет, на сколько байтов. Если сборка прошла, но стек при работе налезает на данные, полезно добавить в скрипт проверку ASSERT или раздел, резервирующий минимальный стек, как в шаблоне ST с _Min_Stack_Size.

Источники

  1. GNU ld 2.47 Documentation: MEMORY Command, Options (-T), Entry Point, Output Section LMA, Miscellaneous Commands — команда MEMORY описывает расположение и размер блоков памяти: name [(attr)] : ORIGIN = origin, LENGTH = len; ключ -T заменяет скрипт по умолчанию, а файл скрипта, указанный как входной, лишь дополняет основной; ENTRY задаёт точку входа; ASSERT(exp, message) прерывает компоновку с сообщением, если выражение равно нулю
  2. STMicroelectronics. cmsis-device-f1, коммит c8e9a4a: Source/Templates/gcc/linker/STM32F103XB_FLASH.ld — ENTRY(Reset_Handler); _estack = 0x20004FFF — конец ОЗУ; _Min_Heap_Size = 0x200, _Min_Stack_Size = 0x400; MEMORY: FLASH (rx) ORIGIN 0x08000000, LENGTH 128K; RAM (xrw) ORIGIN 0x20000000, LENGTH 20K; код и константы — >FLASH, инициализированные данные — >RAM AT> FLASH, .bss — >RAM, проверочная секция резервирует кучу и стек в ОЗУ
  3. GNU Binutils 2.47, исходные тексты: ld/ldlang.c — если область памяти переполнена, компоновщик сообщает, на сколько: «region `…' overflowed by N bytes»
  4. Arm. Procedure Call Standard for the Arm Architecture (AAPCS32), github.com/ARM-software/abi-aa, коммит 7850367 — Stack constraints at a public interface: SP mod 8 = 0, стек выровнен по двойному слову
  5. Arm. Cortex-M3 Devices Generic User Guide, DUI 0552A; Cortex-M0 Devices Generic User Guide, DUI 0497A — у Cortex-M3 регистр VTOR в блоке SCB переносит таблицу векторов; бит STKALIGN регистра CCR — выравнивание стека по 8 байтам при входе в исключение; в описании системных регистров Cortex-M0 регистра VTOR нет

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