Попытаюсь разобраться со структурой стартового файла startup.s
для процессоров Cortex-M в среде Keil MDK-ARM и понять, как запускается процессор, на примере файла инициализации для отечественных процессоров 1986ВЕ1Т, которые являются аналогом ядра Cortex-M1.
Структура файла
Стартовый файл написан на ассемблере. Его можно разделить на несколько основных частей:
- Объявление области стека (stack)
- Объявление области кучи (heap)
- Таблица векторов прерываний
- Код обработчика сброса (reset handler)
- Код остальных обработчиков исключений
uVision позволяет настраивать часть параметров (размер стека и кучи) с помощью специального визуального редактора. Он активируется вкладкой с названием Configuration Wizard в нижней части окна.
Ниже я буду приводить небольшие участки кода из этого файла. Весь файл можно найти в программной библиотеке для этих процессоров.
Область стека
Ассемблерный код делиться на секции с использованием директивы AREA
. Рассмотрим, как происходит объявление области стека:
Stack_Size EQU 0x00001000 AREA STACK, NOINIT, READWRITE, ALIGN=3 Stack_Mem SPACE Stack_Size __initial_sp
Первая строка объявляет константу Stack_Size равной 0x00001000
(4096 байт). Директива EQU
подобна директиве препроцессора #define
.
Далее идет объявление области стека. Директивой AREA
создается отдельная секция в памяти с названием STACK. За названием следуют атрибуты:
- NOINIT — данные в секции заполняются нулями
- READWRITE — секция доступна на чтение и запись
- ALIGN=3 — выравнивание секции по границе 8 байт (2^3)
Следующая строка выделяет пространство заданного размера в области стека. Директива SPACE
просто резервирует заполненное нулями место в памяти.
Последняя строка создает метку __initial_sp
, которая в дальнейшем будет использоваться в таблице векторов. Метка приравнивается следующему адресу после области стека. Так как стек растет вниз, она будет служить начальным значением указателя стека.
Область кучи
По тому же принципу выделяется место для области кучи:
Heap_Size EQU 0x00001000 AREA HEAP, NOINIT, READWRITE, ALIGN=3 __heap_base Heap_Mem SPACE Heap_Size __heap_limit
Heap_size — константа, определяющая размер кучи. Затем создает секция HEAP. Отличие заключается в том, что у кучи есть две метки, которые указывают на начало и на конце кучи.
В конце стартового файла есть код, который в зависимости от того, используется ли библиотека ARM Microlib, либо просто экспортирует ей указатели стека и кучи, либо выполняется ряд дополнительных манипуляций с этими указателями.
Таблица векторов
Следующая секция — таблица векторов прерываний. Секция называется RESET.
AREA RESET, DATA, READONLY EXPORT __Vectors __Vectors DCD __initial_sp ; Top of Stack DCD Reset_Handler ; Reset Handler DCD NMI_Handler ; NMI Handler DCD HardFault_Handler ; Hard Fault Handler
Ее атрибуты:
- DATA — секция содержит данные, а не инструкции. Таблица векторов содержит только адреса обработчиков и начальное значение указателя стека.
- READONLY — защищает эту секцию от перезаписи программой.
Секция размещается перед секцией CODE во Flash памяти. Обычно это адрес 0x08000000
. Подробнее об адресации указывается в документации на процессор. Для 1986ВЕ1Т этот адрес равен 0x00000000
. Так как таблица помещается в начале памяти, то с нее процессор и начинает свою работу.
Таблица векторов содержит:
- Начальное значение указателя стека
- Адрес обработчика сброса, то есть откуда будет выполняться код после сброса процессора
- Адреса обработчиков всех остальных исключений и прерываний
Первая строка DCD __initial_sp
сохраняет значение указателя стека. Инструкция DCD
сохраняет 32-битное слово в памяти.
Следующая строка DCD Reset_Handler
сохраняет адрес обработчика сброса. Это обработчик объявляется ниже в стартовом файле.
Следующие строки записывают адреса различных прерываний, таких как HardFault_Handler и другие. После этого идут «внешние» прерывания. Слово «внешние» относится к ядру ARM-процессора. К ним относятся все прерывания периферии: таймеры, DMA, интерфейсы связи и так далее.
Таблица векторов и, особенно, первые две строки являются неотъемлемой составляющей для запуска процессора, а также работы инструкций PUSH/POP
. Это связано с тем, что процессор при запуске копирует первый элемент в регистр стека MSP (Main Stack Pointer), а вторую в регистр счетчика программы PC (Program Counter) и начинает выполнение программы с этого адреса.
Обработчик сброса
После объявления таблицы векторов начинается фактический код. Он находится в секции с атрибутом CODE
.
AREA |.text|, CODE, READONLY ; Reset Handler Reset_Handler PROC EXPORT Reset_Handler [WEAK] IMPORT SystemInit IMPORT __main LDR R0, =SystemInit BLX R0 LDR R0,=__main BX R0 ENDP
Директива AREA
объявляет секцию .text, которая содержит содержит код программы. Название секции используется общепринятое, но может быть любым другим. Эта секция имеет атрибут «только для чтения», чтобы избежать перезаписи данных программой.
Сначала вызывается функция SystemInit, которая выполняет начальную инициализацию процессора, а затем вызывается функция main
. Таким образом контроль передается функции main
основной программы.
Обработчики исключений
В процессе выполнения программы могут возникать исключения и прерывания, которые нуждаются в обработке. В стартовом файле объявлены функции-заглушки для всех прерываний процессора. Например, NMI_Handler:
NMI_Handler PROC EXPORT NMI_Handler [WEAK] B . ENDP
Директива PROC
объявляет начало функции. Следующая строка EXPORT
делает доступной для остальной части программы метку этой функции. Атрибут [WEAK]
говорит о том, что эта функция может быть переопределена где-нибудь в другом месте проекта. Это позволяет определять свои собственные обработчики прерываний.
Эти функции-заглушки имеют только одну инструкцию B
. Она выполняет переход на свой же адрес, тем самым генерируя бесконечный цикл.
Директива ENDP
обозначает конец функции.
Разное
Есть еще две директивы, которые не были упомянуты ранее.
- PRESERVE8 — указывает компоновщику сохранять 8-байтное выравнивание стека. Это требование Arm Architecture Procedure Call Standard (AAPCS).
- THUMB — указывает ассемблеру интерпретировать последующие инструкции, как THUMB-инструкции.
За основу этого текста была взята статья автора Gopal Amlekar с сайта ARM Community: Decoding the Startup file for Arm Cortex M4