Sunday, January 24, 2016

Linker Script - Memory Map - Startup

Hi,

In this article, I will try to cover all the basics about:

  • Linker Script
  • Memory Map
  • Map file
  • Start up code
  • Interrupt vector
  • Fault Handlers
  • Non fault handlers
All these topics are correlated, and it suitable to cover them in one article.

Platform:


I will be using STM32F103x6 controller which is from ARM-Cortex M3 family.

Memory Map:


From Data sheet of the controller, Memory Map section, we can deduce that the micro controller memory map is as follows:
  • 0x0800 0000 > 0x0801 FFFF: Flash Memory    (128 K)
  • 0x1FFF F000 > 0x1FFF F800: System Memory (2 K)
    • Carries the bootloader.
    • The bootloader is used to reprogram the flash memory using USART.
  • 0x2000 0000 > 0x2000 2800: SRam                (10 K)
  • 0x0000 0000 > 0x0800 0000: Aliased to one of the memories as follows
Screen shot from the controller datasheet
Vector table is a memory section, which contains some info needed by the controller:
  • Initial Stack Pointer Value (SP)
  • Initial Program Counter Value (PC)
  • Addresses of fault handlers
  • Addresses of non-fault handlers
  • Addresses of IRQ handlers
You shall place the vector table in the memory you are booting from.

Screen shot from the controller datasheet

Vector Table Offset Register:

  • TBLBASE
    • Vector table base
      • 0: Code
      • 1: RAM
  • TBLOFF
    • vector table offset from the bottom of the SRAM or Code memory
Both of these values are set to 0 on power on.
You have to change them if needed inside the initialization code.

From Cortex M3 Technical Ref Manual

Linker Script

Assume that we have a small program, consists of 2 files:

  • main.c
  • delay.c
The compiler generates:
  • main.o
  • delay.o
Then the linker generates
  • elf
lets have a look into main.o and appl.elf file, with any elf viewer (ex: "arm-none-eabi-readelf.exe"):

In the symbol table:
  • main.o > functions and  global variables mem addresses are not defined
  • appl.elf > functions and  global variables mem addresses are defined




Taking a further look at the addresses, you can find that:
  • Global Variable B is placed into address "0x20000020"
  • Function main is placed into address "0x08000769"
This is because:
  • Global variables normally are allocated in RAM, which starts at the address "0x20000000"
  • Functions, are allocated inside the code segment into the Flash, which starts at the address "0x08000000"
How can the linker know the addresses where it can allocate the variables, and the functions?
  • The Linker Script contains this info. The linker script contains:
    • The different memories exists into the system
    • The different segments exists and where shall they be allocated
    • Global variables, contains addresses info, which can be used by your C modules.
      • Ex: "_sdata" which contains the start address of the data section, and is needed by start up code to initialize data section
Linker script can be divided into more than one file, for convenience. 
Below is a linker script, divided into two files:
  • mem.ld
    • Where we will get familiar to some linker script keywords:
      • MEMORY
      • ENTRY
      • Variables definitions
        • you can define variables, that can be used inside the code
          • Ex: "_sdata" which contains the start address of the data section, and is needed by start up code to initialize data section
        • the variable is externed into the c file (ex:startup.c), and used, then the linker links it to the variable defined into the linker
      • PROVIDE
  • sections.ld
    • Where we will get familiar to
      • How to define different sections
        • vector table
        • init values of data
        • text
        • data
        • bss
        • noinit
        • stack
      • "KEEP" keyword
      • The '.' operator (location counter)

mem.ld:
/* -----------------------------------------------------------------------------------------------
// MEMORY
// Describe available memory
// MEMORY command is optional
// If MEMORY command is not used the linker assumed sufficient memory is available for different sections
// -----------------------------------------------------------------------------------------------*/
MEMORY
{
  RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 10K /*read-write-exec*/
  FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 128K /*read-execute*/
}

/* -----------------------------------------------------------------------------------------------
// Stack
// -----------------------------------------------------------------------------------------------

// ------------------- 0x20000000 (RAM Origin) ---------------------------------------------------
//          |  data   |                                       |
//          |---------|---------------------------------------|
//    R     |  bss    |                                       |
//          |---------|---------------------------------------|
//    A     |         |                                     | 
//          |  heap   |                                       |
//    M     |---------|---------------------------------------| <-- Limit of stack
//          |         |                                    |
//  (10K)   |  stack  |          stack size = 1K              |
//          |         |                                       |
// ------------------- 0x20002800 (RAM Origin + RAM LENGTH) --  <-- end of stack -----------------
// ---------------------------------------------------------------------------------------------*/

