Tuesday, September 21, 2021

Linker Script

Linker Directives Basics - Based on the ARM Cortex -M3/M4 Processor.

Before going to the Linker script, we need to understand the few basic concepts of the embedded system.

  1. Compilation process or Build process
  2. Definition of Cross-compilation and cross-compiler Tool chain
  3. Different data and sections of program
  4. Write Linker script

================================================
Compilation Process:

Link:  GCC

Compilation is the method of converting the source code into object code. During compilation process, compiler tests the source code for syntactic or structural errors and produces the object code if the source code is error-free.


Structure of the relocatable object file.


Let’s Understand the Four important Sections in the ELF File.

  • .text – Our c program is stored in this section. We only have read and execute permission to this section and not write permission.
  • .data – All initialized global and static variables are stored in the .data section. (Read and Write Permission)
  • .rodata – Constants and literals are stored in the .rodata section. (Read Permission)
  • .bss – This section contains uninitialized global and static variables. (Read and Write Permission)

Executable File Structure


================================================
Definition of Cross-compilation,
Cross-compiler Tool chain.

A Cross Compiler is a complier which compiles Source code at one platform and runs it at any other platform.
If we define the word "host" to mean a computer on which you are compiling. 
"target" as the computer on which you want to run the code, 
then a native compiler is one where the target and the host are the same (kind). 
A cross-compiler is a compiler where the target is different from the host.
example of  cross-compiler tool chain is  gnu toolchain which used for ARM processor/MCUs.
gcc is the cross-compiler.

Different data and sections of program




Variables defined in the program may be Global, static, constant or local variables. 
  • In Global variable declaration: initialized global variable or uninitialized global variable.
  • In Static variable declaration: local static or global static and also initialized or uninitialized
  • Constant variable: global constant or Local constant
  • Global and Static variables initialized with zero
  • Local variables
#include <stdio.h>

/*Global variable declaration*/
unit32_t global_var_i=100;
unit32_t global_var_i_zero=0;
unit32_t global_var_ui;

/*Global static variable declaration*/
static uint32_t gstatic_var_i=200;
static uint32_t gstatic_var_i_zero=0;
static uint32_t gstatic_var_ui;

/*Global constant variable declaration*/
unit32_t const const_var=50;

int main()
{

/*Local Static variable declaration*/

static unit32_t Lstatic_var_i=2;
static unit32_t Lstatic_var_ui;

/*Local constant variable declaration*/
uint32_t const local_const_var=10;

/*Local variable declaration*/
uint8_t local_count_main;

Display_func();
}


void Display_func()
{
     
    /*Local variable declaration*/
    uint8_t local_count_func;

        for(local_count_func=0;local_count_func<20;local_count_func++)
        {
             printf("value is =%d\n", local_count_func);
        }

}




Note: in most of the microcontroller development environment commonly used qualifiers are volatile, const volatile and Register then where are the volatile and Register variables are stored?

Register: 
         
Syntax:
            
register data_type var_name = var_value;            
register unit8 a=10;


Register variables are similar to auto variable but it is stored in the CPU registers.

💬💬  it is not possible to take the address of register variable, regardless whether the variable is actually placed in a register." so it is compiler dependent.
if you use objdump for analysis, which may be difference at different compilation time.


volatile and Const volatile:

volatile is a qualifier that is applied to a variable when it is declared. It tells the compiler that the value of the variable may change at any time. volatile suggest the compiler that avoid optimization hence its variable scope depends on the variable declaration like global or local and storage depends on the "variable scope"

To understand usage of const volatile, Take one case study like toggle the LED every 1ms from the pin 1 of port 0 which address is 0x4000h. 

Here address of the pin is fixed. address won't change at the run-time so we need to use const but value of the of pin may toggle every 1ms.

uint8_t volatile * const port_leg_reg = (uint8_t *) 0x4000h;

conclusion: volatile variable store in the stack or data segment depends on "variable scope" but "Const volatile" logical to place the variable in the memory space is RAM.

====================================================================================================

Write Linker script

The linker command file instructs the linker on how to combine the object files and where to place the binary code and data in the target embedded system. 
The main function of the linker is to combine multiple object files into a larger relocatable object file, a shared object file, or a final executable image.

  • linker scripts are written using GNU linker command language
  • GNU linker script has the extension of .ld


