RISC-V MCU ld link script description

1. What is a ld link script?

Usually, the last step of program compilation is linking. This process links multiple target files (. o) and library files (. a) input files into an executable output file (. elf) according to the "*. ld" link file. It involves the allocation of space and address, symbol parsing and relocation.

The ld link script controls the whole link process, which is mainly used to specify how to allocate the space and address of the program, data and other content segments in the input file in the output file. Generally speaking, the link script is used to describe the segments in the input file, map them to the output file, and specify the memory allocation in the output file.

2. Main contents of ld link script

2.1 link configuration (optional)

Common configurations include entry address, output format, definition of symbol variables, etc. For example:

ENTRY( _start ) /* Entry address */ 
 
__stack_size = 2048; /* Define stack size */
PROVIDE( _stack_size = __stack_size );/* Definition_ stack_size symbol, similar to global variable */

2.2 memory layout definition

Allocate the Flash and RAM space of MCU, where ORIGIN defines the starting address of the address space and LENGTH defines the LENGTH of the address space.

The syntax is as follows:

MEMORY
{
    name[(attr)] : ORIGIN = origin, LENGTH = length
    ...
}

attr here can only consist of the following features

'R' - Read-only section

'W' - Read/write section

'X' - Executable section

'A' - Allocatable section

'I' - Initialized section

'L' - Same as I

'!' - Invert the sense of any of the attributes that follow

2.3 segment link definition

Used to define the link distribution of text, data, bss and other segments of the target file (. o). The syntax is as follows:

SECTIONS
{
    section [address] [(type)] :
    [AT(lma)]
    [ALIGN(section_align) | ALIGN_WITH_INPUT]
    [SUBALIGN(subsection_align)]
    [constraint]
    {
        output-section-command
        output-section-command
        ...
    } [>region] [AT>lma_region] [:phdr :phdr ...] [=fillexp] [,]
    
    ...
}
 
/* Most segments only use some of the above attributes, which can be abbreviated as follows */
SECTIONS
{
      ...
      secname :
      { 
        output-section-command 
      } 
      ...
}

The essence of link script is to describe input and output. secname represents the segment of the output file, and output section command is used to describe which files this segment of the output file is extracted from, that is, the input target file (. o) and the library file (. a).

Section is divided into two types: loadable and allocatable. A memory segment that cannot be loaded or allocated. It usually contains some debugging and other information.
loadable: this segment should be loaded into memory when the program is running.
allocatable: this segment is reserved and no other content should be loaded (in some cases, these memory must be zeroed).

Both loadable and allocatable section s have two addresses: "VMA" and "LMA".
VMA (the volatile memory address): the address of the section when the output file is run. Optional, unconfigured.
LAM (load memory address): the address when the section is loaded.
In most cases, these two addresses are the same. However, in some cases, the code needs to be loaded from Flash to ram for operation. At this time, the Flash address is LAM and the RAM address is VMA. For example:

.data :
    {
        *(.data .data.*)
        . = ALIGN(8);
        PROVIDE( __global_pointer$ = . + 0x800 );
        *(.sdata .sdata.*)
        *(.sdata2.*)
        . = ALIGN(4);
        PROVIDE( _edata = .);
   } >RAM AT>FLASH

In the above example The contents of the data segment will be placed in Flash, but when running, it will be loaded into RAM (usually initializing global variables), that is VMA of data section is ram and LMA is Flash.

3. Common keywords and commands

3.1 ENTRY

Syntax: ENTRY(symbol), the first instruction to be executed in the program, also known as the entry point. Example:

/* Entry Point */
ENTRY( _start ) /* CH32V103 J handle for startup file_ Reset instruction*/

3.2 PROVIDE

Syntax: PROVIDE (symbol = expression), which is used to define a symbol that can be referenced, similar to a global variable. Example:

PROVIDE( end = . ); 

3.3 HIDDEN

Syntax: HIDDEN (symbol = expression). For ELF target port, the symbol will be hidden and will not be exported. Example:

HIDDEN (mySymbol = .);

3.4 PROVIDE_HIDDEN

Syntax: PROVIDE_HIDDEN (symbol = expression) is a combination of PROVIDE and HIDDEN, similar to local variables. Example:

PROVIDE_HIDDEN (__preinit_array_start = .);

3.5 point symbol '.'

‘.’ Represents the current address. It is a variable and always represents an address in the output file (it is continuously increased according to the size of the input file section, cannot be reversed, and is only used in the SECTIONS instruction). It can be assigned or assigned to a variable; Arithmetic operations can also be performed to generate memory space of a specified length. Example:

PROVIDE( end = . );   /* The current address is assigned to the end symbol */
 
.stack ORIGIN(RAM) + LENGTH(RAM) - __stack_size :
{
    . = ALIGN(4); 
    PROVIDE(_susrstack = . );
    . = . + __stack_size;    /* Current address plus__ stack_size, length, generation__ stack_size length space*/
    PROVIDE( _eusrstack = .);
} >RAM 

3.6 KEEP

When the linker uses ('-- GC sections') for garbage collection, KEEP() can make the content of the marked segment unclear. Examples

.init :
{
    _sinit = .;
    . = ALIGN(4);
    KEEP(*(SORT_NONE(.init))) 
    . = ALIGN(4);
    _einit = .;
} >FLASH AT>FLASH

3.7 ASSERT

Syntax: ASSERT(exp, message). Ensure that exp is a non-zero value. If it is zero, it will exit the linked file in the form of error code and output message. It is mainly used to add assertions and locate problems.

Example:

/* The usage of ASSERT */
PROVIDE (__stack_size = 0x100);
 