/* _estack (End of stack)*/
__stack = ORIGIN(RAM) + LENGTH(RAM);
_estack = __stack; /* STM specific definition */

/* Stack Size */
__Main_Stack_Size = 1024 ;
PROVIDE ( _Main_Stack_Size = __Main_Stack_Size ) ;
/* PROVIDE, defines the variable only if referenced and not defined 
 * (if defined in the cmd files or in object file, then do not define it)*/

/* Stack Limit */
__Main_Stack_Limit = __stack  - __Main_Stack_Size ;
PROVIDE ( _Main_Stack_Limit = __Main_Stack_Limit );

/* There will be a link error if there is not this amount of 
// RAM free at the end. */
_Minimum_Stack_Size = 256 ;

/* ------------------------------------------------------------------------------------------------
// -- Heap
// ----------------------------------------------------------------------------------------------*/
PROVIDE ( _Heap_Begin = _end_noinit ) ;
PROVIDE ( _Heap_Limit = __stack - __Main_Stack_Size ) ;

/* ------------------------------------------------------------------------------------------------
// -- ENTRY
// -- Define entry point for debuggers and simulators
// ----------------------------------------------------------------------------------------------*/
ENTRY(_start)


sections.ld
/* Sections Definitions */

/*
 * FLASH
 *  (.isr_vector) Vector Table
 *  (.inits) Initializations of .data and .bss
 *  (.text) Text
 * RAM
 *  (.data) initialized variables
 *  (.bss) uninitialized variables (initialized to zeros)
 *  (.noinit) uninitialized variables (uninitialized)
 *  (.checkstack) to check that the minimum size of stack fits in the RAM 
 * */

SECTIONS
{
    /* VECTOR TABLE*/
    .isr_vector : ALIGN(4)
    {
        FILL(0xFF) /* initialize this segment to 0xFF*/
        
        __vectors_start = ABSOLUTE(.) ;  /* __vectors_start = 0x00000000*/
        __vectors_start__ = ABSOLUTE(.) ; /* STM specific definition */
        KEEP(*(.isr_vector))     /* Interrupt vectors */ 
        /* KEEP: keep this section even if no one referencing it*/
    
        /* Put Startup code and ISRs after the vector table, for convinience!*/
        *(.after_vectors .after_vectors.*) /* Startup code and ISR */

    } >FLASH

_sidata = .;
/* INIT values of DATA and BSS */
    .inits : ALIGN(4)
    {
    /* data and bss initial values*/
         
        __data_regions_array_start = .; 
        /*without using ABSOLUTE, the locaation counter '.' value, is relative to the curretnt section*/
        /*here, the location counter "." = 0, because we did not use ABSOLUTE*/
        
        LONG(LOADADDR(.data)); /*initial values address in FLASH*/
        LONG(ADDR(.data)); /* Copy Target start address (in RAM)*/
        LONG(ADDR(.data)+SIZEOF(.data)); /* Copy Target End address (in RAM)*/

        
        __data_regions_array_end = .;
        __bss_regions_array_start = .;
        
        LONG(ADDR(.bss));         /* Copy Target start address (in RAM)*/
        LONG(ADDR(.bss)+SIZEOF(.bss)); /* Copy Target start address (in RAM)*/
                
        __bss_regions_array_end = .;

        /* End of memory regions initialisation arrays. */  
      

    } >FLASH


    
/* CODE */
    .text : ALIGN(4)
    {
        *(.text .text.*) /* all remaining code */

  /* read-only data (constants) */
        *(.rodata .rodata.* .constdata .constdata.*)

    } >FLASH


/* Initialized Data */
    .data : ALIGN(4)
    {
    FILL(0xFF)
        /* This is used by the startup code to initialise the .data section */
        _sdata = . ;         /* STM specific definition */
        __data_start__ = . ;
*(.data_begin .data_begin.*)

*(.data .data.*)

*(.data_end .data_end.*)
   . = ALIGN(4);

   /* This is used by the startup code to initialise the .data section */
        _edata = . ;         /* STM specific definition */
        __data_end__ = . ;

    } >RAM AT>FLASH
/*place .data section in RAM | initialize it in FLASH*/
    
   

    /* uninitialized data (initialized to zeros) */
    .bss (NOLOAD) : ALIGN(4)
    {
        __bss_start__ = .;     /* standard newlib definition */
        _sbss = .;              /* STM specific definition */
        *(.bss_begin .bss_begin.*)

        *(.bss .bss.*)
        *(COMMON)
        
        *(.bss_end .bss_end.*)
   . = ALIGN(4);
        __bss_end__ = .;        /* standard newlib definition */
        _ebss = . ;             /* STM specific definition */
    } >RAM
    

/*noinit section*/
    .noinit (NOLOAD) : ALIGN(4)
    {
        _noinit = .;
        
        *(.noinit .noinit.*) 
        
         . = ALIGN(4) ;
        _end_noinit = .;   
    } > RAM
    
    /* Mandatory to be word aligned, _sbrk assumes this */
    PROVIDE ( end = _end_noinit ); /* was _ebss */
    PROVIDE ( _end = _end_noinit );
    PROVIDE ( __end = _end_noinit );
    PROVIDE ( __end__ = _end_noinit );
    
    /*
     * Used for validation only, do not allocate anything here!
     *
     * This is just to check that there is enough RAM left for the Main
     * stack. It should generate an error if it's full.
     */
    ._check_stack : ALIGN(4)
    {
        . = . + _Minimum_Stack_Size ;
    } >RAM
    
}