LINK:  https://ftp.gnu.org/old-gnu/Manuals/ld-2.9.1/html_chapter/ld_3.html

Linker script commands

  • ENTRY
  • MEMORY
  • SECTIONS
  • KEEP
  • ALIGN
  • AT>
ENTRY: This command used to set the "Entry point address" information in the final .elf file
most of the microcontroller "Reset_Handler" is the Entry point into the application
ex: ENTRY(_Symbol_Name_)
ENTRY(Reset_Handler)

MEMORY: The "MEMORY" command describes the location and size of blocks of memory in the target.

MEMORY 
  {
    name (attr) : ORIGIN = origin, LENGTH = len
    ...
  }

MEMORY
 {
  /* Code flash area */
  iROM_0     : ORIGIN = 0x00000000,   LENGTH  = 48k
  /* Data flash */
  iROM_1     : ORIGIN = 0x01000000,   LENGTH  = 32k
}

SECTIONS: 

In the following example, the command script arranges the output file into three consecutive sections, named .text.data, and .bss, taking the input for each from the correspondingly named sections of all the input files:

SECTIONS { 
  .text : { *(.text) }
  .data : { *(.data) } 
  .bss :  { *(.bss)  *(COMMON) } 
} 

  1. .text for code & constants
  2. .bss for uninitialized data
  3. .stack for local variables
  4. .data for initialized data

ALIGN:

You can increase an output section’s alignment by using ALIGN.
SECTIONS
 {
/* Start of internal RAM area (iRAM) */         .data :>    iRAM_0     /* initialized data */         .sldata align(4) :>.     /* user defined segment for initialized data */
}

We need to Know which type of Memory Alignment required in your microcontroller Manual. 
Please refer the Reference manual for the Same
ARM processors support the following data types:
  • Byte 8 bits
  • Halfword 16 bits
  • Word 32 bits

ARM instructions are exactly one word and are aligned on a four-byte boundary. Thumb® instructions are exactly one halfword and are aligned on a two-byte boundary. opcodes are a variable number of bytes in length and can appear at any byte alignment

Example of .ld as follows

ENTRY(Reset_Handler)
MEMORY
{
     ROM     : ORIGIN = 0x00000000,   LENGTH  = 16K
  /* Data flash */
  ROM_1     : ORIGIN = 0x01000000,   LENGTH  = 16K  /* Code segment*/
  RAM_0     : ORIGIN = 0X80000000,   LENGTH = 32k  /* Data segment*/
  iRAM_R     : ORIGIN = 0xFEF00000,   LENGTH = 64k /*Retension RAM*/
}
SECTIONS
{
__text_start__=.:

.text :  /* collecting .text memory address from different object files and 
                making single .text segment in the final object file or 
                relocatable object files to final executable file*/
{
    { *(.text)
 }
.=ALIGN(4);
__text_End__=.;
__data_start__=.:
.data :
{
   { *(.data) } 
}
.=ALIGN(4);
__data_End__=.;
__bss_start__=.:
.bss :
{
   { *(.bss)  *(COMMON) }
}
.=ALIGN(4);
__bss_End__=.;

__rodata_start__=.:
.rodata:
{
   { *(.rodata) } 
}
.=ALIGN(4);
__rodata_End__=.;

/*Optional Compiler directives are used*/

}

Memory Regions are calculated by using labels:
text segment total memory calculated by using following labels __text_start__=.;__text_End__=.;
__text_start__=.; Start address of Text segment of merged elf file 
__text_End__=.; End address of Text segment of merged elf file 

=====================================================================
AT command specifies the exact load address of the section.
The AT> keyword takes the name of a memory region as an argument.

Every loadable or allocatable output section has two addresses.
  • The first is the VMA, or virtual memory address. This is the address the section will have when the output file is run.
  • The second is the LMA, or load memory address. This is the address at which the section will be loaded.
In most cases the two addresses will be the same.
An example of when they might be different is when a data section is loaded into ROM,
and then copied into RAM when the program starts up (this technique is often used to initialize
global variables in a ROM based system).
In this case the ROM address would be the LMA, and the RAM address would be the VMA.

SECTION
{ 
    .data : 
        { _sdata = .; 
        *(.data*); 
        _edata = .; 
        } > ram AT >rom 
}

Linker detail: Linker basic
================================================================================================

No comments:

Post a Comment