
If you are one of those software developers who deal with hardware and write code which runs directly on hardware (in short, Firmware Engineer or Embedded Software Developer). Then you might find this article helpful because this is one of those things which seem to be scarce in term of available content over the web and require very minimal attention during development due to the fact that IDE’s (Integrated Development Environment) these days set it (linker script) up automatically when you create the project. But knowledge about linkers and relocator will load you with a strong understanding of the whole system and help you in debugging memory related bugs.
Compiler, How it works?
To understand the working of the compilation process utterly, one must take references from some textbook because of its complexity and ton of literature. But for now, I would walk you through the basic understanding of compiler which will set the context for you to understand the linker script.
So, When you hit the “compile or build” option in IDE (i.e Eclipse, Keil, IAR, etc). There are a couple of actions took place behind the scene which results in the generation of the binary output file (.bin or .hex). Below picture shows the whole process.

Compilation process
First and foremost, All the source (.c/.cpp files) are collected and processed one by one in the PreProcessing process, all the preprocessor (conditional and static) like (#define, #ifdef, etc.) are resolved, comments are removed, and required header files will be included and this process generates an intermediate file (.i file) which we can also say a pure source file. Following command can generate this file.
gcc -E foo.c -o foo.i
Now, Compilation Process starts and take the intermediate file (generated in the previous step) as input and produces the assembly file (.as file). Assembly file is closer to the processor and considered native to processor’s architecture because it follows processor’s supported instruction set.
Once the assembly file is generated, Assembler is invoked which take assembly file as input and produces an object file (.o file).
At this point in time, the compilation process is finished technically. But we still have not got any executable file which can be loaded into the flash of the microcontroller or could be started as a process (in case of OS environment like, Linux, Mac etc.)
So, What are object files?
You can think of it as a collection of functions and variables (initialized, uninitialized, read-only) in a single package. Object file store the code and data with the help of segment and sections. for example, All the instruction goes into the “.text” section by default, similarly, all global variables are packed inside the “.data” section. Some file format standards are defined for the object file. COFF (common object file format) and ELF (executable and loadable file) are most famous. Every object file has a symbol table which keeps the record for each function and variable by its name so that it can be referenced from another object file at the linking time.

The above picture shows an example, how symbol table is storing information about the references of functions and variables. ‘00000000’ is just the serial number. ‘00000033’ is a location relative to this section (.text) only, ‘g’ tell the symbol type and in this case, it is global. ‘.text’ is the segment name, You can extract similar information from an object file (.o file) by running the following command.
objdump -d <object file>
For example,
objdump -d main.o > main.txt
the above line will dump content from main.o into main.txt, which you can open and analyze in any text editor. To Read more about object file I would suggest you read this.
Linking it all together.
Linker’s job is to take all the object file as input and resolve references (function calls and variable names) between them and generate a final object file. The final object file contains all the code, which is required for a program to work correctly without any dependency on any other file.
Now from this point in time, the final object can be executed directly in OS environment where memory management will be taken care by OS itself. But for an embedded processor, Once more step is required which is called memory relocation. Memory relocator maps the different sections in object file into absolute memory location as per the rules specified by “linker script file” or “ld file”.
Generally, nowadays, Linker and Relocator are combined into a single program called “ld”, which does both the task of linking and relocation.
By manupulating the ld file, one can easily configure which section goes into which region of memory.
Now, As you have the basic background of the compilation process and what compiler and linker do? Lets now understand how we can tell the linker to place code & data from object files into the required memory location. Let understand the linker script.
Primarily, There are 3 things to understand in linker script and it makes the basic foundation for all the functionality.
- Memory Region
- Input/Output Section
- Location Counter (“.” dot variable)
Every Linker Script contains instructions, in a certain format and it a basic one shown below.
1. ENTRY(Reset_Handler)2. /* Specify the memory areas */
3. MEMORY
4. {
5. FLASH (rx) : ORIGIN = 0x8000000, LENGTH = 2048K
6. }7. /* Define output sections */
8. SECTIONS
9. {10. /* The program code and other data goes into FLASH */
11. .text:
12. {
13. . = ALIGN(4);
14. *(.text) /* .text sections (code) */
15. . = ALIGN(4);
16. _etext = .; /*define a global symbols at end of code */
17. } >FLASH
18. }
Above code snippet, shows a basic linker script and I will explain you line by line, what above code means and how you can modify as per your requirement.
1. ENTRY(Reset_Handler)
“ENTRY” tells the linker to set the address of the passed argument (function name is C or ASM file) i.e, Reset Handler as the starting value in PC (program counter) which tells the processor to start executing instruction right from that address. You are free to change “Reset_Handle” to the name of your own function which you would your program with start with.
The “main” function is not the first function that gets executed in “Microcontrollers”. Before main, there are a whole lot of actions already had occurred in the background (all the initialization of essential peripheral, copying the data (variables) from flash to RAM, and clock initialization) and then Reset Handler function branches to the main function. You could find the implementation for Reset Handler in “startup” file which in most cases is in assembly language.
If you miss ENTRY instruction, then linker figures out the entry point by the following manner.
- the value of a target-specific symbol, if it is defined; For many targets, this is
start - the address of the first byte of the ‘.text’ section, if present;
- and if not anything from above, then address
0x00000000.
2. /* Specify the memory areas */
Line 2 shows how you can mark comment just in the same manner as you do in C file, block comment.
Memory Region
3. MEMORY
4. {
5. FLASH (rx) : ORIGIN = 0x8000000, LENGTH = 2048K
6. }
Line 3 to 6, Create a memory alias called “FLASH” of type read and execute, which starts from address 0x8000 0000 and expand up to 2048K locations. So, Now you can place your read-only content into memory region called “FLASH” and has a size of 2MB. You can follow the datasheet of the microcontroller and change it as per that. “r” and “x” flag at line 5 marks this memory as read-only and executable. similarly, “w” flag will make it writable also. This explains the 1st point out of 3 about the linker as mentioned above.
Input/Output Section
8. SECTIONS
9. {
This part of the script is fixed and you will find in every linker file. The enclosed information inside parenthesis (line no. 9 and line no 18) contains the information about mapping input section from other object files to a particular memory location.
11. .text:
12. {
13. . = ALIGN(4);
14. *(.text) /* .text sections (code) */
15. . = ALIGN(4);
16. _etext = .; /*define a global symbols at end of code */
17. } >FLASH
Line 11, .text is the name of the output segment. You can specify your own name, but it is strictly recommended to stick with the standard. There are few standard names, and You can check the list, few paragraphs below.
Line 13, . = ALIGN(4); “.” is a location counter. It is one of the essential things to understand about linker script and explained below.
Line 14, tells the linker to copy all the “.text” sections from all the input object file to the output .text section.
Line 17, } >FLASH directs the linker to store all the content in output .text section into FLASH memory region which is already defined as Memory region above.
dot (.) | Location counter
This special variable keeps the value of offset for that particular section in which its being used. Since the . always refers to a location in an output section, it may only appear in an expression within a SECTIONS command.Assigning a value to . will cause the location counter to be moved. This may be used to create holes in the output section. The location counter may never be moved backwards.ExampleSECTIONS
{
output :
{
file1(.text)
. = . + 1000;
file2(.text)
. += 1000;
file3(.text)
} = 0x12345678;
}the .text section from file1 is located at the beginning of the output section output. It is followed by a 1000 byte gap. Then the .text section from file2 appears, also with a 1000 byte gap following before the .text section from file3. The notation = 0x12345678 specifies what data to write in the gaps.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.
Virtual Memory Address (VMA) and Load Memory Address (LMA)
Virtual Memory Address (VMA) represents the memory location in RAM of the microcontroller, as those memory regions are volatile in nature( i.e, stored data clears when power ran out), so a copy initial data which is meant to be stored in RAM, must be kept in FLASH, and when system boots up, those data need to be copied from FLASH to RAM, to retain their initial value. you can always find this piece of code in the startup file, which generally happens to be in assembly language. This startup file is same where Reset_Handler resides and the main function gets called right here.
Load Memory Address (LMA) usually represents the memory location which supports storing of Data and execution of INSTRUCTION and should be non-volatile in nature. Flash Memory is one of those memories available built-in into the Microcontroller, you have to refer the datasheet of the microcontroller to know the available LMAs.
If you liked this post, Please consider subscribing for newsletter – I promise, I won’t spam your inbox. 😊

Leave a Reply