Build Process


Map File

A map file is an output file from the linker.

It contains the start and end addresses of different memory sections as described inside the linker script.

Example:

In linker script, bss section is defined as follows:

  • It comes after the data section inside the RAM

/* uninitialized data (initialized to zeros) */
    .bss (NOLOAD) : ALIGN(4)
    {
        __bss_start__ = .;      /* standard newlib definition */
        _sbss = .;              /* STM specific definition */
        *(.bss_begin .bss_begin.*)

        *(.bss .bss.*)
        *(COMMON)
        
        *(.bss_end .bss_end.*)
    . = ALIGN(4);
        __bss_end__ = .;        /* standard newlib definition */
        _ebss = . ;             /* STM specific definition */
    } >RAM

The linker does these calculations:

  • RAM start address is 0x2000000  (defined in linker script)
  • data section size is equal to the size of all global vars = 0x8C (calculated by the linker)
  • __bss_start__ = _sbss = location counter after placing .data section 
  • __bss_start__ _sbss RAM start addr data section size 
  • __bss_start__ _sbss 0x2000000        0x8C                   0x20000008C
  • __bss_end__ = _ebss = location counter after placing .bss section
  • __bss_end__ _ebss __bss_start__  + .bss section size 
  • __bss_end__ = _ebss =  0x20000008C  + 0xA0 0x20000012C
Here is a snapshot from the Map file generated from the above linker script:

Map:
                0x20000000                _sdata = .
                0x20000000                __data_start__ = .
 *(.data_begin .data_begin.*)
 .data_begin    0x20000000        0x4 ./system/src/newlib/_startup.o
 *(.data .data.*)
 .data.argv.4231
                0x20000004        0x8 ./system/src/newlib/_syscalls.o
 .data.AHBPrescTable
                0x2000000c       0x10 ./system/src/cmsis/system_stm32f10x.o
                0x2000000c                AHBPrescTable
 .data.SystemCoreClock
                0x2000001c        0x4 ./system/src/cmsis/system_stm32f10x.o
                0x2000001c                SystemCoreClock
 .data.B        0x20000020        0x4 ./src/main.o
                0x20000020                B
 .data.impure_data
                0x20000024       0x60 d:/program files/gnu tools arm embedded/5.2 2015q4/bin/../lib/gcc/arm-none-eabi/5.2.1/../../../../arm-none-eabi/lib/armv7-m\libg_nano.a(lib_a-impure.o)
 .data._impure_ptr
                0x20000084        0x4 d:/program files/gnu tools arm embedded/5.2 2015q4/bin/../lib/gcc/arm-none-eabi/5.2.1/../../../../arm-none-eabi/lib/armv7-m\libg_nano.a(lib_a-impure.o)
                0x20000084                _impure_ptr
 *(.data_end .data_end.*)
 .data_end      0x20000088        0x4 ./system/src/newlib/_startup.o
                0x2000008c                . = ALIGN (0x4)
                0x2000008c                _edata = .
                0x2000008c                __data_end__ = .

