simple linker script

cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

simple linker script

2,481 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by IanB on Wed Aug 06 05:20:22 MST 2014
I'm learning how to use the linker.
All I want to do is put my vectors, crp word, and program in the right place, and some global variables in the RAM.

I can do this perfectly well using the .org statement in assembler, but I'd like to learn how to use the linker. I've been writing assembler for donkeys' years, and never had a linker before!

Will this work:

INCLUDE "G0003c_Debug_lib.ld"
INCLUDE "G0003c_Debug_mem.ld"

ENTRY(ResetISR)

SECTIONS
{
   .vectors : { *(.vectors) }
   . = 0x2FC;
   .crp : { KEEP(*(.crp)) }
   .text : { *(.text) }

   .data : { *(.data) } > RamLoc
}

If not, what more do I need?

(I don't need to initialise the variables, the code does that - I was always taught that assuming any startup state for variables was poor programming practice)
0 Kudos
15 Replies

2,052 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by starblue on Wed Aug 27 00:34:07 MST 2014
Here's an example I use.  It ignores CRP, and may be imperfect in various other ways, but it works for me, for C code.

(Note the _.txt at the end of the file name is only there to make the stupid forum software happy.)

Jürgen
0 Kudos

2,052 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by IanB on Tue Aug 26 14:50:58 MST 2014
I think what it's doing is putting a section table where the vectors should be.

I have a working version of this application, which uses .org and .equ statements to put code where it should be, but it's not NEAT. I'd like to use the linker and its .section commands properly.

I suppose what I'd really like is a good example to look at and study. There's one in Joseph Yiu's book (startup_LPC11xx.s), but it's written for the wrong assembler/linker. All the mnemonics are the same, but all the directives are different, and it's the directives I need to understand, and how they relate to the linker.
0 Kudos

2,052 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by TheFallGuy on Tue Aug 26 01:46:16 MST 2014
Without seeing your whole project (source code and linker script) it is impossible to know what you have done wrong...

One thought - have you made "main" a thumb function?
0 Kudos

2,052 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by IanB on Tue Aug 26 00:56:23 MST 2014
When I had a "stripped down" linker script and used .org statements to put my code in the right place it all worked.

Then I changed it to use the standard automatically generated linker script.

I must have missed something, because I get a hardware fault at the <code> B main</code> instruction.

HardFault_Handler() at g003d.s:154 0x308
<signal handler called>() at 0xfffffff9
ResetISR() at g003d.s:150 0x304


Not sure what it's doing at FFFFFFF9 because that's the interrupt return address.


My thoughts on the linker - obviously it has to be this arcane, otherwise writing assembler would just be too easy.
0 Kudos

2,052 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by TheFallGuy on Mon Aug 25 10:37:16 MST 2014
It is a debugger message -nothing to do with the linker.

When your program stops, or is loaded, the debugger looks at the PC and then uses information in the debug tables to work out where in the source code has been compiled to that address. If it is outside the bounds of your application, you get this (descriptive) message.

So, it looks like your application is broken...
0 Kudos

2,052 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by IanB on Mon Aug 25 10:17:38 MST 2014
So what does 'No source available for "0x10000460"' mean?

It assembles correctly and gives this error as it is going into debug.

Address 0x10000460 is the first RAM address that I have not allocated - the previous assembler directive says

<code>magnet: .space 32</code>

(as it allocates space for storing data from a magnet sensor) and the map file says that the label magnet: is at 0x100000440

I seem to think that the linker is looking for something to put at 0x10000460 - but there isn't anything - I don't need it to put anything there.
0 Kudos

2,052 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by starblue on Fri Aug 08 05:02:59 MST 2014

Quote: IanB

Quote:

C code puts initialized variables in the .data section. [...]


So assembler doesn't? - that OK, so long as I know.
Assemblers never used to initialise RAM, so that's what I'm used to.


What you do in assembler is up to you. I think you could easily do the same at the price of a few .section commands. That way you wouldn't need to initialize each variable separately.

Jürgen
0 Kudos

2,052 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by IanB on Fri Aug 08 03:09:38 MST 2014

Quote:

C code puts initialized variables in the .data section. The values are put in flash and then copied to RAM before the C program (main) starts. The initialization code is usually in some C file called crt.c (CRT for C RunTime). This is an example where a section has two addresses, the load address (LMA) where it is loaded in the flash, and the virtual address (VMA) in RAM during runtime.


So assembler doesn't? - that OK, so long as I know.
Assemblers never used to initialise RAM, so that's what I'm used to.


Quote:

I think you should be able to write the difference directly in the instruction. Otherwise please call an offset xyz_OFFSET.


Makes good sense to me.
0 Kudos

2,052 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by starblue on Fri Aug 08 01:15:28 MST 2014

Quote: IanB
1) If I wanted the assembler to initialise my variables in RAM, where do I put the values to which I would like them initialised?



C code puts initialized variables in the .data section.  The values are put in flash and then copied to RAM before the C program (main) starts. The initialization code is usually in some C file called crt<something>.c (CRT for C RunTime).  This is an example where a section has two addresses, the load address (LMA) where it is loaded in the flash, and the virtual address (VMA) in RAM during runtime.


