了解GNU链接器脚本的位置计数器 [英] Understanding the Location Counter of GNU Linker Scripts

查看:216
本文介绍了了解GNU链接器脚本的位置计数器的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在从事一个大学项目,我从头开始为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 {/ * AT91SAM7S64的内存映射* /
ROM(rx):ORIGIN = 0x00100000,LENGTH = 64k
RAM(rwx):ORIGIN = 0x00200000,LENGTH = 16k
}

/ *应用程序使用的堆栈大小。注意:您需要调整* /
C_STACK_SIZE = 512;
IRQ_STACK_SIZE = 0;
FIQ_STACK_SIZE = 0;
SVC_STACK_SIZE = 0;
ABT_STACK_SIZE = 0;
UND_STACK_SIZE = 0;

/ *应用程序使用的堆大小。注意:您需要调整* /
HEAP_SIZE = 0;

SECTIONS {

.reset:{
* startup.o(.text)/ *启动代码(ARM向量和重置处理程序)* /
。 = ALIGN(0x4);
}> ROM

.ramvect:{/ *用于重新映射到RAM的向量* /
__ram_start =。
。 = 0x40;
}> RAM

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

*(。glue_7t)*(。glue_7)
* isr.o(.text。*)
*(.text.fastcode)
* text.Blinky_dispatch)
/ *在此添加其他模块... * /

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

.text:{
。 = ALIGN(4);
*(。text)/ * .text段(code)* /
*(.text *)/ * .text * sections(code)* /
*(。rodata)/ * .rodata部分(常量,字符串等)* /
*(。rodata *)/ * .rodata *部分(常量,字符串等)* /
*(。glue_7)/ *将手臂粘到拇指上(注意:已经放在.fastcode中)* /
*(。glue_7t)/ *粘贴拇指到手臂(注:已经放在.fastcode中)* /

KEEP(*(。init))
KEEP(*(。fini))

。 = ALIGN(4);
_etext =。 / *代码结尾处的全局符号* /
}> 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段* /
*(。data *)/ * .data * sections * /
。 = ALIGN(4);
_edata =。
}> RAM AT> ROM

.bss:{
__bss_start__ =。 ;
*(。bss)
*(。bss *)
*(COMMON)
。 = ALIGN(4);
_ebss =。 / *在bss结尾处定义一个全局符号* /
__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

/ *从标准库中删除信息* /
/ DISCARD /:{
libc.a(*)
libm .a(*)
libgcc.a(*)
}
}

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



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



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



所以例如,在 __ ram_start = / code>,我希望__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符号应该都设置为0x0。哪个会打破程序。



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



如果有人可以清除,请提前感谢。

解决方案

我想我可能已经弄清楚了自己的问题的答案。我不知道我是对的,但这是我能够想到的第一个解释,实际上是有道理的。什么让我重新思考的东西是本页的文档。特别是这个引用:


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


并且此引用:


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

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

如果 ABSOLUTE 没有使用, _edata 将相对于 .data
部分。


我以前读过他们,但这次我从一个新的角度看到他们。



我认为我的错误解释是认为,当分配相对字节偏移地址时,一个符号被简单地设置为该偏移量的值,同时基地址信息丢失。



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


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


相反,我现在明白的是,基地址信息不会丢失。该符号不会简单地从基地址分配偏移量的值。该符号仍将最终解析为绝对地址,但只有当基地址无法改变时才可以。



所以我认为像$ code > __ stack_start__ =。 ; 应该更改为 __ stack_start__ = ABSOLUTE(。); ,这是工作,我现在认为这是没有必要的。此外,我从这个回应的第一个引用中可以明白,您可以重新链接ELF文件?



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



这可能很难跟踪,但是我已经把它写成了我可以。我怀疑我已经接近正确的想法,但是我仍然需要一个真正了解这个东西来确认或否认这个的人。


I'm working on a university project where I'm writing software for an Atmel SAM7S256 microcontroller from the ground up. This is more in depth than other MCUs I've worked with before, as a knowledge of linker scripts and assembly language is necessary this time around.