.igot.plt       0x2000008c        0x0 load address 0x08001248
 .igot.plt      0x2000008c        0x0 ./system/src/stm32f1-stdperiph/misc.o

.bss            0x2000008c       0xa0 load address 0x08001248
                0x2000008c                __bss_start__ = .
                0x2000008c                _sbss = .
 *(.bss_begin .bss_begin.*)
 .bss_begin     0x2000008c        0x4 ./system/src/newlib/_startup.o
 *(.bss .bss.*)
 .bss.current_heap_end.3942
                0x20000090        0x4 ./system/src/newlib/_sbrk.o
 .bss.name.4230
                0x20000094        0x1 ./system/src/newlib/_syscalls.o
 *fill*         0x20000095        0x3 
 .bss.buf.5078  0x20000098       0x80 ./system/src/diag/Trace.o
 .bss.__malloc_sbrk_start
                0x20000118        0x4 d:/program files/gnu tools arm embedded/5.2 2015q4/bin/../lib/gcc/arm-none-eabi/5.2.1/../../../../arm-none-eabi/lib/armv7-m\libg_nano.a(lib_a-nano-mallocr.o)
                0x20000118                __malloc_sbrk_start
 .bss.__malloc_free_list
                0x2000011c        0x4 d:/program files/gnu tools arm embedded/5.2 2015q4/bin/../lib/gcc/arm-none-eabi/5.2.1/../../../../arm-none-eabi/lib/armv7-m\libg_nano.a(lib_a-nano-mallocr.o)
                0x2000011c                __malloc_free_list
 *(COMMON)
 COMMON         0x20000120        0x4 ./system/src/newlib/_syscalls.o
                0x20000120                errno
 COMMON         0x20000124        0x4 ./src/main.o
                0x20000124                A
 *(.bss_end .bss_end.*)
 .bss_end       0x20000128        0x4 ./system/src/newlib/_startup.o
                0x2000012c                . = ALIGN (0x4)
                0x2000012c                __bss_end__ = .
                0x2000012c                _ebss = .


  • When I try to add a global initialized variable int C to my code.int C;
     Then build
    • checking the map file, I find bss start and end are shifted by four bytes
    • __bss_start__ = _sbss = 0x20000090
    • __bss_end__ = _ebss = 0x20000130

0x20000090                __bss_start__ = .
0x20000090                _sbss = .
...
...
...
0x20000130                __bss_end__ = .
0x20000130                _ebss = .

Vector Table

When the controller starts

  • it gets the vector table address from the "Vector Table Offset Register".
  • Initializes the PC to the value defined in the vector table
  • When fault or non fault exception happens, the controller knows the address of the exception handler from the vector table.
  • When IRQ (Interrupt Request) happens, the controller knows the address of its handler from the vector table

Vector Table: Micro controller Data sheet


Here is a snapshot of the first part of the vector table as defined in the data sheet:


Microcontroller Data sheet: Vector Table


Vector Table: In C code



In your code, you shall define your vector table according to the data sheet.

