Понимание linkerscript для микроконтроллера ARM Cortex-M
Я использую микроконтроллер STM32F746NG из STMicroelectronics. Это устройство основано на архитектуре ARM Cortex-M7. Я потратил довольно много времени на понимание linkerscript из примеров проектов. Я понял основы, но я все еще не могу понять большую часть этого. Пожалуйста, помогите мне понять эти части.
начало linkerscript
linkerscript начинается следующим образом:
/* 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 переходит к определению разделов. Первый раздел, определенный в 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
linkerscript определяет e_text
глобальный символ, представляющий адрес, где код программы во flash концы.
постоянного данных
данные только для чтения также оказываются во флэш-памяти (нет смысла помещать их в ОЗУ, которое является изменчивым). В linkerscript определяет, что .rodata
раздел должен быть в flash:
/****************************/
/* CONSTANT DATA */
/****************************/
.rodata :
{
. = ALIGN(4);
*(.rodata) /* .rodata sections (constants, strings, etc.) */
*(.rodata*) /* .rodata* sections (constants, strings, etc.) */
. = ALIGN(4);
} >FLASH
таинственные разделы во flash
после определения, куда должны идти постоянные данные только для чтения, linkerscript определяет, что несколько "таинственных" разделов также должны заканчиваться во flash:
.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
I понятия не имею, что это за секции. Пусть это будет первый вопрос. Что это за разделы и в каких объектных файлах они отображаются? Как вы знаете, linkerscript должен связать вместе некоторые объектные файлы. Я понятия не имею, в каких объектных файлах существуют эти таинственные разделы:
.ARM.extab
.ARM
.preinit_array
.init_array
.fini_array
это конец ассигнований на Flash-память. Linkerscript продолжается с определением разделов, которые заканчиваются в ОЗУ.
разделы в памяти
на .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
linkerscript определяет также :
/****************************/
/* 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
видимо этот раздел не используется сразу. Он определяется только для проверки, имеет ли ОЗУ достаточно места для стека и кучи. Ошибка компоновщика возникает, когда это не так (.
превышает верхний адрес ОЗУ).
конец linkerscript
вот как заканчивается linkerscript. И честно говоря, я понятия не имею, что он делает. Итак, это второй вопрос:Что означает следующее?
/* Remove information from the standard libraries */
/DISCARD/ :
{
libc.a ( * )
libm.a ( * )
libgcc.a ( * )
}
.ARM.attributes 0 : { *(.ARM.attributes) }
}
/* END OF LINKERSCRIPT */
2 ответов
.РУКА.extab и .РУКА.exidx связаны с размоткой. Вы можете найти более подробную информацию здесь http://infocenter.arm.com/help/topic/com.arm.doc.ihi0044e/index.html. Они вам не нужны, если вы не заботитесь о размотке (размотка полезна для исключения C++ и для отладки).
эти символы связаны с конструктором C / C++ и деструктором запуска и сноса кода, который вызывается до / после main (). Разделы .инициализация. ,ctors,.preinit_array, and .init_array связаны с инициализацией объектов C / C++ и разделов .Фини .fini_array, and .dtors для снятия. Символы начала и конца определяют начало и конец разделов кода, связанных с такими операциями, и на них можно ссылаться из других частей кода поддержки среды выполнения.
The .preinit_array and .разделы init_array содержат массивы указателей на функции, которые будут вызываться при инициализации. Этот .fini_array-это массив функций, которые будут вызываться при уничтожении. Предположительно, начальные и конечные метки используются для обхода этих списков.
-
куча: не совсем, эта часть позволяет зарезервировать некоторое пространство для кучи и некоторое пространство для стека. Очевидно, что возникает ошибка, если сумма зарезервированных областей выходит за пределы границ ОЗУ. Вот пример:
_Min_Heap_Size = 0; /* требуемое количество кучи / _Min_Stack_Size = 0x400; / необходимое количество стека */
._user_heap_stack : { . = Выровнять(4); PROVIDE (end = . ); PROVIDE (_end = . ); . = . + _Min_Heap_Size; . = . + _Min_Stack_Size; . = Выровнять(4); } >Оперативной памяти
для связывания библиотек я предпочитаю другую нотацию, это просто пример для проекта bare-metal no RTOS C++ : Группа (libgcc.в libc_nano.в libstdc++в _nano.в libm.в libcr_newlib_nohost.в кртп.o crtn.o crtbegin.o crtend.o)
простым языком я могу объяснить вам, что сценарий компоновщика предоставляет нам три основные вещи:
- точка входа
- выполнить времени, адреса в основной памяти.
- копировать процедуру
Итак, давайте рассмотрим один пример,
предположим, что у нас есть N число .c файлы в нашем проекте. Теперь после компиляции каждый файл содержит свои собственные единицы перевода, называемые объектными файлами.
каждый объектный файл содержит .text
раздел / сегмент, которые содержат фактический код. И.раздел/сегмент данных для данных.
объединить все .text
раздел каждой единицы перевода сценарий компоновщика предоставляет некоторые конкретные команды для того же самого. Это то же самое для как что ж.
после объединения всех объектных файлов окончательный исполняемый файл готов к использованию.
теперь переходим к некоторому парадоксу...
точка входа в случае серии Cortex-M-это просто ResetISR. После выполнения функции ResetISR и инициализации других маскируемых прерываний в SoC следующим шагом является процедура копирования.
процедура копирования вниз ничего, кроме копирования .data
раздел в ОЗУ (который включает в себя интересные .bss
раздел даже, но я не рассматриваю эту часть прямо сейчас).
копирование из ПЗУ в ОЗУ имеет важное значение, потому что фактический файл ELF всегда хранится в ПЗУ.
после выполнения всех этих связанных с запуском вещей, теперь мы можем назвать наш