I've been really scrutinizing example projects for the SAM7S chips in order to fully understand how to start a SAM7/ARM project from scratch. A notable example is Miro Samek's "Building Bare-Metal ARM Systems with GNU" tutorial found here (where the code in this question is from). I've also spent a lot of time reading the linker and assembler documentation from sourceware.org.

I'm quite happy that I understand the following linker script for the most part. There's just one thing involving the location counter that doesn't make sense to me. Below is the linker script provided with the above tutorial:

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 ( * )
    }
}

Throughout the example (such as in the .ramvect, .fastcode and .stack sections) there are symbol definitions such as __ram_start = .;. These addresses are used by the startup assembly code and initialization C code in order to initialize the correct locations in the MCU's RAM.

What I have a problem understanding, is how these symbol definitions result in the correct values being assigned. This does happen, the script is correct, I just don't understand how.

The way I understand it, when you use the location counter within a section, it only contains a relative offset from the virtual memory address (VMA) of the section itself.

So for example, in the line __ram_start = .;, I would expect __ram_start to be assigned a value of 0x0 - as it is assigned the value of the location counter at the very beginning of the .ramvect section. However, for the initialization code to work correctly (which it does), __ram_start must be getting assigned as 0x00200000 (the address for the beginning of RAM).

I would have thought this would only work as intended if the line was instead __ram_start = ABSOLUTE(.); or __ram_start = ADDR(.ramvect);.

The same goes for __fastcode_start and __stack_start__. They can't all be getting defined as address 0x0, otherwise the program wouldn't work. But the documentation linked here seems to suggest that that's what should be happening. Here's the quote from the documentation:

Note: . actually refers to the byte offset from the start of the current containing object. Normally this is the SECTIONS statement, whose start address is 0, hence . can be used as an absolute address. If . is used inside a section description however, it refers to the byte offset from the start of that section, not an absolute address.

So the location counter values during those symbol assignments should be offsets from the corresponding section VMAs. So those "_start" symbols should all be getting set to 0x0. Which would break the program.

So obviously I'm missing something. I suppose it could simply be that assigning the location counter value to a symbol (within a section) results in ABSOLUTE() being used by default. But I haven't been able to find a clear explanation anywhere that confirms this.

Thanks in advance if anybody can clear this up.

解决方案

I think I may have figured out the answer to my own question. I'm not sure I'm right, but it's the first explanation I've been able to think of that actually makes sense. What made me rethink things was this page of the documentation. Particularly this quote:

Addresses and symbols may be section relative, or absolute. A section relative symbol is relocatable. If you request relocatable output using the `-r' option, a further link operation may change the value of a section relative symbol. On the other hand, an absolute symbol will retain the same value throughout any further link operations.

and this quote:

You can use the builtin function ABSOLUTE to force an expression to be absolute when it would otherwise be relative. For example, to create an absolute symbol set to the address of the end of the output section .data:

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

If ABSOLUTE were not used, _edata would be relative to the .data section.

I had read them before, but this time I saw them from a new perspective.

So I think my misinterpretation was thinking that a symbol, when assigned a relative byte offset address, is simply set to the value of that offset while the base address information is lost.

That was based on this quote from my original question:

Note: . actually refers to the byte offset from the start of the current containing object. Normally this is the SECTIONS statement, whose start address is 0, hence . can be used as an absolute address. If . is used inside a section description however, it refers to the byte offset from the start of that section, not an absolute address.

Instead what I now understand to be happening is that the base address information is not lost. The symbol does not simply get assigned the value of the offset from the base address. The symbol will still eventually resolves to an absolute address, but only when there's no chance its base address can change.

So where I thought that something like __stack_start__ = . ; should have to be changed to __stack_start__ = ABSOLUTE(.) ;, which does work, I now think it is unnecessary. What's more, I understand from the first quote in this response that you can relink an ELF file?

So if I used __stack_start__ = ABSOLUTE(.) ;, ran the linker script to create the ELF executable, then tried to relink it and moved the .stack section somewhere else, the __stack_start__ symbol would still be pointing to the same absolute address from the first link, and thus be incorrect.

This is probably hard to follow, but I've written it as articulately as I could. I suspect I've got close to the right idea, but I still need someone who actually knows about this stuff to confirm or deny this.

这篇关于了解GNU链接器脚本的位置计数器的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