Понимание линкерскрипта для микроконтроллера ARM Cortex-M
Я использую микроконтроллер STM32F746NG от STMicroelectronics. Это устройство основано на архитектуре ARM Cortex-M7. Я потратил довольно много времени на понимание линкерскрипта из примеров проектов. Я понял основы, но я все еще не могу понять большие части этого. Пожалуйста, помогите мне понять эти части.
Начало линкерскрипта
Ссылочный скрипт начинается следующим образом:
/* Entry Point */
ENTRY(Reset_Handler) /* The function named 'Reset_Handler' is defined */
/* in the 'startup.s' assembly file. */
/* Highest address of the user mode stack */
/* Remember: the stack points downwards */
_estack = 0x20050000; /* End of RAM */
/* Generate a link error if heap and stack don't fit into RAM */
_Min_Heap_Size = 0x200; /* Required amount of heap */
_Min_Stack_Size = 0x400; /* Required amount of stack */
/* --------------------------------------------------------------------*/
/* MEMORY AREAS */
/* --------------------------------------------------------------------*/
MEMORY
{
/* FLASH MEMORY */
/* ------------ */
/* Remember: the flash memory on this device can */
/* get accessed through either the AXIM bus or the */
/* ITCM bus. Accesses on the ITCM bus start at */
/* address 0x0020 0000. Accesses on the AXIM bus */
/* at address 0x0800 0000. */
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1024K
/* FLASH (rx) : ORIGIN = 0x00200000, LENGTH = 1024K */
/* RAM MEMORY */
/* ---------- */
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 320K
}
Векторная таблица и программный код
После определения областей памяти, линкерскрипт приступает к определению разделов. Первый раздел, определенный в linkerscript - это таблица векторов. Это должно закончиться в первых байтах флэш-памяти.
/* --------------------------------------------------------------------*/
/* OUTPUT SECTIONS */
/* --------------------------------------------------------------------*/
SECTIONS
{
/****************************/
/* VECTOR TABLE */
/****************************/
.isr_vector :
{
. = ALIGN(4);
KEEP(*(.isr_vector)) /* Vector Table */
. = ALIGN(4);
} >FLASH
После вставки таблицы векторов пришло время для программного кода:
/****************************/
/* PROGRAM CODE */
/****************************/
.text :
{
. = ALIGN(4);
*(.text) /* .text sections (code) */
*(.text*) /* .text* sections (code) */
*(.glue_7) /* Glue ARM to Thumb code */
*(.glue_7t) /* Glue Thumb to ARM code */
*(.eh_frame)
/* Note: The function ‘.text.Reset_Handler’ is one of the *(.text*) sections, */
/* such that it gets linked into the output .text section somewhere here. */
/* We can verify the exact spot where the Reset_Handler section is positioned, by */
/* examining the second entry of the vector table. */
/* A test has given the following results:
/* FLASH (rx) : ORIGIN = 0x0800 0000 ==> Reset_Handler = 0x0800 1C91 */
/* FLASH (rx) : ORIGIN = 0x0020 0000 ==> Reset_Handler = 0x0020 1CB9 */
/*
/* In both cases, the Reset_Handler section ends up a few hundred bytes after the */
/* vector table in Flash. But in the first case, the “Reset_Handler” symbol points */
/* to the Reset-code through AXIM-interface, whereas in the latter case it points */
/* to the Reset-code through the ITCM-interface. */
KEEP (*(.init))
KEEP (*(.fini))
. = ALIGN(4);
_etext = .; /* Define a global symbol at end of code */
} >FLASH
Ссылочный скрипт определяет e_text
глобальный символ, представляющий адрес, на котором заканчивается программный код во флэш-памяти.
Постоянные данные
Данные, доступные только для чтения, также попадают во флэш-память (нет смысла помещать их в оперативную память, которая является энергозависимой). Линкерскрипт определяет, что .rodata
раздел должен быть во флеше:
/****************************/
/* CONSTANT DATA */
/****************************/
.rodata :
{
. = ALIGN(4);
*(.rodata) /* .rodata sections (constants, strings, etc.) */
*(.rodata*) /* .rodata* sections (constants, strings, etc.) */
. = ALIGN(4);
} >FLASH
Таинственные разделы во флеше
После определения того, куда должны поступать постоянные данные только для чтения, скрипт linkerscript определяет, что несколько "загадочных" разделов также должны заканчиваться во флэш-памяти:
.ARM.extab :
{
*(.ARM.extab* .gnu.linkonce.armextab.*)
} >FLASH
.ARM :
{
__exidx_start = .;
*(.ARM.exidx*)
__exidx_end = .;
} >FLASH
.preinit_array :
{
PROVIDE_HIDDEN (__preinit_array_start = .);
KEEP (*(.preinit_array*))
PROVIDE_HIDDEN (__preinit_array_end = .);
} >FLASH
.init_array :
{
PROVIDE_HIDDEN (__init_array_start = .);
KEEP (*(SORT(.init_array.*)))
KEEP (*(.init_array*))
PROVIDE_HIDDEN (__init_array_end = .);
} >FLASH
.fini_array :
{
PROVIDE_HIDDEN (__fini_array_start = .);
KEEP (*(SORT(.fini_array.*)))
KEEP (*(.fini_array*))
PROVIDE_HIDDEN (__fini_array_end = .);
} >FLASH
Я понятия не имею, что это за разделы. Так что пусть это будет первый вопрос. Что это за разделы и в каких объектных файлах они отображаются? Как вы знаете, скрипт линкера должен связать вместе несколько объектных файлов. Я понятия не имею, в каких объектных файлах существуют эти загадочные разделы:
.ARM.extab
.ARM
.preinit_array
.init_array
.fini_array
Это конец выделения во флэш-память. Ссылочный скрипт продолжается с определения разделов, которые попадают в ОЗУ.
Разделы в оперативной памяти
.data
а также .bss
разделы мне понятны. Нет вопросов по этому поводу.
/****************************/
/* INITIALIZED DATA */
/****************************/
_sidata = LOADADDR(.data);
.data :
{
. = ALIGN(4);
_sdata = .; /* create a global symbol at data start */
*(.data) /* .data sections */
*(.data*) /* .data* sections */
. = ALIGN(4);
_edata = .; /* define a global symbol at data end */
} >RAM AT> FLASH
/****************************/
/* UNINITIALIZED DATA */
/****************************/
. = ALIGN(4);
.bss :
{
_sbss = .; /* define a global symbol at bss start */
__bss_start__ = _sbss;
*(.bss)
*(.bss*)
*(COMMON)
. = ALIGN(4);
_ebss = .; /* define a global symbol at bss end */
__bss_end__ = _ebss;
} >RAM
Ссылочный скрипт определяет также ._user_heap_stack
раздел:
/****************************/
/* USER_HEAP_STACK SECTION */
/****************************/
/* User_heap_stack section, used to check that there is enough RAM left */
._user_heap_stack :
{
. = ALIGN(8);
PROVIDE ( end = . );
PROVIDE ( _end = . );
. = . + _Min_Heap_Size;
. = . + _Min_Stack_Size;
. = ALIGN(8);
} >RAM
Видимо, этот раздел не используется сразу. Он определяется только для проверки, достаточно ли в ОЗУ места для стека и кучи. Ошибка компоновщика выдается, когда это не так (.
превышает верхний адрес ОЗУ).
Конец линкерскрипта
Так заканчивается линкерскрипт. И, честно говоря, я понятия не имею, что он делает. Итак, это второй вопрос: что означает следующее?
/* Remove information from the standard libraries */
/DISCARD/ :
{
libc.a ( * )
libm.a ( * )
libgcc.a ( * )
}
.ARM.attributes 0 : { *(.ARM.attributes) }
}
/* END OF LINKERSCRIPT */
2 ответа
.ARM.extab и.ARM.exidx относятся к размотке. Вы можете найти больше информации здесь http://infocenter.arm.com/help/topic/com.arm.doc.ihi0044e/index.html. Они вам не нужны, если вас не волнует разматывание (разматывание полезно для исключения C++ и для отладки).
Эти символы относятся к запуску конструктора C / C++ и деструктора, а также к разрушению кода, который вызывается до / после main(). Разделы с именами.init, .ctors, .preinit_array и.init_array имеют отношение к инициализации объектов C / C++, а разделы.fini, .fini_array и.dtors предназначены для разборки. Символы начала и конца определяют начало и конец разделов кода, связанных с такими операциями, и на них могут ссылаться другие части кода поддержки времени выполнения.
Разделы.preinit_array и.init_array содержат массивы указателей на функции, которые будут вызываться при инициализации..Fini_array - это массив функций, которые будут вызываться при уничтожении. Предположительно метки начала и конца используются для обхода этих списков.
куча: не совсем, эта часть позволяет зарезервировать некоторое пространство для кучи и некоторое место для стека. Очевидно, возникает ошибка, если сумма зарезервированных областей выходит за границы ОЗУ. Это пример:
_Min_Heap_Size = 0; / * необходимое количество кучи / _Min_Stack_Size = 0x400; / необходимое количество стека * /
._user_heap_stack: {. = ALIGN (4); ПРЕДОСТАВИТЬ (конец = .); ПРЕДОСТАВИТЬ ( _end = .);, знак равно + _Min_Heap_Size;, знак равно + _Min_Stack_Size;, = ALIGN(4); } >RAM
чтобы связать библиотеки, я предпочитаю другую нотацию, это всего лишь пример для проекта без ОС RTOS C++: GROUP(libgcc.a libc_nano.a libstdC++_nano.a libm.a libcr_newlib_nohost.a crti.o crtn.o crtbegin.o crtend.o)
Прежде всего, ваш подход к пониманию этой концепции неверен; это то, во что я верю. Я столкнулся с подобной проблемой во время понимания этой концепции.
Говоря простым языком, я могу объяснить вам, что скрипт компоновщика предоставляет нам три основные вещи:
- Точка входа
- Адреса времени выполнения в основной памяти.
- Процедура копирования
Итак, давайте рассмотрим один пример,
Предположим, что у нас есть N файлов.c в нашем проекте. Теперь после компиляции каждый файл содержит свои собственные единицы перевода, называемые объектными файлами.
Каждый объектный файл содержит .text
раздел / сегмент, который содержит фактический код. И.data раздел / сегмент для данных.
Совместить все .text
В разделе каждой единицы перевода скрипт компоновщика предоставляет некоторые конкретные команды для одного и того же. Это то же самое для .data
раздел также.
После объединения всех объектных файлов конечный исполняемый файл готов к использованию.
Теперь подходит к некоторому парадоксу...
Точкой входа в случае серии Cortex-M является просто ResetISR. После выполнения функции ResetISR и инициализации других маскируемых прерываний в SoC, следующим шагом является процедура копирования.
Процедура копирования не что иное, как копировать .data
раздел в оперативную память (которая включает в себя .bss
раздел даже, но я не рассматриваю эту часть прямо сейчас).
Копирование из ПЗУ в ОЗУ необходимо, поскольку фактический файл ELF всегда хранится в ПЗУ.
После выполнения всех этих вещей, связанных с запуском, мы можем позвонить нашим main()
функция.