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

19
我正在进行一项大学项目,编写适用于Atmel SAM7S256微控制器的软件。这比我之前使用的其他MCU更加深入,因为这次需要了解链接脚本和汇编语言的知识。
为了完全理解如何从头开始启动SAM7/ARM项目,我一直在仔细研究SAM7S芯片的示例项目。一个值得注意的例子是Miro Samek的“使用GNU构建裸机ARM系统”教程,可以在这里找到(此问题中的代码来自该教程)。我还花了很多时间阅读来自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,因此可以将.用作绝对地址。但是,如果在部分描述中使用.,则它将引用该部分的开头处的字节偏移量,而不是绝对地址。
因此,在这些符号分配期间,位置计数器值应该是相应部分VMAs的偏移量。因此,这些“_start”符号应该全部设置为0x0。这将破坏程序。
很明显我漏掉了什么。我想这可能只是将位置计数器的值分配给符号(在一个节内)默认使用ABSOLUTE()。但我无法找到任何清晰的解释来确认这一点。如果有人能澄清这个问题,提前感谢。

1
这是我使用.fastcode时的经验。我反汇编了输出并查看了链接器映射(因为我还有其他问题)。我看到的是,.bss部分具有与.data部分完全相同的RAM地址。这意味着如果您在启动文件中有代码,首先复制数据,然后清除bss,使用提供的地址,则数据部分将被清除为bss部分的大小。我无法确定,因为这取决于许多因素,包括Makefile和您的源代码。尝试反汇编测试程序。 - user1985657
1
我看了一下。虽然我已经有一段时间没有在这个项目上工作了,但我认为这个问题所基于的代码最终没有 .fastcode、.data 或 .bss。所以那里不会有任何问题。 然而,我在项目的下一个阶段再次使用了相同的链接脚本。这次有 .fastcode 和 .bss(虽然没有 .data)。当我查看链接器映射时, .fastcode 从 0x00200040 开始并在 0x0020029C 结束。 .data 由于为空,从 0x0020029C 开始并结束。 .bss 从 0x0020029C 开始并在 0x00200438 结束。 所以一切看起来都很好。 - Adam Goodwin
1
为了确保,我创建了一些全局变量并重新编译。.fastcode、.bss和.data的VMAs都如预期所示,没有重叠。我认为这可能是我们工具链之间的差异,我正在使用带有GCC 4.7.2和Binutils 2.23.1的YAGARTO。 编辑:刚看到你的新评论。是的,我认为你是对的。有趣的是我们正在使用相同的LD版本,但可能与YAGARTO有关。无论如何,感谢您的帮助,确保一切正常很好。 - Adam Goodwin
1
另一个提示:如果您在 .fastcode 部分中使用汇编语言,则不应使用.section .fastcode,而应改为使用.section .fastcode,"ax",%progbits。因为如果您不添加标志,您的代码只有在某些情况下才会被包括进来(如果你很幸运)。 - user1985657
1
@nonsensical 这是来自非官方来源的旧版本GNU ld的手册。https://sourceware.org/binutils/docs/ld/ - starblue
显示剩余7条评论
3个回答

10

我想我可能已经找到了自己问题的答案。我不确定是否正确,但这是我能够想到的第一个真正有意义的解释。让我重新考虑事情的是文档中的这个页面,尤其是这句话:

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

以及这句话:

当表达式本应是相对的时,您可以使用内置函数ABSOLUTE将其强制为绝对的。例如,要创建一个设置为输出节.data末尾地址的绝对符号:

 SECTIONS
   {
     .data : { *(.data) _edata = ABSOLUTE(.); }
   }
如果没有使用ABSOLUTE_edata将相对于.data部分。我以前读过它们,但这次我从一个新的角度看到了它们。因此,我认为我的误解是认为当符号被赋予相对字节偏移地址时,它仅设置为该偏移量的值,而基地址信息则丢失了。我现在理解的情况是,基地址信息并没有丢失。符号不仅仅被赋予来自基地址的偏移量值。只有当其基地址不可能改变时,该符号最终才会解析为绝对地址。因此,我认为无需更改像__stack_start__ = . ;这样的内容,将其更改为__stack_start__ = ABSOLUTE(.) ;(虽然它确实起作用),现在我认为这是不必要的。此外,从这个回答中的第一条引用中我了解到,可以重新链接一个ELF文件?因此,如果我使用__stack_start__ = ABSOLUTE(.) ;,运行链接器脚本创建ELF可执行文件,然后尝试重新链接并将.stack section移动到其他地方,那么__stack_start__符号仍将指向第一个链接的相同绝对地址,因此是不正确的。

1
我认为你已经正确回答了你的问题。我也曾被这份文档搞糊涂过,但并不在意因为它能正常工作。还有一条关于向后兼容行为的注释。我认为这个解决方案随着时间的推移已经发生了变化。请参见ld文档中的LD_FEATURE("SANE_EXPR") - artless noise

6
该部分的位置由右括号后面的内存区域(>RAM AT>ROM)确定。因此,执行地址位于RAM中的0x00200000及其后面,但加载地址位于ROM(闪存)中的0x00100000。启动代码必须将.fastcode输出部分从其加载地址复制到其执行地址,这就是符号的作用。
请注意,这些地址不一定为0,因为AT91SAM7S会将RAM或ROM映射到地址0。通常情况下,它启动时会将ROM映射,而启动代码会将其切换到RAM。

谢谢,但这并没有解释为什么分配给__fastcode_start等的位置计数器的值不是0x0。我知道那不是期望的结果,但我的位置计数器的理解是应该发生这种情况。每个__xxx_start符号都在其对应部分的开头设置为等于.。在输出部分内,我相信.仅保留该部分的基本VMA偏移量。 - Adam Goodwin
我已经更新了问题,现在有一段来自文档的引用。这个引用(对我来说)清楚地表明0x0应该被分配给所有这些“_start”符号。我不知道我怎么可能会误解它。那么文档是错的吗? - Adam Goodwin
3
如果您没有指定内存区域,地址0将不会被使用。在http://sourceware.org/binutils/docs-2.21/ld/MEMORY.html(接近结尾处)中提到:“一旦您定义了内存区域,您可以通过使用`>region`输出段属性,将特定的输出段定向到该内存区域。” AT也有记录,但我记得当我第一次为AT91SAM7S256编写链接器脚本时,很难找到并理解它。 - starblue

1
这个问题也让我困扰过,以下是我的理解:
.ramvect : {                        /* used for vectors remapped to RAM */
    __ram_start = .;
    . = 0x40;
} >RAM

上面的语句告诉链接器将__ram_start符号放置在位置计数器上,即在.ramvect段的开头。
由于__ram_start符号位于.ramvect段的开头,因此当C代码用于获取__ramvect地址时,它将获得.ramvect段的起始地址,即其绝对地址。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接