.stack
{
    PROVIDE (__stack = .);
    ASSERT ((__stack > (_end + __stack_size)), "Error: No room left for the stack");
}
/* When "_stack" is greater than "__end + _stack_size", an error will appear when linking and prompt "Error: No room left for the stack" */
 

4. Full ld link script example

Take RISC-V MCU CH32V103 as an example.

ENTRY( _start )  
 
__stack_size = 2048;
 
PROVIDE( _stack_size = __stack_size );
 
 
MEMORY
{
  FLASH (rx) : ORIGIN = 0x00000000 , LENGTH = 0x10000
  RAM (xrw) : ORIGIN = 0x20000000 , LENGTH = 0x5000
}
 
 
SECTIONS
{
 
    .init :
    {
        _sinit = .;
        . = ALIGN(4);
        KEEP(*(SORT_NONE(.init)))
        . = ALIGN(4);
        _einit = .;
    } >FLASH AT>FLASH
 
  .vector :
  {
      *(.vector);
      . = ALIGN(64);
  } >FLASH AT>FLASH
 
  .flag :
  {
      . = ORIGIN(FLASH)+0x8000;
      KEEP(*(SORT_NONE(.myBufSection)))
  }>FLASH AT>FLASH
 
    .text :
    {
        . = ALIGN(4);
        *(.text)
        *(.text.*)
        *(.rodata)
        *(.rodata*)
        *(.glue_7)
        *(.glue_7t)
        *(.gnu.linkonce.t.*)
        . = ALIGN(4);
    } >FLASH AT>FLASH 
 
    .fini :
    {
        KEEP(*(SORT_NONE(.fini)))
        . = ALIGN(4);
    } >FLASH AT>FLASH
 
    PROVIDE( _etext = . );
    PROVIDE( _eitcm = . );    
 
    .preinit_array  :
    {
      PROVIDE_HIDDEN (__preinit_array_start = .);
      KEEP (*(.preinit_array))
      PROVIDE_HIDDEN (__preinit_array_end = .);
    } >FLASH AT>FLASH 
    
    .init_array     :
    {
      PROVIDE_HIDDEN (__init_array_start = .);
      KEEP (*(SORT_BY_INIT_PRIORITY(.init_array.*) SORT_BY_INIT_PRIORITY(.ctors.*)))
      KEEP (*(.init_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .ctors))
      PROVIDE_HIDDEN (__init_array_end = .);
    } >FLASH AT>FLASH 
    
    .fini_array     :
    {
      PROVIDE_HIDDEN (__fini_array_start = .);
      KEEP (*(SORT_BY_INIT_PRIORITY(.fini_array.*) SORT_BY_INIT_PRIORITY(.dtors.*)))
      KEEP (*(.fini_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .dtors))
      PROVIDE_HIDDEN (__fini_array_end = .);
    } >FLASH AT>FLASH 
    
    .ctors          :
    {
      /* gcc uses crtbegin.o to find the start of
         the constructors, so we make sure it is
         first.  Because this is a wildcard, it
         doesn't matter if the user does not
         actually link against crtbegin.o; the
         linker won't look for a file to match a
         wildcard.  The wildcard also means that it
         doesn't matter which directory crtbegin.o
         is in.  */
      KEEP (*crtbegin.o(.ctors))
      KEEP (*crtbegin?.o(.ctors))
      /* We don't want to include the .ctor section from
         the crtend.o file until after the sorted ctors.
         The .ctor section from the crtend file contains the
         end of ctors marker and it must be last */
      KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .ctors))
      KEEP (*(SORT(.ctors.*)))
      KEEP (*(.ctors))
    } >FLASH AT>FLASH 
    
    .dtors          :
    {
      KEEP (*crtbegin.o(.dtors))
      KEEP (*crtbegin?.o(.dtors))
      KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .dtors))
      KEEP (*(SORT(.dtors.*)))
      KEEP (*(.dtors))
    } >FLASH AT>FLASH 
 
    .dalign :
    {
        . = ALIGN(4);
        PROVIDE(_data_vma = .);
    } >RAM AT>FLASH    
 
    .dlalign :
    {
        . = ALIGN(4); 
        PROVIDE(_data_lma = .);
    } >FLASH AT>FLASH
 
    .data :
    {
        *(.gnu.linkonce.r.*)
        *(.data .data.*)
        *(.gnu.linkonce.d.*)
        . = ALIGN(8);
        PROVIDE( __global_pointer$ = . + 0x800 );
        *(.sdata .sdata.*)
        *(.sdata2.*)
        *(.gnu.linkonce.s.*)
        . = ALIGN(8);
        *(.srodata.cst16)
        *(.srodata.cst8)
        *(.srodata.cst4)
        *(.srodata.cst2)
        *(.srodata .srodata.*)
        . = ALIGN(4);
        PROVIDE( _edata = .);
    } >RAM AT>FLASH
 
    .bss :
    {
        . = ALIGN(4);
        PROVIDE( _sbss = .);
          *(.sbss*)
        *(.gnu.linkonce.sb.*)
        *(.bss*)
         *(.gnu.linkonce.b.*)        
        *(COMMON*)
        . = ALIGN(4);
        PROVIDE( _ebss = .);
    } >RAM AT>FLASH
 
    PROVIDE( _end = _ebss);
    PROVIDE( end = . );
 
    .stack ORIGIN(RAM) + LENGTH(RAM) - __stack_size :
    {
        . = ALIGN(4);
        PROVIDE(_susrstack = . );
        . = . + __stack_size;
        PROVIDE( _eusrstack = .);
    } >RAM 
 
}

Tags: debian p2p GNU

Posted by jpschwartz on Sat, 21 May 2022 03:19:46 +0300