Understanding the Location Counter of GNU Linker Scripts
Asked Answered
N

3

19

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.

Neoterize answered 12/12, 2012 at 1:39 Comment(12)
Off-Topic: Note: It appears there's a bug in this linker-script. If you want to use it, change .bss : { to .bss _edata : {, otherwise, your .data and .fastcode sections will be overwritten by the BSS section. Check disassembly.Cybele
Thanks, but are you sure? Everything seems to work ok when I have global variables and code running from RAM. Can you explain further what the problem is?Neoterize
That was my experience when I used .fastcode. I disassembled the output and looked at the linker map (because I had problems with other things as well). What I saw, was that the .bss section had the exact same RAM address as the .data section. This means if you have code in your startup file, that copies the data first, and then clears the bss afterwards, using the supplied addresses, the data section will be cleared with the size of the bss section. I can not say for sure, though, because it depends on many things, including the Makefile and your sources. Try disassembling a test-program.Cybele
Please also check the output for .fastcode and .ramvect, just to be sure they're OK.Cybele
Ha ha, I think what you're talking about is exactly what I asked about in this question: #14454496 Actually, maybe not. I'll have a look at the map file I have.Neoterize
Uhm.. I can see how one can be mistaken for being the other. In my case, though, the address of the bss section was 0x10000000 and the address of the data section was also 0x10000000 (the VMA, not the LMA). So as soon as I 'daisy-chained' the bss on _edata, the VMA changed to 0x100000dc, which was the end of my .fastcode section.Cybele
I've had a look. It's been a while since I worked on this project, but I think the code that this question was based on ended up having no .fastcode, .data or .bss. So that's why there would be no problems there. However, I did use the same linker script again for the next stage of the project. There was .fastcode and .bss this time (although no .data). When I look at the linker map, .fastcode starts and ends at 0x00200040 and 0x0020029C. .data starts and ends at 0x0020029C as it is empty. .bss starts and ends at 0x0020029C and 0x00200438. So everything seems ok.Neoterize
That's good to hear, as I've seen many linker-scripts without 'daisy-chaining' the VMA. :) -It could be related to the version of LD too, or maybe even how it's compiled. I'm using GNU LD version 2.23.1.Cybele
Just to be sure, I made a couple of global variables and recompiled. The VMAs for .fastcode, .bss and .data were all as expected and not overlapping. I think it must be a difference between our toolchains, I'm using YAGARTO with GCC 4.7.2 and Binutils 2.23.1. EDIT: Just saw your new comment. Yes, I think you're right. Interesting that we're using the same LD version, but it might be to do with the YAGARTO aspect of it. Thanks anyway for the help, it's good to make sure everything is working.Neoterize
One additional hint: If you use assembler for your .fastcode section, you should not use .section .fastcode, but instead use .section .fastcode,"ax",%progbits. Because if you do not add the flags, your code will only sometimes be included (if you're lucky).Cybele
There is already an answer to your question but I think that you could find out a lot more about what's happening in the script if you read web.eecs.umich.edu/~prabal/teaching/eecs373-f10/readings/…Cistern
@nonsensical That's the manual of an old version of GNU ld from an unofficial source. sourceware.org/binutils/docs/ldSculpt
N
10

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.

Neoterize answered 9/1, 2013 at 8:49 Comment(1)
I think you have answered your question correctly. I was confused by this documentation too, but didn't care because it worked. There is another note about back-wards compatible behavior. I think that this resolution has changed over time. See ` LD_FEATURE ("SANE_EXPR")` in the ld documents.Singly
S
6

The placement of the section is determined by the memory region after the closing brace (>RAM AT>ROM). So the execution address is in RAM at 0x00200000 and following, but the load address is in ROM (flash) at 0x00100000. The startup code must copy the .fastcode output section from its load to its execution address, that's what the symbols are for.

Note that these need not be at address 0, because the AT91SAM7S remaps either RAM or ROM to address 0. Usually it starts up with ROM mapped, and the startup code switches that to RAM.

Sculpt answered 13/12, 2012 at 11:6 Comment(3)
Thanks, but this doesn't explain why the values of the location counter assigned to __fastcode_start etc aren't 0x0. I know that's not the desired result, but my understanding of the location counter says that that's what should be happening. Each of those __xxx_start symbols are set equal to . at the very beginning of their corresponding sections. Inside of an output section, I am lead to believe that . only holds the offset from the base VMA of that section.Neoterize
I've updated the question, there's a quote from the documentation now. The quote makes it clear (to me) that 0x0 is what should be getting assigned to all of those _start symbols. I don't know how I could be misinterpreting it. So is the documentation wrong?Neoterize
The address 0 is only used if you don't specify a memory region. From sourceware.org/binutils/docs-2.21/ld/MEMORY.html (near the end): "Once you define a memory region, you can direct the linker to place specific output sections into that memory region by using the >region output section attribute." The AT is also documented somewhere, but I remember it was hard to find and understand when I first did this (I wrote a linker script for AT91SAM7S256 in 2006).Sculpt
G
1

This question also troubled me, Give my understanding:

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

The above statement tells the linker to place the __ram_start symbol at location counter, that is at the start of the .ramvect segment.

Since the __ram_start symbol is located at the head of the .ramvect segment, when the C code is used to get the __ramvect address, it will get the starting address of the.ramvect segment, i.e. its absolute address.

Giorgione answered 24/2, 2021 at 9:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.