Home | Projects | Notes > ARM Cortex-M3/M4 Processor > Linker Script
Linker script is a text file which explains how different sections of the object files should be merged to create a single output file.
Linker and locator combination assigns unique absolute addresses to the different sections of the output file by referring to the address information mentioned in the linker script.
Linker script also includes the code and data memory address and size information.
Linker scripts are written using the GNU linker command language.
GNU linker script has the file extension of .ld
You must supply linker script at the linking phase to the linker using -T
flag.
If the linker finds any necessary sections that are not defined in the linker script, it will simply include them in the final ELF file as is.
Before writing a linker script you must understand what the linker script commands mean:
ENTRY
Used to set the "Entry point address" information in the header of final ELF file generated
In our case, Reset_Handler()
is the entry point into the application. The first piece of code that executes right after the processor reset.
The debugger uses this information to locate the first function to execute.
Not a mandatory command to use, but required when you debug the ELF file using the debugger (GDB)
Syntax: Entry(_symbol_name_)
xxxxxxxxxx
11Entry(Reset_Handler)
MEMORY
Allows you to describe the different memories present in the target and their start address and size information.
The linker uses information mentioned in this command to assign addresses to merged sections. (Relocation of sections)
The information given under this command also helps the linker to calculate total code and data memory consumed so far and throws an error message if data, code, heap or stack areas cannot fit into available size.
By using this command, you can fine-tune various memories available in your target and allow different sections to occupy different memory areas.
Typically one linker script has one MEMORY command.
Syntax:
xxxxxxxxxx
41MEMORY
2{
3 name(attr):ORIGIN =origin, LENGTH =len
4}
[!] Note: There must be a space before each
=
.
name(attr): "label" - defines name of the memory region present in your target which will be later referenced by other parts of the linker script.
(attr): defines the attribute list of the memory region. Valid attribute list must be made up of the characters "ALIRWX" that match section attributes:
R - read-only sections
W - read/write sections
X - sections containing executable code
A - allocated sections
I - initialized sections
L - same as 'I'
! - Invert the sense of the following attributes
Attributes can be lower/upper cases. Two or more can be combined.
ORIGIN: defines origin(start) address of the memory region
LENGTH: defines the length of the memory region
Example:
STM32F4VGT6 / FLASH (1024 KB) / SRAM1 (112 KB) / SRAM2 (16 KB)
xxxxxxxxxx
81MEMORY
2{
3 FLASH(rx): ORIGIN=0x08000000, LENGTH=1024K
4 /* rx since code memory contains information not writable by user program */
5 SARM(rwx): ORIGIN=0x20000000, LENGTH=128K
6
7 /* continue defining other memory regions here, if necessary */
8}
SECTIONS
Used to create multiple output sections in the final ELF executable generated.
Important command by which you can instruct the linker how to merge the sections with the same name (from the input files) to yield one output section in the output file
(All .text
sections from input files .text
section in the output file.)
This command also controls the order in which different output sections appear in the ELF file generated.
By using this command, you also mention the placement of a section in a memory region. For example, you instruct the linker to place the .text
section in the FLASH memory region, which is described by the MEMORY command.
Eample:
xxxxxxxxxx
201SECTIONS
2{
3 /* This section should include .text section of all input files. */
4 .text :
5 {
6 /*
7 * merge all .isr_vector section of all intput files
8 * merge all .text section of all input files
9 * merge all .rodata section of all input files
10 */
11 }>(vma) AT>(lma) /* informs to which address this section should go */
12
13 /* This section should include .data section of all input files */
14 .data :
15 {
16 /*
17 * merge all .data section of all input files
18 */
19 }>(vma) AT>(lma)
20}
There must be a space between a section name and a
:
. e.g.,.text :
(.text:
will cause a syntax error.)When only VMA or LMA is applicable such as
.bss
(VMA only) or.text
(LMA only), regard it as VMA = LMA and simply specify one. For example, for.bss
section>SRAM
, and for.text
section>FLASH
is sufficient.
KEEP
ALIGN
ALIGN command is used to force boundary alignment.
Linker will introduce padding *fill*
if alignment is ncessary at the beginning of each section, but will not care about the end of section. When you want to make sure that the section ending address is boundary-aligned for the section immediately following it, use ALIGN command.
Example:
xxxxxxxxxx
121SECTIONS
2{
3 .text :
4 {
5 *(.isr_vector)
6 *(.text)
7 *(.rodata)
8 . = ALIGN(4); /* assigning 4-byte word-aligned address to the location counter */
9 _etext = .;
10 }> FLASH
11 ...
12 }
ALIGN(4) forces 4-byte word boundary alignment.
.
)Location counter is a special linker symbol denoted by a dot (.
).
This symbol is called "location counter" since linker automatically updates this symbol with location (address) information. (Location counter always tracks VMA of the section in use, not LMA)
You can use this symbol inside the linker script to track and define boundaries of various sections (e.g., _edata
, _sdata
, etext
). This will be used when we write a section of Reset_Handler()
where we copy the .data
section into the SRAM.
You can also set location counter to any specific value while writing linker script.
Location counter should appear only inside the SECTIONS command. Attempting to use the location counter outside the SECTIONS command will result in a linker error.
The location counter is incremented by the size of the output section.
Example: See the exmple in the "Linker Script Symbols" section.
A linker symbol is a name of an address.
An identifier such as a variable name, a function name is essentially a human-friendly representation of the memory address that is bound to that specific identifier. What we call as an identifier is called a symbol in the compiler's language! The compiler maintains what's called a "Symbol table" in every object file which is a group of symbol-to-Address mappings.
Symbols are created and maintained by the compiler.
A symbol declaration is not equivalent to a variable declaration what you do in your C program. It is done in a linker sript. We need to be able to create linker script symbols so we can capture some important information such as the boundary addresses of .data
section, etc.
xxxxxxxxxx
201/* linker script */
2__max_heap_size = 0x400; /* a symbol declaration (NOT a C variable!!!) */
3__max_stack_size = 0x200; /* a symbol declaration (NOT a C variable!!!) */
4
5...
6SECTIONS
7{
8 .text:
9 {
10 ...
11 end_of_text = .; /* capture the updated location counter value into this symbol */
12 /* at this point (.) will represent (VMA of .text) + (size of .text) */
13 }
14
15 .data:
16 {
17 start_of_data = 0x20000000; /* assign a value to a symbol 'start_of_data' */
18 ...
19 }
20}
L11: Usage of the "location counter (.)"
These linker symbols added to the linker script will get added to the symbol table of the final executable by the linker. Remember! This is the linking phase that we are talking about and the compiler has nothing to do with the linker script symbols.
Linker script symbols are accessible by the C program. This is how we can write a section of program inside the Reset_Handler()
to copy .data
section from FLASH to RAM.
To view the list of symbols (or symbol table) of an object or ELF file:
xxxxxxxxxx
21arm-none-eabi-nm <filename>.o
2arm-none-eabi-nm <filename>.elf
xxxxxxxxxx
6120000058 B _ebss
220000004 D _edata
3080004b6 T enable_processor_faults
408000768 T _etext
508000754 W ETH_IRQHandler
608000754 W ETH_WKUP_IRQHandler
Address of
_ebss
is 0x20000058. (0x20000058 is not the value stored in_ebss
.)Therefore, to calculate the size of
.data
section, you need to doxxxxxxxxxx
11uint32_t size = (uint32_t)&_edata - (uint32_t)&_sdata; /* CORRECT */
xxxxxxxxxx
31uint32_t size = _edata - _sdata; /* WRONG */
2/* This means that you want to access the value stored in the memory location associated with
3the name '_edata' and '_sdata'. No, we want the address associated with the names */
Nothing different than how a C variable name is associated with the memory location.
To link all the object files (suppose our main application does not use standard library calls):
xxxxxxxxxx
11arm-none-eabi-gcc -nostdlib -T stm32_ls.ld *.o -o final.elf
-nostdlib
since we are not using standard library calls in our main application
-T
flag is followed by the linker script name
*.o
since we are linking all the object files
Linker script for "Task Scheduler" project:
xxxxxxxxxx
581/* stm32_ls.ld */
2
3NTRY(Reset_Handler)
4
5MEMORY
6{
7 FLASH(rx): ORIGIN =0x08000000, LENGTH =1024K /* a space is necessary before '=' */
8 /* since code memory contains information not writable by user program */
9 SRAM(rwx): ORIGIN =0x20000000, LENGTH =128K
10
11 /* continue defining other memory regions here, if necessary */
12}
13
14SECTIONS
15{
16 .text :
17 {
18 *(.isr_vector) /* put vector table at the beginning of code memory */
19 *(.text) /* merge .text sections from all(*) input files */
20 *(.text.*) /* merge if anything like '.text.<function_name>' gets generated */
21 *(.init) /* merge c standard library specific section into .text */
22 *(fini) /* merge c standard library specific section into .text */
23 *(.rodata)
24 *(.rodata.*)
25 . = ALIGN(4); /* assigning 4-byte word-aligned address to the location counter */
26 /*_etext = .; // store the location counter value (end of text) to symbol _etext */
27 }> FLASH /* for .text VMA = LMA */
28 /* linker generates absolute address for VMA, LMA */
29
30 _la_data = LOADADDR(.data);
31
32 .data : /* section name and the ':' must be separated by space */
33 {
34 _sdata = .; /* location counter always tracks VMA, so here it contains SRAM */
35 *(.data)
36 *(.data.*)
37 . = ALIGN(4); /* force word-boundary alignment for the next section */
38 _edata = .; /* now location counter contains (SRAM + size of .data) */
39 }> SRAM AT> FLASH /* VMA = SRAM, LMA = FLASH */
40
41 .bss :
42 {
43 _sbss = .;
44 __bss_start__ = _sbss;
45 *(.bss)
46 *(.bss.*)
47 *(COMMON) /* While analyzing the map file you found some data you epxpected to
48 be placed in the .bss section but somehow placed under a COMMON
49 section created by the linker. This is how you bring it into
50 the the section you want it to be placed in. */
51 . = ALIGN(4); /* force word-boundary alignment for the next section */
52 _ebss = .;
53 __bss_end__ = _ebss;
54 . = ALIGN(4);
55 end = .;
56 __end__ = .;
57 }> SRAM /* .bss does not need LMA since it does not get loded onto FLASH */
58}
Nayak, K. (2022). Embedded Systems Programming on ARM Cortex-M3/M4 Processor [Video file]. Retrieved from https://www.udemy.com/course/embedded-system-programming-on-arm-cortex-m3m4/