Quote: IanB
2) If I had some variables which all relate to one task e.g. PARTITION_START, FAT1_START, FAT2_START etc. I can access them by writing
LDR R3,=PARTITION_START
LDR R2,[R3]
LDR R3,=FAT1_START
LDR R1,[R3]

and I could also write:
LDR R3,=PARTITION_START
LDR R2,[R3]
LDR R1,[R3,#4]

saving a line of code, a place in the literal pool and two instruction cycles; but making the code difficult to read

or I could write
.equ   _FAT1_start, FAT1_start -  PARTITION_START
LDR R3,=PARTITION_START
LDR R2,[R3]
LDR R1,[R3,_FAT1_START]

but I have to define all the offsets

or is there a better way?


I think you should be able to write the difference directly in the instruction.  Otherwise please call an offset xyz_OFFSET.


Jürgen
0 Kudos

2,052 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by IanB on Thu Aug 07 12:20:22 MST 2014
Thanks.
My favourite search engine found the standard GNU Linker manual in many different fonts and many different colours, but eventually I managed to find a linker script for which someone had written an explanation of each line. It helped a lot!

I have now succeeded in organising the code so that it works with the automatically generated linker script.

A couple of things that I came across on the way:

1) If I wanted the assembler to initialise my variables in RAM, where do I put the values to which I would like them initialised?

2) If I had some variables which all relate to one task e.g. PARTITION_START, FAT1_START, FAT2_START etc. I can access them by writing
LDR R3,=PARTITION_START
LDR R2,[R3]
LDR R3,=FAT1_START
LDR R1,[R3]

and I could also write:
LDR R3,=PARTITION_START
LDR R2,[R3]
LDR R1,[R3,#4]

saving a line of code, a place in the literal pool and two instruction cycles; but making the code difficult to read

or I could write
.equ   _FAT1_start, FAT1_start -  PARTITION_START
LDR R3,=PARTITION_START
LDR R2,[R3]
LDR R1,[R3,_FAT1_START]

but I have to define all the offsets

or is there a better way?

3) Why do we now put FUNCTION and ENDFUNC before and after a subroutine? Does it do anything?
0 Kudos

2,052 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by lpcxpresso-support on Thu Aug 07 01:31:31 MST 2014
We use the standard GNU linker and documentation. If you want more information or tutorials, please use your favourite search engine and look for "GNU Linker"
0 Kudos

2,052 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by IanB on Thu Aug 07 00:59:06 MST 2014
I'd like to understand what the linker script does, but I find it inscrutable; and I don't find the linker manual that helpful.

There are a lot of commands the purpose of which I do not know, and hardly a comment line to explain why they are there and what they are doing.
0 Kudos

2,052 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by lpcxpresso-support on Wed Aug 06 09:51:52 MST 2014
Using the location counter is one way to write linker scripts, but it is not very flexible.

A much more flexible and reliable scheme is to use the MEMORY definitions, which give a base and size. You can then move your application by simply changing the MEMORY definitions. Using this mechanism also protects you against overflowing the available memory for a part by giving you linker errors if your code exceeds the amount of memory available (the location counter scheme will not do this).

Maybe, in the 'old' days, you were told to initialise data through code, instead of initialising variables when declaring them, but that is not necessary, or even recommended today. If you do decide to initialise variables, then your data section definition will
need to look something like this:

.data : ALIGN(4)
{
   FILL(0xff)
*(.data*)
   . = ALIGN(4) ;
} > RamLoc40 AT>MFlashA512

This will place the initialised values into flash (to be copied by the startup code) but the actual location of the data into RAM.

Again, I recommend that you use our built-in linker scripts, or as an absolute minimum, follow the pattern that we provide.

Regarding your comments about the advice from lpc-support, that was in response to a specific question that you asked and had a caveat applied...
0 Kudos

2,052 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by IanB on Wed Aug 06 09:27:47 MST 2014
Thanks.

In section 3.3 it gives this example:
     SECTIONS
     {
       . = 0x10000;
       .text : { *(.text) }
       . = 0x8000000;
       .data : { *(.data) }
       .bss : { *(.bss) }
     }
and says:
"Since the location counter is `0x10000' when the output section `.text' is defined, the linker will set the address of the `.text' section in the output file to be `0x10000'."

Does the location counter not auto-increment having defined the .crp section, to start the text section at 0x300?

RamLoc is defined by the MEMORY command as:
RamLoc (rwx) : ORIGIN = 0x10000000, LENGTH = 0x2000 /* 8K bytes */

so does } > RamLoc not put the data into the RAM?

Do I really need a .bss section?

By the way, when I tried to use the auto-generated scripts, I got error messages, and was advised by lpc-support that I should write my own linker scripts.
0 Kudos

2,052 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by lpcxpresso-support on Wed Aug 06 08:52:45 MST 2014
No, this is not sufficient. For example, you do not tell the linker where to place you code (.text) section, or the zero-init (.bss) sections.

In the vast majority of cases, you can just use the auto generated linker scripts. You can also read those to find out what we do, and read about it in the Linker manual (Help->Contents) and find the linker manual under the Tools section.
0 Kudos