【问题标题】:Understanding the Location Counter of GNU Linker Scripts了解 GNU 链接描述文件的位置计数器
【发布时间】:2012-11-29 16:01:04
【问题描述】:

我正在从事一个大学项目,我正在从头开始为 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() 默认使用。但我无法在任何地方找到明确的解释来证实这一点。

如果有人可以解决这个问题,请提前感谢。

【问题讨论】:

  • 这是我使用 .fastcode 时的经验。我反汇编了输出并查看了链接器映射(因为我也遇到了其他问题)。我看到的是 .bss 部分与 .data 部分具有完全相同的 RAM 地址。这意味着如果您的启动文件中有代码,首先复制数据,然后使用提供的地址清除 bss,数据部分将被清除为 bss 部分的大小。不过,我不能肯定地说,因为它取决于很多东西,包括 Makefile 和你的来源。尝试反汇编一个测试程序。
  • 我看过了。自从我从事这个项目以来已经有一段时间了,但我认为这个问题所基于的代码最终没有 .fastcode、.data 或 .bss。所以这就是为什么那里没有问题。但是,我确实在项目的下一阶段再次使用了相同的链接器脚本。这次有 .fastcode 和 .bss(虽然没有 .data)。当我查看链接器映射时,.fastcode 以0x002000400x0020029C 开始和结束。 .data 以0x0020029C 开始和结束,因为它是空的。 .bss 以0x0020029C0x00200438 开始和结束。所以一切似乎都很好。
  • 为了确定,我创建了几个全局变量并重新编译。 .fastcode、.bss 和 .data 的 VMA 都符合预期并且没有重叠。我认为这一定是我们的工具链之间的区别,我使用的是 YAGARTO 与 GCC 4.7.2 和 Binutils 2.23.1。编辑:刚刚看到你的新评论。是的,我认为你是对的。有趣的是,我们使用的是相同的 LD 版本,但这可能与它的 YAGARTO 方面有关。无论如何感谢您的帮助,确保一切正常是件好事。
  • 一个额外的提示:如果你在你的 .fastcode 部分使用汇编程序,你不应该使用.section .fastcode,而应该使用.section .fastcode,"ax",%progbits。因为如果你不添加标志,你的代码有时会被包含(如果你幸运的话)。
  • @nonsensical 这是来自非官方来源的旧版本 GNU ld 的手册。 sourceware.org/binutils/docs/ld

标签: linker embedded arm gnu linker-scripts


【解决方案1】:

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

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

【讨论】:

  • 谢谢,但这并不能解释为什么分配给__fastcode_start 等的位置计数器的值不是0x0。我知道这不是预期的结果,但我对位置计数器的理解表明这就是应该发生的事情。每个__xxx_start 符号在其相应部分的开头都设置为等于.。在输出部分内部,我相信. 仅保存与该部分基本 VMA 的偏移量。
  • 我已经更新了问题,现在有来自文档的引用。引用清楚地表明(对我而言) 0x0 应该分配给所有这些 _start 符号。我不知道我怎么会误解它。那么文档有问题吗?
  • 地址0只在不指定内存区域的情况下使用。来自sourceware.org/binutils/docs-2.21/ld/MEMORY.html(接近尾声):“一旦定义了内存区域,您可以使用>region 输出部分属性指示链接器将特定的输出部分放入该内存区域。” AT 也记录在某处,但我记得当我第一次这样做时很难找到和理解(我在 2006 年为 AT91SAM7S256 编写了一个链接器脚本)。
【解决方案2】:

我想我可能已经找到了自己问题的答案。我不确定我是对的,但这是我能想到的第一个解释实际上是有道理的。让我重新思考事情的是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__ 符号仍将指向相同的绝对地址第一个链接,因此是不正确的。

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

【讨论】:

  • 我认为您已经正确回答了您的问题。我也被这个文档弄糊涂了,但我不在乎,因为它有效。还有另一个关于向后兼容行为的说明。我认为这个决议随着时间的推移而改变。请参阅ld documents 中的`LD_FEATURE ("SANE_EXPR")`。
【解决方案3】:

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

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

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

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

【讨论】:

    猜你喜欢
    • 2014-07-02
    • 2018-11-09
    • 2017-03-24
    • 2020-05-09
    • 1970-01-01
    • 2019-12-02
    • 2013-05-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多