首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >理解ARM Cortex-M微控制器的链接脚本

理解ARM Cortex-M微控制器的链接脚本
EN

Stack Overflow用户
提问于 2016-11-10 16:14:09
回答 2查看 10.5K关注 0票数 13

我使用的是来自STM32F746NG的STMicroelectronics微控制器。该设备基于ARM Cortex-M7体系结构。我花了相当多的时间来理解示例项目中的linkerscript。我弄明白了基本知识,但我仍然不能掌握其中的大部分。请帮助我理解这些部分。

链接脚本的启动

链接脚本的开头如下:

代码语言:javascript
复制
/* 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中定义的第一部分是向量表。它必须在闪存的第一个字节中结束。

代码语言:javascript
复制
/* --------------------------------------------------------------------*/
/*                    OUTPUT SECTIONS                                  */
/* --------------------------------------------------------------------*/
SECTIONS
{
    /****************************/
    /*      VECTOR TABLE        */
    /****************************/
    .isr_vector :
    {
        . = ALIGN(4);
        KEEP(*(.isr_vector)) /* Vector Table */
        . = ALIGN(4);
    } >FLASH

插入向量表后,程序代码的时间到了:

代码语言:javascript
复制
    /****************************/
    /*      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全局符号,该符号表示闪存中的程序代码结束的地址。

常数数据

只读数据也会出现在闪存中(将其放入不稳定的RAM中是没有意义的)。链接脚本定义了.rodata部分应该是闪存的:

代码语言:javascript
复制
    /****************************/
    /*      CONSTANT DATA       */
    /****************************/
    .rodata :
    {
        . = ALIGN(4);
        *(.rodata)         /* .rodata sections (constants, strings, etc.) */
        *(.rodata*)        /* .rodata* sections (constants, strings, etc.) */
        . = ALIGN(4);
    } >FLASH

神秘的瞬间

在定义了常量只读数据的去向之后,linkerscript定义了一些“神秘”部分也应该在闪存中结束:

代码语言:javascript
复制
    .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

这是对闪存分配的结束。链接脚本继续定义在RAM中结束的部分。

RAM中的部分

.data.bss部分对我来说是清楚的。这件事没问题。

代码语言:javascript
复制
    /****************************/
    /*    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部分:

代码语言:javascript
复制
    /****************************/
    /* 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

显然这部分没有立即使用。它仅用于检查RAM是否仍然有足够的空间容纳堆栈和堆。当情况不是这样( .超过最高内存地址)时,将引发链接器错误。

链接脚本的结尾

链接脚本就是这样结束的。老实说,我不知道它是干什么的。这是第二个问题:以下是什么意思?

代码语言:javascript
复制
    /* Remove information from the standard libraries */
    /DISCARD/ :
    {
        libc.a ( * )
        libm.a ( * )
        libgcc.a ( * )
    }

    .ARM.attributes 0 : { *(.ARM.attributes) }
}
/* END OF LINKERSCRIPT */
EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2016-11-11 00:16:53

  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. 堆:不完全是,该部分允许为堆预留一些空间,为堆栈保留一些空间。显然,如果保留区域的和超出RAM边界,则会产生错误。这就是一个例子: _Min_Heap_Size = 0;/*需要堆/ _Min_Stack_Size = 0x400;/所需堆栈数量*/ ._user_heap_stack:{.=对齐(4);提供(结束=。提供( _end =。);。=。+ _Min_Heap_Size;=。+ _Min_Stack_Size;=对齐(4);} >RAM
  2. 为了链接库,我更喜欢使用不同的表示法,这只是一个裸露的无RTOS C++项目的例子: GROUP(libgcc.a libc_nano.a libstdc++_nano.a libm.a libcr_newlib_nohost.a crti.o crtn.o crtn.o.o crten.o)
票数 9
EN

Stack Overflow用户

发布于 2016-11-11 05:08:21

首先,你理解这个概念的方法是错误的,这就是我所相信的。在理解这个概念的过程中,我也遇到了类似的问题。

用简单的语言,我可以解释链接器脚本为我们提供了三个主要的东西:

  1. 入口点
  2. 主内存中的运行时地址。
  3. 复印程序

所以让我们考虑一个例子,

假设我们的项目中有N个.c文件。现在,编译后,每个文件包含自己的翻译单元,称为对象文件。

每个对象文件都包含包含实际代码的.text节/段。以及数据的.data部分/段。

为了组合每个翻译单元的所有.text部分,链接器脚本提供了一些特定的命令。这对于.data部分也是一样的。

合并所有对象文件之后,最终的可执行文件就可以使用了。

现在讲到一些悖论..。

在Cortex-M系列的情况下,入口点就是ResetISR.在执行ResetISR函数并在SoC中初始化其他可掩蔽中断之后,下一步是复制过程。

复制过程只不过是将.data部分复制到内存中(其中甚至包括有趣的.bss部分,但我现在不考虑这个部分)。

从ROM复制到RAM是必要的,因为实际的ELF文件总是存储在ROM中。

在执行了所有这些与启动相关的事情之后,我们现在可以调用我们的main()函数了。

票数 -2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/40532180

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档