了解GNU链接器脚本的位置计数器

时间:2012-12-12 01:39:44

标签: linker embedded arm gnu linker-scripts

我正在开展一个大学项目,我正在从头开始为Atmel SAM7S256微控制器编写软件。这比我之前使用过的其他MCU更深入,因为这次需要了解链接器脚本和汇编语言。

我一直在仔细审查SAM7S芯片的示例项目,以便完全了解如何从头开始SAM7 / ARM项目。一个值得注意的例子是Miro Samek的“使用GNU构建裸金属ARM系统”教程here(此问题中的代码来自)。我也花了很多时间从sourceware.org阅读链接器和汇编文档。

我很高兴我在大多数情况下理解以下链接描述文件。只有一件涉及位置计数器的东西对我来说没有意义。以下是上述教程提供的链接描述文件:

OUTPUT_FORMAT("elf32-littlearm", "elf32-bigarm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_vectors)

MEMORY {                                       /* memory map of AT91SAM7S64 */
    ROM (rx)  : ORIGIN = 0x00100000, LENGTH = 64k
    RAM (rwx) : ORIGIN = 0x00200000, LENGTH = 16k
}

/* The sizes of the stacks used by the application. NOTE: you need to adjust */
C_STACK_SIZE   = 512;
IRQ_STACK_SIZE = 0;
FIQ_STACK_SIZE = 0;
SVC_STACK_SIZE = 0;
ABT_STACK_SIZE = 0;
UND_STACK_SIZE = 0;

/* The size of the heap used by the application. NOTE: you need to adjust   */
HEAP_SIZE = 0;

SECTIONS {

    .reset : {
        *startup.o (.text)  /* startup code (ARM vectors and reset handler) */
        . = ALIGN(0x4);
     } >ROM

    .ramvect : {                        /* used for vectors remapped to RAM */
        __ram_start = .;
        . = 0x40;
    } >RAM

    .fastcode : {
        __fastcode_load = LOADADDR (.fastcode);
        __fastcode_start = .;

        *(.glue_7t) *(.glue_7)
        *isr.o (.text.*)
        *(.text.fastcode)
        *(.text.Blinky_dispatch)
        /* add other modules here ... */

        . = ALIGN (4);
        __fastcode_end = .;
    } >RAM AT>ROM

    .text : {
        . = ALIGN(4);
        *(.text)                                   /* .text sections (code) */
        *(.text*)                                 /* .text* sections (code) */
        *(.rodata)           /* .rodata sections (constants, strings, etc.) */
        *(.rodata*)         /* .rodata* sections (constants, strings, etc.) */
        *(.glue_7) /* glue arm to thumb (NOTE: placed already in .fastcode) */
        *(.glue_7t)/* glue thumb to arm (NOTE: placed already in .fastcode) */

        KEEP (*(.init))
        KEEP (*(.fini))

        . = ALIGN(4);
        _etext = .;                         /* global symbol at end of code */
    } >ROM

    .preinit_array : {
        PROVIDE_HIDDEN (__preinit_array_start = .);
        KEEP (*(SORT(.preinit_array.*)))
        KEEP (*(.preinit_array*))
        PROVIDE_HIDDEN (__preinit_array_end = .);
    } >ROM

    .init_array : {
        PROVIDE_HIDDEN (__init_array_start = .);
        KEEP (*(SORT(.init_array.*)))
        KEEP (*(.init_array*))
        PROVIDE_HIDDEN (__init_array_end = .);
    } >ROM

    .fini_array : {
        PROVIDE_HIDDEN (__fini_array_start = .);
        KEEP (*(.fini_array*))
        KEEP (*(SORT(.fini_array.*)))
        PROVIDE_HIDDEN (__fini_array_end = .);
    } >ROM

    .data : {
        __data_load = LOADADDR (.data);
        __data_start = .;
        *(.data)                                          /* .data sections */
        *(.data*)                                        /* .data* sections */
        . = ALIGN(4);
        _edata = .;
    } >RAM AT>ROM

    .bss : {
        __bss_start__ = . ;
        *(.bss)
        *(.bss*)
        *(COMMON)
        . = ALIGN(4);
        _ebss = .;                     /* define a global symbol at bss end */
        __bss_end__ = .;
    } >RAM

    PROVIDE ( end = _ebss );
    PROVIDE ( _end = _ebss );
    PROVIDE ( __end__ = _ebss );

    .heap : {
        __heap_start__ = . ;
        . = . + HEAP_SIZE;
        . = ALIGN(4);
        __heap_end__ = . ;
    } >RAM

    .stack : {
        __stack_start__ = . ;

        . += IRQ_STACK_SIZE;
        . = ALIGN (4);
        __irq_stack_top__ = . ;

        . += FIQ_STACK_SIZE;
        . = ALIGN (4);
        __fiq_stack_top__ = . ;

        . += SVC_STACK_SIZE;
        . = ALIGN (4);
        __svc_stack_top__ = . ;

        . += ABT_STACK_SIZE;
        . = ALIGN (4);
        __abt_stack_top__ = . ;

        . += UND_STACK_SIZE;
        . = ALIGN (4);
        __und_stack_top__ = . ;

        . += C_STACK_SIZE;
        . = ALIGN (4);
        __c_stack_top__ = . ;

        __stack_end__ = .;
    } >RAM

    /* Remove information from the standard libraries */
    /DISCARD/ : {
        libc.a ( * )
        libm.a ( * )
        libgcc.a ( * )
    }
}

在整个示例中(例如.ramvect,.fastcode和.stack部分),有一些符号定义,例如__ram_start = .;。这些地址由启动汇编代码和初始化C代码使用,以初始化MCU RAM中的正确位置。

我理解的问题是,这些符号定义如何导致分配正确的值。这确实发生了,脚本是正确的,我只是不明白如何。

我理解它的方式,当你在一个部分中使用位置计数器时,它只包含与部分本身的虚拟内存地址(VMA)的相对偏移量。

因此,例如,在行__ram_start = .;中,我希望__ram_start被赋值为0x0 - 因为它在.ramvect部分的开头被赋予了位置计数器的值。但是,为了使初始化代码正常工作(它确实如此),必须将__ram_start指定为0x00200000(RAM开头的地址)。

如果该行代替__ram_start = ABSOLUTE(.);__ram_start = ADDR(.ramvect);,我原以为这只会按预期工作。

__fastcode_start__stack_start__也是如此。它们不能全部被定义为地址0x0,否则程序将无法工作。但文档linked here似乎表明应该发生的事情。以下是文档中的引用:

  

注意:。实际上是指从当前包含对象的开头起的字节偏移量。通常这是SECTIONS语句,其起始地址为0,因此。可以用作绝对地址。如果。但是在段描述中使用它,它指的是从该段开始的字节偏移量,而不是绝对地址。

因此,这些符号分配期间的位置计数器值应与相应的VMA段偏移。所以那些“_start”符号应 all 设置为0x0。哪会打破这个计划。

显然我错过了一些东西。我想它可能只是将位置计数器值分配给符号(在一个部分内)导致默认情况下使用ABSOLUTE()。但我无法在任何证实这一点的地方找到明确的解释。

如果有人能清除这一点,请提前致谢。

3 个答案:

答案 0 :(得分:8)

我想我可能已经找到了自己问题的答案。我不确定我是对的,但这是我能够想到的第一个解释实际上是有道理的。让我重新思考的是this page of the documentation。特别是这句话:

  

地址和符号可以是相对的部分,也可以是绝对的。一节   相对符号是可重定位的。如果您请求可重定位输出   使用`-r'选项,进一步的链接操作可能会更改该值   部分相对符号。另一方面,绝对符号   在任何进一步的链接操作中都将保留相同的值。

和这句话:

  

您可以使用内置函数ABSOLUTE强制表达式   绝对的,否则它是相对的。例如,要创建   绝对符号设置为输出节末尾的地址   .data

 SECTIONS
   {
     .data : { *(.data) _edata = ABSOLUTE(.); }
   }
     

如果未使用ABSOLUTE_edata将相对于.data   部分。

我以前读过它们,但这次我从新的角度看过它们。

所以我认为我的错误解释是认为当一个符号被赋予一个相对字节偏移地址时,它只是设置为该偏移的值而基地址信息丢失了。

这是基于我原来问题的引用:

  

注意:。实际上是指从开始的字节偏移量   当前包含对象。通常这是SECTIONS声明,   因此,其起始地址为0。可以用作绝对地址。   如果。然而,在部分描述中使用它,它指的是   从该部分的开头起的字节偏移量,而不是绝对地址。

相反,我现在理解的是,基地址信息不会丢失。该符号不会简单地分配基址的偏移值。该符号仍将最终解析为绝对地址,但只有在其基地址无法更改的情况下才会解析。

所以,我认为像__stack_start__ = . ;这样的东西必须改为__stack_start__ = ABSOLUTE(.) ;,这确实有效,我现在认为这是不必要的。更重要的是,我从这个回复的第一个引言中了解到你可以重新链接一个ELF文件吗?

因此,如果我使用__stack_start__ = ABSOLUTE(.) ;,则运行链接描述文件以创建ELF可执行文件,然后尝试重新链接它并将.stack部分移动到其他位置,__stack_start__符号仍将指向来自第一个链接的绝对地址相同,因此不正确。

这可能很难遵循,但我尽可能清晰地写出来。我怀疑我已接近正确的想法,但我仍然需要有人真正了解这些东西以确认或否认这一点。

答案 1 :(得分:5)

截面的放置由闭合括号(>RAM AT>ROM)后的内存区域决定。因此执行地址在0x00200000及以下的RAM中,但加载地址在ROM(闪存)中为0x00100000。启动代码必须将.fastcode输出部分从其加载复制到其执行地址,这就是符号的用途。

请注意,这些不必位于地址0,因为AT91SAM7S将RAM或ROM重新映射到地址0.通常它以ROM映射启动,启动代码将其切换到RAM。

答案 2 :(得分:1)

这个问题也困扰着我,给我的理解:

.ramvect : {                        /* used for vectors remapped to RAM */
    __ram_start = .;
    . = 0x40;
} >RAM

上面的语句告诉链接器将 __ram_start 符号放置在位置计数器处,即 .ramvect 段的开头。

由于__ram_start符号位于.ramvect段的头部,所以当使用C代码获取__ramvect地址时,会得到.ramvect段的起始地址,即绝对地址。