Linker Script - Secciones de memoria en un script de enlazado (linker script)

馃敡 Linker Script: Nomenclatura y C贸mo Crear Uno

馃П Nomenclatura B谩sica

ENTRY(_start)             /* Punto de entrada */
MEMORY
{
  FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 256K
  RAM (rwx)  : ORIGIN = 0x20000000, LENGTH = 64K
}

SECTIONS
{
  .text : {
    *(.text)           /* C贸digo */
    *(.text*)          /* M谩s c贸digo */
    *(.rodata)         /* Datos de solo lectura */
  } > FLASH

  .data : {
    *(.data)           /* Variables inicializadas */
  } > RAM AT > FLASH

  .bss : {
    *(.bss)            /* Variables no inicializadas */
    *(COMMON)
  } > RAM
}

馃З Paso a Paso para Crear un Linker Script

  1. Define el punto de entrada:
    ENTRY(_start)
  2. Declara la memoria disponible:
    MEMORY
    {
      FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 256K
      RAM (rwx)  : ORIGIN = 0x20000000, LENGTH = 64K
    }
  3. Organiza las secciones:
    • .text → C贸digo.
    • .data → Variables con valor inicial.
    • .bss → Variables sin valor inicial.
    • .stack, .heap, .isr_vector → Opcionales.
  4. Indica en qu茅 memoria va cada secci贸n:
    .text > FLASH
    .data > RAM AT > FLASH
  5. Incluye secciones adicionales si es necesario: interrupt vector table, stack, heap, etc.
  6. Guarda como archivo .ld, por ejemplo: stm32f103.ld
  7. Enlaza usando ld o un sistema de compilaci贸n como Makefile o CMake.

¿Est谩s trabajando con alg煤n microcontrolador espec铆fico o sistema operativo?

 

 

Uso adecuado de las secciones de memoria en un script de enlazado

El fragmento de c贸digo que proporcionaste es un ejemplo de c贸mo se definen las secciones de memoria en un script de enlazado (linker script). Un script de enlazado le dice al enlazador c贸mo organizar las diferentes partes de tu programa (c贸digo, datos, etc.) en la memoria. 

text       (rx)    : ORIGIN = 0x7C00, LENGTH = 64K
rodata     (r)     : ORIGIN = 0x9000, LENGTH = 32K  /* 32K Mueve rodata fuera de data */
data       (rw)    : ORIGIN = 0xA000, LENGTH = 64K
bss        (rw)    : ORIGIN = 0xB000, LENGTH = 2M    /* Incrementado de 32K a 2MB */
eh_frame   (r)     : ORIGIN = 0xFBA0, LENGTH = 32K  /* 16K Asignaci贸n independiente */
got_plt    (rw)    : ORIGIN = 0xFF00, LENGTH = 32K  /* 16K Ubicaci贸n separada */

  • text (rx):

    • text: Nombre de la secci贸n. Esta secci贸n contiene el c贸digo ejecutable de tu programa (instrucciones de la CPU).

    • (rx): Atributos de la secci贸n:

      • r (read): La secci贸n se puede leer.

      • x (execute): La secci贸n se puede ejecutar.

    • ORIGIN = 0x7C00: Direcci贸n de memoria donde se cargar谩 esta secci贸n. En este caso, 0x7C00. Esta es una direcci贸n muy com煤n para el c贸digo del bootloader.

    • LENGTH = 64K: Tama帽o m谩ximo de la secci贸n, en este caso, 64 kilobytes.

    Uso adecuado: La secci贸n text debe contener solo c贸digo ejecutable. Es crucial que esta secci贸n tenga los atributos rx (lectura y ejecuci贸n) y que su direcci贸n de origen sea una direcci贸n v谩lida donde el c贸digo puede ser cargado y ejecutado por la CPU.

  • rodata (r):

    • rodata: Nombre de la secci贸n. Esta secci贸n contiene datos de solo lectura, como cadenas de texto constantes y otros datos que no deber铆an modificarse durante la ejecuci贸n del programa.

    • (r): Atributo de la secci贸n:

      • r (read): La secci贸n solo se puede leer.

    • ORIGIN = 0x9000: Direcci贸n de memoria donde se cargar谩 esta secci贸n.

    • LENGTH = 32K: Tama帽o m谩ximo de la secci贸n.

    Uso adecuado: Coloca las constantes y los datos que no cambian en rodata. Esto ayuda al compilador y al sistema operativo a proteger estos datos de modificaciones accidentales, mejorando la estabilidad del programa.

  • data (rw):

    • data: Nombre de la secci贸n. Esta secci贸n contiene datos inicializados que pueden ser modificados por el programa (variables globales y est谩ticas con valores iniciales).

    • (rw): Atributos de la secci贸n:

      • r (read): La secci贸n se puede leer.

      • w (write): La secci贸n se puede escribir.

    • ORIGIN = 0xA000: Direcci贸n de memoria.

    • LENGTH = 64K: Tama帽o m谩ximo.

    Uso adecuado: Utiliza la secci贸n data para las variables que tu programa necesita leer y modificar.

  • bss (rw):

    • bss: Nombre de la secci贸n. Esta secci贸n contiene variables globales y est谩ticas que no est谩n inicializadas expl铆citamente. El sistema operativo inicializa estas variables a cero antes de que el programa comience a ejecutarse.

    • (rw): Atributos de la secci贸n:

      • r (read): La secci贸n se puede leer.

      • w (write): La secci贸n se puede escribir.

    • ORIGIN = 0xB000: Direcci贸n de memoria.

    • LENGTH = 2M: Tama帽o m谩ximo.

    Uso adecuado: Define las variables no inicializadas en la secci贸n bss. Es importante reservar suficiente espacio para estas variables, ya que si se desborda esta secci贸n, puede corromperse la memoria del programa. En este ejemplo, se ha incrementado el tama帽o de bss de 32KB a 2MB, lo que proporciona mucho m谩s espacio para las variables no inicializadas.

  • eh_frame (r):

    • eh_frame: Nombre de la secci贸n. Esta secci贸n contiene informaci贸n para el manejo de excepciones, como el rastreo de la pila.

    • (r): Atributo de la secci贸n:

      • r (read): La secci贸n se puede leer.

    • ORIGIN = 0xFBA0: Direcci贸n de memoria.

    • LENGTH = 32K: Tama帽o m谩ximo.

    Uso adecuado: La secci贸n eh_frame es importante para la correcta gesti贸n de excepciones en C++ y otros lenguajes que las utilizan. Col贸cala en una regi贸n de memoria que no se superponga con otras secciones y que sea accesible en tiempo de ejecuci贸n.

  • got_plt (rw):

    • got_plt: Nombre de la secci贸n. Esta secci贸n se utiliza para la vinculaci贸n din谩mica de funciones de bibliotecas compartidas.

    • (rw): Atributos de la secci贸n:

      • r (read): La secci贸n se puede leer.

      • w (write): La secci贸n se puede escribir.

    • ORIGIN = 0xFF00: Direcci贸n de memoria.

    • LENGTH = 32K: Tama帽o m谩ximo.

    Uso adecuado: La secci贸n got_plt es esencial para los programas que utilizan bibliotecas din谩micas. Debe colocarse en una ubicaci贸n que permita tanto la lectura como la escritura por parte del enlazador din谩mico y el programa en tiempo de ejecuci贸n.

Consideraciones importantes

  • Superposici贸n de memoria: Es crucial que las secciones no se solapen en la memoria. El enlazador utiliza las direcciones de origen y las longitudes para asignar espacio a cada secci贸n. Si hay superposiciones, el programa puede fallar o comportarse de manera impredecible.
  • Direcciones de memoria v谩lidas: Las direcciones de origen deben ser direcciones de memoria v谩lidas para la plataforma en la que se est谩 ejecutando el programa. Por ejemplo, en un sistema embebido, ciertas 谩reas de memoria pueden estar reservadas para hardware o firmware.
  • Tama帽os de secci贸n adecuados: Los tama帽os de las secciones deben ser lo suficientemente grandes como para contener todos los datos que se van a colocar en ellas. Si una secci贸n es demasiado peque帽a, el enlazador generar谩 un error o los datos se escribir谩n fuera de los l铆mites de la secci贸n, lo que puede provocar corrupci贸n de la memoria.
  • Atributos de secci贸n correctos: Los atributos de cada secci贸n deben configurarse correctamente. Por ejemplo, intentar ejecutar c贸digo desde una secci贸n marcada como solo de lectura (r) provocar谩 un error.
  • Script de enlazado: Estas definiciones de secci贸n suelen estar contenidas en un archivo de script de enlazado (linker script), que se utiliza como entrada para el enlazador. El script de enlazado proporciona un control detallado sobre c贸mo se organiza la memoria del programa.
 
 
 

script de Linker

Como hemos le铆do anteriormente, el c贸digo de inicio necesita saber algo de informaci贸n sobre el dise帽o de la memoria, espec铆ficamente el comienzo y el final de secciones de datos y bss, y el inicio de la inicializaci贸n datos en ROM.

Esta informaci贸n es proporcionada por el enlazador cuando combina todo archivos de objetos para producir el binario final. Pero el dise帽o de la memoria es muy dependiente de la plataforma, ya que no todos los dispositivos tienen flash y RAM en los mismos lugares.

La elegante manera elegida por las herramientas gcc y gnu para manejar esto es a trav茅s de scripts de enlace; uno o m谩s archivos de texto que describen la particularidades del dise帽o de la memoria, colocaci贸n de secciones y otros informaci贸n requerida para producir el ejecutable final.

Una revisi贸n r谩pida

El proceso de construcci贸n de la herramienta gnu es el siguiente:

proceso de construcci贸n gnu

  • Todos *.c archivos se construyen individualmente a un archivo objeto *.o. Estos archivos son una recopilaci贸n de secciones que contienen datos binarios de tipos de diferentes:
    • El c贸digo de cada archivo C genera el .textSecci贸n
    • Los datos de la lectura generan el .rodataSecci贸n
    • Los datos no cero inicializados generan el .dataSecci贸n
    • Los datos cero inicializados generan el .bssSecci贸n
    • Los datos no fijos generan el COMMONSecci贸n. C mandatos est谩ndar para que se rubrice como 0 como .bssSecci贸n. Debido a esto, algunos compiladores s贸lo generan un .bssSecci贸n unirse a ambos .bssy COMMONsecciones.
  • El enlazador se une a las secciones de objetos individuales y coloca secciones unidas en direcciones espec铆ficas dependiendo del dise帽o del script del enlace.
  • Si no proporcionamos un script de enlazador, la cadena de herramientas proporciona uno por defecto. Normalmente, este script no es adecuado para usar en dispositivos met谩licos desnudos.

c c贸digo gen

El script de enlace consiste en comandos para describir la colocaci贸n de la secciones diferentes en el 谩rea de la memoria.

script de enlazador b谩sico

En el siguiente c贸digo, podemos ver un script de enlazador funcional completo compatible con la startup expuesta en las 煤ltimas secciones:

/* Start of section description layout */
SECTIONS
{
  . = 0x08000000; /* The dot is current address counter. */

  /* If you do not specify any, start at 0 */
  .text : {
    /* KEEP keyword is used to  ensure that symbols are not discarded if the 
       linker is instructed to remove unused symbols. */
    KEEP(*(.vector_core*)) /* Place .vector_core at start of ROM */
    *(.text)   /* Place all symbols in ".text" section */
    *(.text.*)  /* Place all symbols that start with section ".text.<any>" */
  }

  /* In many platforms, the linker script puts the .rodata in .text if
     the corresponding section does not exist */
  .rodata : {
    . = ALIGN(4); /* Ensure word alignment of sections */
    *(.rodata)
    *(.rodata*)
    . = ALIGN(4); /* Ensure word alignment of sections */
  }

  .init_array : {
    . = ALIGN(4); /* Ensure word alignment of sections */
    __init_array_start = .;
    /* SORT function sorts the symbols listed in alphabetical order.
       This is required to meet with the initialization order.
       All symbols are in the form of .init_array.0000_nameofsymbol
       when 0000 is replaced by a priority number that can be specified
       at compile time. */
    KEEP(*(SORT(.init_array.*)))
    KEEP(*(.init_array))
    . = ALIGN(4); /* Ensure word alignment of sections */
    __init_array_end = .;
  }

  _etext = .; /* You can define symbols with any expression using dot */

  . = 0x20000000;
  /* AT(expr) is used to specify load address */
  .data : AT(_etext) {
    _data = .;
    *(.data)
    *(.data*)
    . = ALIGN(4);
    _edata = .;
  }

  /* The function LOADADDR returns the load address of a specific section */
  _data_loadaddr = LOADADDR(.data);

  .bss : {
    . = ALIGN(4); /* Ensure word alignment of sections */
    _bss = .;
    *(.bss)
    *(.bss*)
    *(COMMON)
    . = ALIGN(4);
    _ebss = .;
  }

  /* You use prefixes like K to multiply by 1024 or M to multiply for 1048576 */
  _stack = 0x20000000 + 20K; /* Put the stack at end of ram (20K in this example) */
}

La 煤nica magia no visible en este gui贸n es la AT(_etext)la expresi贸n. Esto instruye al enlazador a poner la secci贸n en una direcci贸n de carga espec铆fica, pero para el este mapa a la direcci贸n actual de puntos. Esto est谩 dise帽ado espec铆ficamente para colocar los datos inicializados en ROM y copiar estos datos a la RAM en el inicio.

Este script de enlace supone que la memoria rom comienza en 0x08000000 y el ariete la direcci贸n comienza en 0x20000000 y finaliza 8K m谩s tarde (el caso m谩s com煤n es el azulgrana procesador stm32f103cb)

Mejorando los scripts con az煤car sintaxis

Podemos mejorar el script a帽adiendo m谩s constantes amigables con el usuario:

ROM_BASE = 0x08000000;
RAM_BASE = 0x20000000;
RAM_SIZE = 20K;

/* Start of section description layout */
SECTIONS
{
  . = ROM_BASE;
  
  ...

  . = RAM_BASE;
  /* AT(expr) is used to specify load address */
  .data : AT(_etext) {
    _data = .;

  ...

  _stack = RAM_BASE + RAM_SIZE;
}

O mover esto a otro archivo dependiente de plataforma:

INCLUDE(memory.ld)

/* Start of section description layout */
SECTIONS
{
  . = ROM_BASE;
  ...
/* Platform dependent memory.ld */
ROM_BASE = 0x08000000;
RAM_BASE = 0x20000000;
RAM_SIZE = 20K;

Pero la manera realmente bonita de poner secciones diferentes en 谩reas de memoria es a trav茅s de la MEMORYcomando. Usted puede reemplazar el contenido de memory.ldcon:

MEMORY {
  rom rx : ORIGIN 0x08000000, LENGTH = 64K
  ram rwx : ORIGIN 0x20000000, LENGTH = 20K
}

Ahora el script del enlazador necesita algunos cambios para usar las regiones de memoria adecuado:

INCLUDE memory.ld

SECTIONS
{
  .text : {
    KEEP(*(.vector_core*))
    *(.text)
    *(.text.*)
  } > rom /* fill rom memory area with .text section */

  .rodata : {
    . = ALIGN(4);
    *(.rodata)
    *(.rodata*)
    . = ALIGN(4);
  } > rom /* the second >rom appends the current block to previous block */

  .init_array : {
    . = ALIGN(4);
    __init_array_start = .;
    KEEP(*(SORT(.init_array.*)))
    KEEP(*(.init_array))
    . = ALIGN(4);
    __init_array_end = .;
  } > rom

  .data : {
    _data = .;
    *(.data)
    *(.data*)
    . = ALIGN(4);
    _edata = .;
  } > ram AT>rom /* AT>section is to append in load memory address mode */

  _data_loadaddr = LOADADDR(.data);

  .bss : {
    . = ALIGN(4);
    _bss = .;
    *(.bss)
    *(.bss*)
    *(COMMON)
    . = ALIGN(4);
    _ebss = .;
  } > ram

  /* ORIGIN(region) returns the address of memory region, LENGTH returns the length */
  _stack = ORIGIN(ram) + LENGTH(ram);
}

Este truco tiene algunas ventajas:

  • El enlazador puede comprobarlo autom谩ticamente cuando una secci贸n de memoria se agota y termina con un error
  • No necesitamos mantener el puntero de punto en el valor correcto
  • El script del enlace se explica autom谩ticamente d贸nde est谩 su c贸digo
  • Nuevas versiones de ld pueden mostrar el consumo de memoria para todas las 谩reas de memoria de una manera f谩cil de usar

El 煤ltimo punto se puede ilustrar con el volcado del comando de la continuaci贸n:

Memory region         Used Size  Region Size  %age Used
             rom:         220 B        64 KB      0.34%
             ram:          12 B        20 KB      0.06%

El siguiente paso es construir todo con un gui贸n Makefile y producir la final ejecutable, adecuado para ser enviado a la memoria ROM...

Leer m谩s: 

    Destacado

    Bootloader Avanzado en Ensamblador

    Bootloader Avanzado en Ensamblador Caracter铆sticas del Bootloader Se carga en la direcci贸n 0x7C00 (BIOS). ...