Bienvenido: Ingresar

This local extension action "SlideShow" requires moin 1.8.

Quitar mensaje
location: WebHome / HerramientasProgramacion / LinkerScript

Linker

En este proceso, el programa que realiza el linker, debe conocer en que lugar del mapa de memoria del microcontrolador se ubicarán las variables, el código y las constantes de nuestro programa, esta información puede cambiar, dependiendo del modelo de microcontrolador o de la empresa que fabricó el mismo, por ejemplo un empresa X puede haber decidido disponer el mapa de memoria RAM en un lugar distinto que la empresa Y.

Para poder tener una idea mas clara de la función del linker y de los datos que necesita el mismo, podemos hacer una división de los tipos de información que esta compuesto una imagen ROM ejecutable

Este esquema, se suele complicar mas si tenemos en cuenta que en diversos sistemas embebidos, en el momento de arranque se pueden remapear chips, habilitar dispositivos "memory-management", etc

Por esta razón es necesario conocer la diferencia entre dos conceptos, Load Memory Address (LMA), y Virtual Memory Address (VMA) El concepto de Memoria Virtual, no hace referencia a la idea de virtual vs física, tampoco a la idea de memorias paginadas, simplemente se refiere a la diferencia entre un código o dato en el archivo ejecutable (LMA) vs una posición de memoria que referencia a código o dato que debe ser redirigido (VMA.

Como ejemplo tenemos un código que se ubicará y correrá en la RAM, este código junto con las variables inicializadas deberá copiarse en el momento de arranque desde la ROM a la RAM, obviamente, cualquier referencia a un símbolo (variable o salto) deberá realizarse referenciado a la copia de la RAM, en este caso el linker enlazara este codigo y variables con LMA en la ROM y con VMA en la versión RAM.

Existen dos maneras de indicarle al Linker como y donde cargar las diferentes partes de un programa.

Usando el segundo método construiremos un archivo de configuración denominado Linker Script.

La función Linker Script será informar al Linker en que lugar de memoria se guardarán cada bloque de mi programa, la ausencia de un archivo de este tipo, hace que el Linker adopte posiciones estándar para ubicar cada bloque lo que no siempre es correcto.

Linker Script

En este pequeño ejemplo, vemos en la primera linea un comentario del mismo formato que C, luego tenemos un comando SECTIONS seguido de una lista de secciones de salida (output sections) encerradas entre llaves, este comando indica al linker como construir el archivo destino, la primera linea dentro de SECTIONS, indica el valor inicial del contador, el contador es una variables especial denominada con un punto '.', de esta forma podemos calcular un valor de memoria utilizándola o simplemente como en este caso asignarle un valor, el cual, en este caso es la posición de memoria donde comienza la RAM de nuestro LPC2114 ubicando en ese lugar nuestro programa. Este contador es incrementado cada vez que genero una salida al archivo destino, las lineas subsiguientes, indican que secciones son incluidas en el archivo de salida y en que lugar se ubicarán, estas 3 ultimas lineas entonces indicarán por ejemplo tomar todas las secciones denominadas .text y agruparlas en una sección denominada .text en el archivo de salida, tomar todas las secciones .data y agruparlas en una .data en el archivo de salida y hacer lo mismo con .bss,

Comando Entry

ENTRY(comienzo)

Este comando no debe necesariamente estar dentro de SECCIONS, el echo de poner este comando ahí dentro, es si queremos que el valor de comienzo sea calculado a partir de algún valor intermedio del contador por ejemplo

ENTRY( . + 0x100)

Asignaciones, Expresiones y Funciones

La asignación de símbolos es de la forma nombre_simbolo = valor; ( el punto y coma al final es obligatorio).

Todos los símbolos que se definen en el linker script son globales, y pueden ser referenciados desde nuestro programa lo que resulta muy útil como veremos a continuación.

Ejemplo

Disponemos de linker script con la siguiente SECTIONS

SECTIONS
{
          . = 0x40000000;
          .text : { *(.text) }
          .data : { *(.data) }
          __bss__start = . ;
          .bss : { *(.bss) }
          __bss__end = . ;
}

Luego cuando escribimos nuestro código, si pretendemos inicializar con cero toda la sección de .bbs

     .global __bss__start
     .global __bss__end

     @ seccion de borrado de .bss
     ldr r1, pbss_start  
     ldr r2, pbss_end
     ldr r3, #0
clrbss:
     cmp r1,r2
     strne r3,[r1],#+4
     bne clrbss

     @ otro código de inicialización
     @ .....
     @ Saltar al programa en C
     bl main
     @ bucle infinito si el programa principal retorna
fin: b fin
pbss_start:
     .word __bss__start
pbss_endptr:
     .word __bss__end

Esto permite, que independientemente del tamaño de nuestra .bss, estarán siempre bssstart y bssend actualizadas por el linker script.

Además de la asignación, se puede realizar una serie de operaciones aritméticas con una construcción similar al C

Asignación

expresión1 = expresión2;

Suma

expresión1 += expresión2;

Resta

expresión1 -= expresión2;

Multiplicación

expresión1 *= expresión2;

División

expresión1 /= expresión2;

Corrimiento a la Izquierda

expresión1 <<= expresión2;

Corrimiento a la Derecha

expresión1 >>= expresión2;

AND lógico

expresión1 &= expresión2;

Or lógico

expresión1 |= expresión2;

Comando PROVIDE

SECTIONS
{
     .data :
     {
        *(.data)
        _edata = .;
        PROVIDE(edata = .);
     }
}

En este caso si en nuestro programa definimos el símbolo _edata, tendremos un error debido a símbolo duplicado, si ahora definimos un símbolo edata, nuestro programa utilizará el valor de esa definición cada vez que hagamos referencia a él y no habrá error por símbolo duplicado, en cambio si no lo definimos, entonces si utilizará el valor asignado en el linker script.

Descripción de la Sección de Salida ( Output Section)

Sección de Entrada (Input Section)

Si bien esta es el ejemplo mas común, hay ocasiones en que necesitamos por ejemplo la salida de uno de los archivos en una sección diferente al resto Por ejemplo, si queremos asegurarnos de que el STARTUP esté al comienzo como lo requiere nuestro procesador y sabiendo que el STARTUP se encuentra en boot.o, construiremos el linker script de la siguiente mantera

SECTIONS
{
     .init : { boot.o (.text) }
     .text : { *(EXCLUDE_FILE (*boot.o) .text) }
     .data : { *(.data) }
     .bss : { *(.bss) }
}

donde *(EXCLUDE_FILE (*boot.o)) lista todos los archivos de entrada menos boot.o

Denominación de Regiones de Memoria (Named Memory Regions)

Podemos evitar estos problemas de desborde que son invisibles al linker pero que pueden producir fenómenos muy difícil de detectar, mediante el comando MEMORY.

ejemplo

 MEMORY
{
     sram : org = 0x 0x40000000, len = 0x00020000
} 

En este ejemplo asignamos con el nombre sram a un bloque de memoria o región de rango 0x40000000 a 0x4001FFFF, el cual corresponde a los 16Kb de RAM de un LPC2114, si ahora incluimos a SECTIONS dentro de nuestro linker script, nos queda

SECTIONS
{
     .text : { *.(text) } >sram
     .data : { *.(data) } >sram
     .bss : { *.(bss) } >sram
} 

Como vemos, al definir regiones, usamos entonces "region" dentro de cada sección de salida

forma generalizada

 MEMORY
{
     name attributes : ORIGIN = origin, LENGTH = len 
     .... 
     [ otras regiones definidas por el usuario ]
     ....
} 

A

Sección Asignable

I

Sección Inicializada

L

Sección Cargable (sinónimo de I)

R

Sección de solo lectura

W

Sección de escritura Lectura

X

Sección ejecutable

!

Operador de inversión ( !R significa de no solo lectura)

Ejemplo

A continuación se detalla el linker script del microcontrolador LPC2114

/* lpc2114_flash.ld
 *
 * Linker script for Philips LPC2114 ARM microcontroller 
 * applications that execute from Flash.
 */
/* The LPC2114 has 128kB of Flash, and 16kB SRAM */
MEMORY
{
    flash (rx) : org = 0x00000000, len = 0x00020000
    sram  (rw) : org = 0x40000000, len = 0x00004000
}

SECTIONS
{
    /* ------------------------------------------------------------
     * .text section (executable code)
     * ------------------------------------------------------------
     */
    .text :
    {
        *start.o (.text)
        *(.text)
        *(.glue_7t) *(.glue_7)
    } > flash
    . = ALIGN(4);

    /* ------------------------------------------------------------
     * .rodata section (read-only (const) initialized variables)
     * ------------------------------------------------------------
     */
    .rodata :
    {
        *(.rodata)
    } > flash
    . = ALIGN(4);

    /* End-of-text symbols */
    _etext = . ;
    PROVIDE (etext = .);

    /* ------------------------------------------------------------
     * .data section (read/write initialized variables)
     * ------------------------------------------------------------
     *
     * The values of the initialized variables are stored
     * in Flash, and the startup code copies them to SRAM.
     *
     * The variables are stored in Flash starting at _etext,
     * and are copied to SRAM address ''data to ''edata.
     */
     .data : AT (_etext)
     {
         _data = . ;
         *(.data)
        _edata = . ;
        PROVIDE (edata = .);
     } > sram
     . = ALIGN(4);

    /* ------------------------------------------------------------
     * .bss section (uninitialized variables)
     * ------------------------------------------------------------
     *
     * These symbols define the range of addresses in SRAM that
     * need to be zeroed.
     */
    .bss :
    {
       _bss = . ;
       *(.bss)
       *(COMMON)
      _ebss = . ;
    } > sram
    . = ALIGN(4);
    _end = .;
   PROVIDE (end = .);

    /* Stabs debugging sections.  */
    .stab     1. : { *(.stab) }
    .stabstr     1. : { *(.stabstr) }
    .stab.excl      1. : { *(.stab.excl) }
    .stab.exclstr  0 : { *(.stab.exclstr) }
    .stab.index     1. : { *(.stab.index) }
    .stab.indexstr 0 : { *(.stab.indexstr) }
    .comment     1. : { *(.comment) }
    /* DWARF debug sections.
       Symbols in the DWARF debugging sections are relative to the beginning
       of the section so we begin them at 0.  */
    /* DWARF 1 */
    .debug     1. : { *(.debug) }
    .line      1. : { *(.line) }
    /* GNU DWARF 1 extensions */
    .debug_srcinfo  0 : { *(.debug_srcinfo) }
    .debug_sfnames  0 : { *(.debug_sfnames) }
    /* DWARF 1.1 and DWARF 2 */
    .debug_aranges  0 : { *(.debug_aranges) }
    .debug_pubnames 0 : { *(.debug_pubnames) }
    /* DWARF 2 */
    .debug_info      1. : { *(.debug_info .gnu.linkonce.wi.*) }
    .debug_abbrev    1. : { *(.debug_abbrev) }
    .debug_line      1. : { *(.debug_line) }
    .debug_frame     1. : { *(.debug_frame) }
    .debug_str    1. : { *(.debug_str) }
    .debug_loc    1. : { *(.debug_loc) }
    .debug_macinfo  0 : { *(.debug_macinfo) }
    /* SGI/MIPS DWARF 2 extensions */
    .debug_weaknames 0 : { *(.debug_weaknames) }
    .debug_funcnames 0 : { *(.debug_funcnames) }
    .debug_typenames 0 : { *(.debug_typenames) }
    .debug_varnames  0 : { *(.debug_varnames) }
}