Понимание линкерскрипта для микроконтроллера 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 ответа

Решение
  1. .ARM.extab и.ARM.exidx относятся к размотке. Вы можете найти больше информации здесь http://infocenter.arm.com/help/topic/com.arm.doc.ihi0044e/index.html. Они вам не нужны, если вас не волнует разматывание (разматывание полезно для исключения C++ и для отладки).

  2. Эти символы относятся к запуску конструктора C / C++ и деструктора, а также к разрушению кода, который вызывается до / после main(). Разделы с именами.init, .ctors, .preinit_array и.init_array имеют отношение к инициализации объектов C / C++, а разделы.fini, .fini_array и.dtors предназначены для разборки. Символы начала и конца определяют начало и конец разделов кода, связанных с такими операциями, и на них могут ссылаться другие части кода поддержки времени выполнения.

Разделы.preinit_array и.init_array содержат массивы указателей на функции, которые будут вызываться при инициализации..Fini_array - это массив функций, которые будут вызываться при уничтожении. Предположительно метки начала и конца используются для обхода этих списков.

  1. куча: не совсем, эта часть позволяет зарезервировать некоторое пространство для кучи и некоторое место для стека. Очевидно, возникает ошибка, если сумма зарезервированных областей выходит за границы ОЗУ. Это пример:

    _Min_Heap_Size = 0; / * необходимое количество кучи / _Min_Stack_Size = 0x400; / необходимое количество стека * /

    ._user_heap_stack: {. = ALIGN (4); ПРЕДОСТАВИТЬ (конец = .); ПРЕДОСТАВИТЬ ( _end = .);, знак равно + _Min_Heap_Size;, знак равно + _Min_Stack_Size;, = ALIGN(4); } >RAM

  2. чтобы связать библиотеки, я предпочитаю другую нотацию, это всего лишь пример для проекта без ОС 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)

Прежде всего, ваш подход к пониманию этой концепции неверен; это то, во что я верю. Я столкнулся с подобной проблемой во время понимания этой концепции.

Говоря простым языком, я могу объяснить вам, что скрипт компоновщика предоставляет нам три основные вещи:

  1. Точка входа
  2. Адреса времени выполнения в основной памяти.
  3. Процедура копирования

Итак, давайте рассмотрим один пример,

Предположим, что у нас есть N файлов.c в нашем проекте. Теперь после компиляции каждый файл содержит свои собственные единицы перевода, называемые объектными файлами.

Каждый объектный файл содержит .text раздел / сегмент, который содержит фактический код. И.data раздел / сегмент для данных.

Совместить все .text В разделе каждой единицы перевода скрипт компоновщика предоставляет некоторые конкретные команды для одного и того же. Это то же самое для .data раздел также.

После объединения всех объектных файлов конечный исполняемый файл готов к использованию.

Теперь подходит к некоторому парадоксу...

Точкой входа в случае серии Cortex-M является просто ResetISR. После выполнения функции ResetISR и инициализации других маскируемых прерываний в SoC, следующим шагом является процедура копирования.

Процедура копирования не что иное, как копировать .data раздел в оперативную память (которая включает в себя .bss раздел даже, но я не рассматриваю эту часть прямо сейчас).

Копирование из ПЗУ в ОЗУ необходимо, поскольку фактический файл ELF всегда хранится в ПЗУ.

После выполнения всех этих вещей, связанных с запуском, мы можем позвонить нашим main() функция.

Другие вопросы по тегам