__attribute__ ((section(".isr_vector"),used))
pHandler __isr_vectors[] =
  {
  // Core Level - CM3
      (pHandler) &_estack, // The initial stack pointer
      Reset_Handler, // The reset handler
      NMI_Handler, // The NMI handler
      HardFault_Handler, // The hard fault handler
      MemManage_Handler,                        // The MPU fault handler
      BusFault_Handler,                        // The bus fault handler
      UsageFault_Handler,                        // The usage fault handler
      0,                                        // Reserved
      0,                                        // Reserved
      0,                                        // Reserved
      0,                                        // Reserved
      SVC_Handler,                              // SVCall handler
      DebugMon_Handler,                         // Debug monitor handler
      0, // Reserved
      PendSV_Handler, // The PendSV handler
      SysTick_Handler, // The SysTick handler

      WWDG_IRQHandler, // Window WatchDog
      PVD_IRQHandler, // PVD through EXTI Line detection

      TAMPER_IRQHandler, // Tamper through the EXTI 


  • __isr_vector, will not be placed in .data section as any other initialized array.
  • Instead it will be placed in the memory section ".isr_vector".
  • The linker does this as a result of the line: __attribute__ ((section(".isr_vector"),used))
  • Note that the variable _estack used in the above code, is not defined in the code, it is defined in the linker script (Check mem.ld above).



Vector Table: Linker Script


You shall define a memory section for vector table in the linker script.
You shall place it in the same place reported by the "Vector Table Offset Register" at start up (normally in the start of the flash)


.isr_vector : ALIGN(4)    {
    FILL(0xFF) /* initialize this segment to 0xFF*/
    __vectors_start = ABSOLUTE(.) ;  /* __vectors_start = 0x00000000*/
    __vectors_start__ = ABSOLUTE(.) ; /* STM specific definition */
    
    KEEP(*(.isr_vector))      /* Interrupt vectors */
    /* KEEP: keep this section even if no one referencing it*/
    
    /* Put Startup code and ISRs after the vector table, for convinience!*/
    *(.after_vectors .after_vectors.*) /* Startup code and ISR */
} >FLASH


What happens when the controller resets?


  • Get the PC from vector table
    • In our example: PC = Reset
  • Reset function calls the start up function
void __attribute__ ((section(".after_vectors"),noreturn))Reset_Handler (void){  _start ();} 
    • attribute: section(".after_vectors")
      • Tells the linker to place this function into the memory section .after_vectors defined into the linker script
    • attribute: noreturn
      • Tells the compiler that this function will not return
      • This makes sense, because no one calls this function, hence no need to save any values in the stack, no need to save return address inside link register
  • The startup function:
    • Makes some initialization
    • Calls the main (Or may call the bootloader, and the bootloader calls the main)

Start up Code:

Start up code is a code that usually is provided by the compiler, however you can modify it, or even write your own.

Normally start up code shall make the following:

  • Initialize RCC (Reset and Clock Controller)
  • PLL initialization
  • initialize .data section
__initialize_data(&_sidata, &_sdata, &_edata);
    • Note that _sidata, _sdata and _edata, are all defined in the linker script (check sections.ld above)
      • they represent
        • .inits section start addr
        • .data section start addr
        • .data section end addr
    • The function implementation can be
      • memcpy(_sidata_sdata_edata - _sdata);
  • initialize .bss section
__initialize_bss(&__bss_start__, &__bss_end__);

    • Note that __bss_start__ and __bss_end__, are defined in the linker script (check sections.ld above)
      • they represent
        • .bss section start addr
        • .bss section end addr
    • The function implementation can be 
      • memset (__bss_start__, 0, __bss_end__ - __bss_start___);
    • Call the main
main ();


Fault Handlers

Usually, fault handlers try to:

  • Print an error on RS232 for example
  • Reset the system.

So, you do not return from a fault handler.
Hence, they shall be no return functions.
so, use naked attribute, to prevent the compiler adding any stack push/pop stuff 
__attribute__ (naked)

  • This is also useful in the case where the fault happened in the stack pointer.
    • Assume that the stack pointer is pointing to a wrong value
      • you can do this using a debugger
        • Set a break point before a function call
        • Overwrite the stack pointer with a value out of RAM
        • Step into the function call 
        • The fault handler of busfault will be called due to undefined address access
    • If you did not define the fault handler as naked function
    • Each time the fault handler will be invoked, it will try to push some data on the stack.
    • Since the stack pointer carries a wrong value, the fault handler will be called again.
    • This will happen again and again, and the system will never reset.

Non Fault handlers


  • Non fault exceptions are not critical.
  • You can define handlers for non fault exceptions if you want.
  • In case you do not want to define non fault handlers, you can just define "Unhandled_exception" function, which for example prints "unhandled exception".
  • You can make weak aliasing for all non fault handlers to the unhandled_exception function

void __attribute__ ((weak, alias ("unhandled_exception"))) SysTick_Handler(void);


    • If you defined the function SysTick_Handler, then, the linker will link it
    • If you did not, the linker will link it to the function "unhandled_exception
  • The same concept can be applied to the IRQ handlers

void __attribute__ ((weak, alias ("Default_Handler"))) WWDG_IRQHandler(void);




1 comment: