SpacemanScott
SpacemanScott

Reputation: 1013

How to set an entry address in GNU Linker

I am working on converting a ARMv7 project.

The tools are Code Compose Studio, and GNU 7.3.1 compiler. These cannot change.

The program entry point is 0x8000 0000 and cannot change.

The entry point is in an assembler files named init.s and the label is "Entry"

There will be hundreds of files in this project.

I have hit a road bump trying to get the assembler label set to the correct address. I have been manipulating this dozens of ways, and here are some examples: I can't promise that I didn't get a few of these attempts mixed up. But nothing is working.

First, the conventional linker script:

{
    .text :
    {
        *(.text*)
    } > DDR0

(This is the first entry in the section, location counter hasn't been touched, and DDR0 is defined as 0x80000000 so the location counter should be set to that)

Ok, so everything that in .text is included. I notice the Entry is not where I need it, it is not at the starting address of 0x80000000:

 *(.text*)
 .text          0x80000000      0x190 ./CAThread.o
                0x80000000                CAThread::~CAThread()
                0x80000000                CAThread::~CAThread()
                0x80000004                CAThread::CAThread()
                0x80000004                CAThread::CAThread()
                0x80000084                CAThread::Run()
                0x80000174                CAThread::StartThread(void*)
 .text          0x80000190       0xb4 ./init.o
                0x80000190                Entry  <-----   WRONG LOCATION
 .text          0x80000244      0x194 ./main.o

The IDE is placing the object files in sorted order (and also recursive through sub directories). And I can't control that.

I tried a dedicated section, but that failed

{
   .entryPoint : {
       *(.Entry)
    } > DDR0

    .text :
    {
        *(.text*)
    } > DDR0

The new section is in the map file, but does NOT have any address, nor any size

 *(.Entry)

.text           0x80000000     0x9484
 *(.text*)
 .text          0x80000000      0x190 ./CAThread.o
                0x80000000                CAThread::~CAThread()
                0x80000000                CAThread::~CAThread()
                0x80000004                CAThread::CAThread()
                0x80000004                CAThread::CAThread()
                0x80000084                CAThread::Run()
                0x80000174                CAThread::StartThread(void*)
 .text          0x80000190       0xb4 ./init.o
                0x80000190                Entry
 .text          0x80000244      0x194 ./main.o

I tried the documented method of setting an absolute address, but that is failing on all accounts. Apparently this version does not understand the syntax, and is ignoring it.

SECTIONS
{
   .entryPoint 0x80000000 : {
       KEEP(*(.Entry))
    }

    .text  0x80000000 :
    {
        *(.text*)
    }

I get an error about missing sections. So that I go back to using the trailing > DDR0 syntax.

Next up, I try placing a section name in the assembler file:

@**************************** Code Seection ***********************************
        .text
        .entryPoint               @  ADDED THIS
@ This code is assembled for ARM instructions
        .code 32
Entry:
         LDR   r0, =_stack                     @ Read the stack address
         MSR   cpsr_c, #MODE_UND|I_F_BIT       @ switch to undef  mode

Now I can see in the object dump that the label is in the section name I want it in, and is no longer .text

00000000 l    d  .debug_aranges 00000000 .debug_aranges
00000000 l    d  .ARM.attributes        00000000 .ARM.attributes
00000000 g       entryPoint     00000000 Entry
00000000         *UND*  00000000 _stack

Still... No change. The section is not there, and now the "init.o : Entry" is gone from the location in the map file. I did find it in the map file, at address 0, with a correct length. So it's there, but not where it's told to be.

So now I get aggressive, and try to tell the linker script EXACTLY what I want and where.

   {
       KEEP(*(.Entry))
       init.o(entryPoint.Entry)
    } > DDR0
    .text :
    {
        *(.text*)
    } > DDR0 ```

And the result is now an error that says:

```(entryPoint+0x0): multiple definition of `Entry'
./init.o:(entryPoint+0x0): first defined here

Where "here" is the same location. I have to assume that the cryptic statement *(.text*) is telling it to just drag in every piece of text into the output. So 'Entry' is getting added twice (I haven't found any doc that explains how all the wildcards in *(text*) are interpreted, nor what those are parenthesis around some of these mean ).

When I tried to explicitly set the location counter to the start address:

   {
       . = 0x80000000;
       KEEP(*(.Entry))
       init.o(entryPoint.Entry)
    } > DDR0

I received the error:

cannot move location counter backwards (from 80000000 to 00000000)

Unless my math is wrong, moving from an initial location counter of 0 to 0x80000000 is actually moving forward...

Because there will be hundreds of files, and the project evolves, I cannot expect to be manually adding all the individual object files to the linker script. I haven't even tries that at this point, because it's already not worth the effort due to its size. It would be nice if I could tell it to leave out the "init.s" file from the .text section of the linker script, so it could be in it's own .entryPoint section.

But it would be nicer if the linker script actually behaved according to the doc. But it's refusing to place this one single label in it's own section, at the specific address I am trying to use.

Any ideas or suggestions how to accomplish this?
Is it somehow related to the assembler file "init.S" (because it's not a C file)?

EDIT: More Info. This is getting more insane...

I added a section to the assembler file. Apparently it needs a leading dot:

@**************************** Code Section ***********************************
        .text
        .section .entry

        .code 32

Entry:
         LDR   r0, =_stack     

So my linker script is this:

SECTIONS
{
    .entry :
    {
        KEEP(*(.entry))     /*  Keep entry here. */
    } > DDR0
    .text :
    {
        *(.text*)
        KEEP(*(.init))
        KEEP(*(.fini))

        /* .ctors */
        *crtbegin.o(.ctors)
        *crtbegin?.o(.ctors)
      /*  *(EXCLUDE_FILE(*crtend?.o *crtend.o) .ctors) */
        *(SORT(.ctors.*))
        *(.ctors)

        /* .dtors */
         *crtbegin.o(.dtors)
         *crtbegin?.o(.dtors)
      /*   *(EXCLUDE_FILE(*crtend?.o *crtend.o) .dtors)*/
         *(SORT(.dtors.*))
         *(.dtors)

        *(.rodata*)

        KEEP(*(.eh_frame*))
    } > DDR0

    .ARM.extab : 
    {
        *(.ARM.extab* .gnu.linkonce.armextab.*)
    } > DDR0

One would think, according to all all the doc I can find, that now the .entry section would be first, and the .text section would be at address after it.

And yet... This is the map file generated:

.entry          0x80000000       0xb4
 *(.entry)
 .entry         0x80000000       0xb4 ./init.o
                0x80000000                Entry

.text           0x80000000     0x9554
 *(.text*)
 .text          0x80000000      0x190 ./CAThread.o
                0x80000000                CAThread::~CAThread()
                0x80000000                CAThread::~CAThread()
                0x80000004                CAThread::CAThread()
                0x80000004                CAThread::CAThread()
                0x80000084                CAThread::Run()
                0x80000174                CAThread::StartThread(void*)
 .text          0x80000190      0x180 ./main.o
                0x80000190                ConsoleTask(void*)
                0x800001c8                main

It correctly placed the .entry section at address 0x80000000, then proceeded to OVERWRITE THAT SECTION with the object code from .text

And yet... It is correctly placing those .ctors and .dtors after the .text And the There are other sections AFTER this which are correctly placed after the .text.

What is causing it to think that the section .entry can be overwritten?

Upvotes: 2

Views: 2365

Answers (1)

SpacemanScott
SpacemanScott

Reputation: 1013

After digging more I have found there are attributes necessary to the section command in the assembler file. (assembler is still a learning exercise)

It is necessary to append flags to the section telling it the purpose.

@**************************** Code Section ***********************************
        .text
        .section .entry, "ax"   @  Attributes of the section
        .func Entry

https://ftp.gnu.org/old-gnu/Manuals/gas-2.9.1/html_chapter/as_7.html

These define the section as allocatable and executable. I did a object dump for no flags, a only, and ax. These are the reports:

Without attribtes: CONTENTS, RELOC,              READONLY
Allocatable  :     CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA    
Alloc + Exec :     CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE

Adding the 'a' only was enough to properly allocate the section, although including 'x' wisely marks the memory as code. The map file output:

.entry          0x80000000       0xb4
 .entry         0x80000000       0xb4 ./init.o
                0x80000000                Entry

.text           0x800000b4     0x9554
 *(.text*)
 .text          0x800000b4      0x190 ./CAThread.o
                0x800000b4                CAThread::~CAThread()
                0x800000b4                CAThread::~CAThread()
                0x800000b8                CAThread::CAThread()
                0x800000b8                CAThread::CAThread()
                0x80000138                CAThread::Run()
                0x80000228                CAThread::StartThread(void*)
 .text          0x80000244      0x180 ./main.o

(For CAThread, the destructor is just empty)

At this point the actual entry point is correctly set, and the program is loading and executing properly on the target MCU.

If someone else runs into this issue, I hope it helps them. It was certainly an education.

Upvotes: 2

Related Questions