GNU链接器(LD):链接脚本分析——以stm32MP135 SoC链接脚本为例

0 参考资料

GNU-LD-v2.30-中文手册.pdf
GNU linker.pdf

1 前言

一个完整的编译工具链应该包含以下4个部分:
(1)编译器
(2)汇编器
(3)链接器
(4)lib库
在GNU工具链中,对应的是:
(1)编译器:GCC(GNU Compiler Collection,GNU编译器套件)
(2)汇编器:GAS(GNU Assembler,GNU汇编器)
(3)链接器:LD(GNU Linker,GNU链接器)
(4)lib库:glibc(GNU C Library,GNU C 库)
本文介绍GNU链接器(LD):链接脚本——以stm32MP135 SoC链接脚本为例。

2 GCC链接脚本解析:以stm32MP135 SoC链接脚本为例

2.1 链接脚本源码

/* Entry Point  设置入口点 */
ENTRY(Reset_Handler)
/* 定义堆栈和页表大小 */
_Min_Heap_Size = 0x0000;     /* required amount of heap  */
_Min_Stack_Size = 0x1000;    /* required amount of stack */

TTB_L1_SIZE = 0;        /* MMU Level 1 Translation table size */
TTB_L2_SIZE = 0;         /* MMU Level 2 Translation table size */

/* 定义存储区域 */
MEMORY {
      SYSRAM_BASE (rwx)   : ORIGIN = 0x2FFE0000, LENGTH = 128K /* 存储区域名:SYSRAM_BASE,可读写可执行 起始地址:0x2FFE0000 大小:128KB */
      SRAM1_BASE (rwx)    : ORIGIN = 0x30000000, LENGTH = 16K /* 存储区域名:SRAM1_BASE,可读写可执行 起始地址:0x30000000 大小:16KB */
      SRAM2_BASE (rwx)    : ORIGIN = 0x30004000, LENGTH = 8K /* 存储区域名:SRAM2_BASE,可读写可执行 起始地址:0x30004000 大小:8KB */
      SRAM3_BASE (rwx)    : ORIGIN = 0x30006000, LENGTH = 8K /* 存储区域名:SRAM3_BASE,可读写可执行 起始地址:0x30006000 大小:8KB */
      DDR_BASE (rwx)      : ORIGIN = 0xC0000000, LENGTH = 512M /* 存储区域名:DDR_BASE,可读写可执行 起始地址:0xC0000000 大小:512MB */ 
}

/* 为存储区域取别名 */
/* Select Memory in which code has to be placed and offset of code from start of this memory */
REGION_ALIAS("RAM", SYSRAM_BASE); /* 给SYSRAM_BASE存储区域取别名为RAM */
REGION_ALIAS("SRAM1", SRAM1_BASE); /* 给SRAM1_BASE存储区域取别名为SRAM1 */
REGION_ALIAS("SRAM2", SRAM2_BASE); /* 给SRAM2_BASE存储区域取别名为SRAM2 */
REGION_ALIAS("SRAM3", SRAM3_BASE); /* 给SRAM3_BASE存储区域取别名为SRAM3 */
REGION_ALIAS("DDR", DDR_BASE); /* 给DDR_BASE存储区域取别名为DDR */


/* 为CPU工作各种模式分配栈起始地址 */
/* Code Memory */
START_OFFSET = 0x00;
__MEM_START__ = ORIGIN(RAM) + START_OFFSET;
__MEM_SIZE__  = LENGTH(RAM) - START_OFFSET;
__FIQ_STACK_SIZE = 0x400;
__IRQ_STACK_SIZE = 0x400;
__ABT_STACK_SIZE = 0x400;
__SVC_STACK_SIZE = 0x400;
__UND_STACK_SIZE = 0x400;

__MEM_START2__ = ORIGIN(SRAM1);
__MEM_SIZE2__ = LENGTH(SRAM1);

/* put the stacks at the end of the memory */
FIQ_STACK = __MEM_START2__ + __MEM_SIZE2__;
IRQ_STACK = FIQ_STACK - __FIQ_STACK_SIZE;
ABT_STACK = IRQ_STACK - __IRQ_STACK_SIZE;
SVC_STACK = ABT_STACK - __ABT_STACK_SIZE;
UND_STACK = SVC_STACK - __SVC_STACK_SIZE;
SYS_STACK = UND_STACK - __UND_STACK_SIZE;
_estack = SYS_STACK;

/* 
  告知链接器应该如何加载.text、.data、.bss段
  text、data、bss的PT_LOAD属性告诉链接器这些段(.text段、.data段、.bss段)全都需要加载到存储器中。
  FLAGS(5)表示属性为可读可执行,FLAGS(6)表示属性为可读可写。
 */
PHDRS
{
	text	PT_LOAD FLAGS(5);	/* READ and EXECUTE */
	data 	PT_LOAD FLAGS(6);	/* READ and WRITE */
	bss 	PT_LOAD FLAGS(6);	/* READ and WRITE */
}

SECTIONS
{
    . = __MEM_START__;  /* 将位置计数器值设置为__MEM_START__,也就是0x2FFE0000 */
    .RESET . : {        /* 定义输出分区名为.RESET,且将VMA(虚拟内存地址)设置为.也就是0x2FFE0000 */
        __TEXT_START__ = .; /* 将__TEXT_START__符号的值设置为.,也就是0x2FFE0000 */
        *(.text.Reset_Handler) /* 定义输入分区为任意输入文件中的Reset_Handler段,也就是复位中断服务函数 */
        * (RESET)  /* 定义输入分区为任意输入文件中的RESET段 */
        *(.text*) /* 定义输入分区为任意输入文件中的.text段 */

        /* .init is used by libc_nano */  
        /* 保护.init段,.init段会被libc_nano(精简版C库)使用到,避免被优化掉 */
        /* 保护??fini段是?ELF文件格式中的一个组成部分,用于在程序执行完毕后执行清理操作 */
        KEEP (*(.init))
        KEEP (*(.fini))

        __TEXT_END_UNALIGNED__ = .; /* 将__TEXT_END_UNALIGNED__符号的值设置为. */

        /*
         * Memory page(s) mapped to this section will be marked as
         * read-only, executable.  No non-executable data from the next section must
         * creep in.  Ensure the rest of the current memory page is unused.
         */
        __TEXT_END__ = .; /* 将__TEXT_END__符号的值设置为. */

        . = NEXT(4096); /* 将.的值修改为下一个是4096字节倍数的地址 */

        __RO_START__ = .; /* 将__RO_START__的值设置为. */
        *(.rodata*)  /* 定义输入分区为任意输入文件的rodata段 */

        __RO_END_UNALIGNED__ = .; /* 将__RO_END_UNALIGNED__的值设置为. */
        /*
         * Memory page(s) mapped to this section will be marked as
         * read-only, non-executable.  No RW data from the next section must
         * creep in.  Ensure the rest of the current memory page is unused.
         */
        . = NEXT(4096); /* 将.的值修改为下一个是4096字节倍数的地址 */
        __RO_END__ = .; /* 将__RO_END__的值设置为. */
    } >RAM :text /* 将输出分区的LMA(加载内存地址)分配到RAM中,按照PHDRS中指定的text加载方式 */


    .ARM.extab :   /* 定义输出分区名为.ARM.extab */
    {
    . = ALIGN(4); /* 位置计数器(.)设置为空闲存储区域首个4字节对齐地址 */
    *(.ARM.extab* .gnu.linkonce.armextab.*) /* 将任意输入文件的包含.ARM.extab、.gnu.linkonce.armextab.关键字的段添加到输入分区 */
    . = ALIGN(4); /* 位置计数器(.)设置为空闲存储区域首个4字节对齐地址 */
    } >RAM /* 将输出分区的LMA(加载内存地址)分配到RAM中 */
    
    .ARM :  /* 定义输出分区名为.ARM */
    {
    . = ALIGN(4); /* 位置计数器(.)设置为空闲存储区域首个4字节对齐地址 */
    __exidx_start = .; /* __exidx_start值设置为位置计数器(.)的值 */
    *(.ARM.exidx*) /* 将任意输入文件的包含.ARM.exidx关键字的段添加到输入分区 */
    __exidx_end = .; /* __exidx_end值设置为位置计数器(.)的值 */
    . = ALIGN(4); /* 位置计数器(.)设置为空闲存储区域首个4字节对齐地址 */
    } >RAM /* 将输出分区的LMA(加载内存地址)分配到RAM中 */

    /* .init_array is used by libc_nano */

    .preinit_array : /* 定义输出分区名为.preinit_array */
    {
    . = ALIGN(4); /* 位置计数器(.)设置为空闲存储区域首个4字节对齐地址 */
    PROVIDE_HIDDEN (__preinit_array_start = .);  /* 定义__preinit_array_start值为位置计数器(.)的值,仅当C中无定义时有效 */
    KEEP (*(.preinit_array*)) /* 将所有输入文件中的包含.preinit_array关键字的段保护起来,避免被当做垃圾优化掉 */
    PROVIDE_HIDDEN (__preinit_array_end = .); /* 定义__preinit_array_end值为位置计数器(.)的值,仅当C中无定义时有效 */
    . = ALIGN(4); /* 位置计数器(.)设置为空闲存储区域首个4字节对齐地址 */
    } >RAM /* 将输出分区的LMA(加载内存地址)分配到RAM中 */

    .init_array : /* 定义输出分区名为.init_array */
    {
    . = ALIGN(4); /* 位置计数器(.)设置为空闲存储区域首个4字节对齐地址 */
    PROVIDE_HIDDEN (__init_array_start = .); /* 定义__init_array_start值为位置计数器(.)的值,仅当C中无定义时有效 */
    KEEP (*(SORT(.init_array.*))) /* 将所有输入文件中的包含.init_array关键字的段按名称升序排序然后保护起来,避免被当做垃圾优化掉 */
    KEEP (*(.init_array*)) /* 将所有输入文件中的包含.init_array关键字的段保护起来,避免被当做垃圾优化掉 */
    PROVIDE_HIDDEN (__init_array_end = .); /* 定义__init_array_end值为位置计数器(.)的值,仅当C中无定义时有效 */
    . = ALIGN(4); /* 位置计数器(.)设置为空闲存储区域首个4字节对齐地址 */
    } >RAM /* 将输出分区的LMA(加载内存地址)分配到RAM中 */

    .fini_array : /* 定义输出分区名为.fini_array */
    {
    . = ALIGN(4); /* 位置计数器(.)设置为空闲存储区域首个4字节对齐地址 */
    PROVIDE_HIDDEN (__fini_array_start = .); /* 定义__fini_array_start值为位置计数器(.)的值,仅当C中无定义时有效 */
    KEEP (*(SORT(.fini_array.*))) /* 将所有输入文件中的包含.fini_array.关键字的段按名称升序排序然后保护起来,避免被当做垃圾优化掉 */
    KEEP (*(.fini_array*)) /* 将所有输入文件中的包含.fini_array关键字的段保护起来,避免被当做垃圾优化掉 */
    PROVIDE_HIDDEN (__fini_array_end = .); /* 定义__fini_array_end值为位置计数器(.)的值,仅当C中无定义时有效 */
    . = ALIGN(4); /* 位置计数器(.)设置为空闲存储区域首个4字节对齐地址 */
    } >RAM /* 将输出分区的LMA(加载内存地址)分配到RAM中 */

   /* Used by the startup to initialize data */
    __DATA_ROM = .; /* 定义__DATA_ROM值为位置计数器(.)的值 */
    _sidata = LOADADDR(.data); /* 定义_sidata的值设为.data输出分区的LMA(加载内存地址)[绝对地址] */

    .data . : { /* 定义输出分区名为.data,且将VMA(虚拟内存地址)设置为位置计数器(.)的值 */
        RW_DATA = .; /* 定义RW_DATA值为位置计数器(.)的值 */
        _sdata = .; /* 定义_sdata值为位置计数器(.)的值 */
        *(.data*) /* 将任意输入文件的包含.data关键字的段添加到输入分区 */
        _edata = .; /* 定义_edata值为位置计数器(.)的值 */
        __DATA_END__ = .; /* 定义__DATA_END__值为位置计数器(.)的值 */
    } >RAM /* 将输出分区的LMA(加载内存地址)分配到RAM中 */

    /*
     * The .bss section gets initialised to 0 at runtime.
     * Its base address must be 16-byte aligned.
     */
    .bss : ALIGN(32) { /* 定义输出分区名为.bss,且VMA(虚拟内存地址)首地址32字节对齐 */
    	_sbss = .;         /* 定义RW_DATA值为位置计数器(.)的值 */
        ZI_DATA = .; /* 定义ZI_DATA值为位置计数器(.)的值 */
        *(SORT_BY_ALIGNMENT(.bss*)) /* 将所有输入文件中的包含.bss关键字的段按名称升序排序添加到输入分区 */
        *(COMMON) /* 将所有输入文件中的公共符号放到输入分区 */
        . = ALIGN(32); /* 位置计数器(.)设置为空闲存储区域首个32字节对齐地址 */
        _ebss = .;         /* 定义_ebss值为位置计数器(.)的值 */
        __BSS_END__ = .; /* 定义__BSS_END__值为位置计数器(.)的值 */
    } >RAM /* 将输出分区的LMA(加载内存地址)分配到RAM中 */

    __BSS_SIZE__ = SIZEOF(.bss); /* 将__BSS_SIZE__的值设置为.bss输出分区大小(单位:字节) */

/* 1 TTB Level 1 table */
/* 4 TTB Level 2 tables */
    ._TTB : ALIGN(16384) { /* 定义输出分区名为._TTB,且VMA(虚拟内存地址)首地址16384字节对齐 */
       TTB = .; /* 定义TTB值为位置计数器(.)的值 */
       . = . + TTB_L1_SIZE; /* 位置计数器(.)值+=TTB_L1_SIZE */
       . = . + TTB_L2_SIZE * 4; /* 位置计数器(.)值+=TTB_L2_SIZE * 4 */
    } > RAM /* 将输出分区的LMA(加载内存地址)分配到RAM中 */


    /* User_heap_stack section, used to check that there is enough RAM left */
    ._user_heap_stack : /* 定义输出分区名为._user_heap_stack */
    {
        . = ALIGN(8); /* 位置计数器(.)设置为空闲存储区域首个8字节对齐地址 */
        PROVIDE ( end = . ); /* 定义end值为位置计数器(.)的值,仅当C中无定义时有效 */
        PROVIDE ( _end = . ); /* 定义_end值为位置计数器(.)的值,仅当C中无定义时有效 */
        . = . + _Min_Heap_Size; /* 位置计数器(.)的值+=_Min_Heap_Size */
        . = . + _Min_Stack_Size; /* 位置计数器(.)的值+=_Min_Stack_Size */
        . = ALIGN(8); /* 位置计数器(.)设置为空闲存储区域首个8字节对齐地址 */
    } >SRAM1 /* 将输出分区的LMA(加载内存地址)分配到SRAM中 */

  .SRAM1 (NOLOAD): /* 定义输出分区名为.SRAM1,不加载到存储空间 */
  {
    . = ALIGN(4); /* 位置计数器(.)设置为空闲存储区域首个4字节对齐地址 */
    *(.SRAM1); /* 将所有输入文件的包含.SRAM1关键字的段添加到输入分区 */
    . = ALIGN(4); /* 位置计数器(.)设置为空闲存储区域首个4字节对齐地址 */
  } >SRAM1 /* 将输出分区的LMA(加载内存地址)分配到SRAM1中 */

  .SRAM2 (NOLOAD): /* 定义输出分区名为.SRAM2,不加载到存储空间 */
  {
    . = ALIGN(4); /* 位置计数器(.)设置为空闲存储区域首个4字节对齐地址 */
    *(.SRAM2);  /* 将所有输入文件的包含.SRAM2关键字的段添加到输入分区 */
    . = ALIGN(4); /* 位置计数器(.)设置为空闲存储区域首个4字节对齐地址 */
  } >SRAM2 /* 将输出分区的LMA(加载内存地址)分配到SRAM2中 */

  .SRAM3 (NOLOAD): /* 定义输出分区名为.SRAM3,不加载到存储空间 */
  {
    . = ALIGN(4); /* 位置计数器(.)设置为空闲存储区域首个4字节对齐地址 */
    *(.SRAM3); /* 将所有输入文件的包含.SRAM3关键字的段添加到输入分区 */
    . = ALIGN(4); /* 位置计数器(.)设置为空闲存储区域首个4字节对齐地址 */
  } >SRAM3 /* 将输出分区的LMA(加载内存地址)分配到SRAM3中 */

  /* Remove information from the compiler libraries */
  /DISCARD/ : /* 特殊输出分区:用来丢弃输入分区。丢弃的是以下库中未使用的部分 */
  {
    libc.a ( * ) /* 添加libc.a到输入分区 */
    libm.a ( * ) /* 添加libm.a到输入分区 */
    libgcc.a ( * ) /* 添加libgcc.a到输入分区 */
  }
}

2.2 设置入口点

/* Entry Point */
ENTRY(Reset_Handler)

这里以复位中断服务函数作为入口点,配合后面的输出分区将复位中断服务函数Reset_Handler定义在镜像的首地址。

2.3 定义堆栈和页表大小

_Min_Heap_Size = 0x0000;     /* required amount of heap  */
_Min_Stack_Size = 0x1000;    /* required amount of stack */

TTB_L1_SIZE = 0;        /* MMU Level 1 Translation table size */
TTB_L2_SIZE = 0;         /* MMU Level 2 Translation table size */

定义符号并赋初值,从上到下依次是堆大小、栈大小、一级页表大小、二级页表大小

2.4 定义存储区域

/* 定义存储区域 */
MEMORY {
      SYSRAM_BASE (rwx)   : ORIGIN = 0x2FFE0000, LENGTH = 128K /* 存储区域名:SYSRAM_BASE,可读写可执行 起始地址:0x2FFE0000 大小:128KB */
      SRAM1_BASE (rwx)    : ORIGIN = 0x30000000, LENGTH = 16K /* 存储区域名:SRAM1_BASE,可读写可执行 起始地址:0x30000000 大小:16KB */
      SRAM2_BASE (rwx)    : ORIGIN = 0x30004000, LENGTH = 8K /* 存储区域名:SRAM2_BASE,可读写可执行 起始地址:0x30004000 大小:8KB */
      SRAM3_BASE (rwx)    : ORIGIN = 0x30006000, LENGTH = 8K /* 存储区域名:SRAM3_BASE,可读写可执行 起始地址:0x30006000 大小:8KB */
      DDR_BASE (rwx)      : ORIGIN = 0xC0000000, LENGTH = 512M /* 存储区域名:DDR_BASE,可读写可执行 起始地址:0xC0000000 大小:512MB */ 
}

定义的存储区域名称分别为SYSRAM_BASE、SRAM1_BASE、SRAM2_BASE、SRAM3_BASE、DDR_BASE,起始地址分别为0x2FFE0000、0x30000000、0x30004000、0x30006000、0xC0000000,大小分别为128KB、16KB、8KB、8KB、512MB。

2.5 为存储区域取别名

/* 为存储区域取别名 */
/* Select Memory in which code has to be placed and offset of code from start of this memory */
REGION_ALIAS("RAM", SYSRAM_BASE); /* 给SYSRAM_BASE存储区域取别名为RAM */
REGION_ALIAS("SRAM1", SRAM1_BASE); /* 给SRAM1_BASE存储区域取别名为SRAM1 */
REGION_ALIAS("SRAM2", SRAM2_BASE); /* 给SRAM2_BASE存储区域取别名为SRAM2 */
REGION_ALIAS("SRAM3", SRAM3_BASE); /* 给SRAM3_BASE存储区域取别名为SRAM3 */
REGION_ALIAS("DDR", DDR_BASE); /* 给DDR_BASE存储区域取别名为DDR */

分别将名为SYSRAM_BASE、SRAM1_BASE、SRAM2_BASE、SRAM3_BASE、DDR_BASE的存储区域取别名为RAM、SRAM1、SRAM2、SRAM3、DDR。

2.6 为不同CPU工作模式分配栈起始(栈底)地址

/* 为CPU工作各种模式分配栈起始地址 */
/* Code Memory */
START_OFFSET = 0x00;
__MEM_START__ = ORIGIN(RAM) + START_OFFSET;
__MEM_SIZE__  = LENGTH(RAM) - START_OFFSET;
__FIQ_STACK_SIZE = 0x400;
__IRQ_STACK_SIZE = 0x400;
__ABT_STACK_SIZE = 0x400;
__SVC_STACK_SIZE = 0x400;
__UND_STACK_SIZE = 0x400;

__MEM_START2__ = ORIGIN(SRAM1);
__MEM_SIZE2__ = LENGTH(SRAM1);

/* put the stacks at the end of the memory */
FIQ_STACK = __MEM_START2__ + __MEM_SIZE2__;
IRQ_STACK = FIQ_STACK - __FIQ_STACK_SIZE;
ABT_STACK = IRQ_STACK - __IRQ_STACK_SIZE;
SVC_STACK = ABT_STACK - __ABT_STACK_SIZE;
UND_STACK = SVC_STACK - __SVC_STACK_SIZE;
SYS_STACK = UND_STACK - __UND_STACK_SIZE;
_estack = SYS_STACK;

为FIQ、IRQ、ABT、SVC、UND、SYS工作模式分配栈起始(栈底)地址。

2.7 告知链接器应该如何加载.text、.data、.bss段

/* 
  告知链接器应该如何加载.text、.data、.bss段
  text、data、bss的PT_LOAD属性告诉链接器这些段(.text段、.data段、.bss段)全都需要加载到存储器中。
  FLAGS(5)表示属性为可读可执行,FLAGS(6)表示属性为可读可写。
 */
PHDRS
{
	text	PT_LOAD FLAGS(5);	/* READ and EXECUTE */
	data 	PT_LOAD FLAGS(6);	/* READ and WRITE */
	bss 	PT_LOAD FLAGS(6);	/* READ and WRITE */
}

text、data、bss的PT_LOAD属性告诉链接器这些段(.text段、.data段、.bss段)全都需要加载到存储器中。FLAGS(5)表示属性为可读可执行,FLAGS(6)表示属性为可读可写。

2.8 分区命令,告知链接器如何把输入分区映射到输出分区

分区命令包含在SECTIONS的花括号中,如下:

SECTIONS
{
    . = __MEM_START__;  /* 将位置计数器值设置为__MEM_START__,也就是0x2FFE0000 */
    .RESET . : {        /* 定义输出分区名为.RESET,且将VMA(虚拟内存地址)设置为.也就是0x2FFE0000 */
        __TEXT_START__ = .; /* 将__TEXT_START__符号的值设置为.,也就是0x2FFE0000 */
        *(.text.Reset_Handler) /* 定义输入分区为任意输入文件中的Reset_Handler段,也就是复位中断服务函数 */
        * (RESET)  /* 定义输入分区为任意输入文件中的RESET段 */
        *(.text*) /* 定义输入分区为任意输入文件中的.text段 */

        /* .init is used by libc_nano */  
        /* 保护.init段,.init段会被libc_nano(精简版C库)使用到,避免被优化掉 */
        /* 保护??fini段是?ELF文件格式中的一个组成部分,用于在程序执行完毕后执行清理操作 */
        KEEP (*(.init))
        KEEP (*(.fini))

        __TEXT_END_UNALIGNED__ = .; /* 将__TEXT_END_UNALIGNED__符号的值设置为. */

        /*
         * Memory page(s) mapped to this section will be marked as
         * read-only, executable.  No non-executable data from the next section must
         * creep in.  Ensure the rest of the current memory page is unused.
         */
        __TEXT_END__ = .; /* 将__TEXT_END__符号的值设置为. */

        . = NEXT(4096); /* 将.的值修改为下一个是4096字节倍数的地址 */

        __RO_START__ = .; /* 将__RO_START__的值设置为. */
        *(.rodata*)  /* 定义输入分区为任意输入文件的rodata段 */

        __RO_END_UNALIGNED__ = .; /* 将__RO_END_UNALIGNED__的值设置为. */
        /*
         * Memory page(s) mapped to this section will be marked as
         * read-only, non-executable.  No RW data from the next section must
         * creep in.  Ensure the rest of the current memory page is unused.
         */
        . = NEXT(4096); /* 将.的值修改为下一个是4096字节倍数的地址 */
        __RO_END__ = .; /* 将__RO_END__的值设置为. */
    } >RAM :text /* 将输出分区的LMA(加载内存地址)分配到RAM中,按照PHDRS中指定的text加载方式 */


    .ARM.extab :   /* 定义输出分区名为.ARM.extab */
    {
    . = ALIGN(4); /* 位置计数器(.)设置为空闲存储区域首个4字节对齐地址 */
    *(.ARM.extab* .gnu.linkonce.armextab.*) /* 将任意输入文件的包含.ARM.extab、.gnu.linkonce.armextab.关键字的段添加到输入分区 */
    . = ALIGN(4); /* 位置计数器(.)设置为空闲存储区域首个4字节对齐地址 */
    } >RAM /* 将输出分区的LMA(加载内存地址)分配到RAM中 */
    
    .ARM :  /* 定义输出分区名为.ARM */
    {
    . = ALIGN(4); /* 位置计数器(.)设置为空闲存储区域首个4字节对齐地址 */
    __exidx_start = .; /* __exidx_start值设置为位置计数器(.)的值 */
    *(.ARM.exidx*) /* 将任意输入文件的包含.ARM.exidx关键字的段添加到输入分区 */
    __exidx_end = .; /* __exidx_end值设置为位置计数器(.)的值 */
    . = ALIGN(4); /* 位置计数器(.)设置为空闲存储区域首个4字节对齐地址 */
    } >RAM /* 将输出分区的LMA(加载内存地址)分配到RAM中 */

    /* .init_array is used by libc_nano */

    .preinit_array : /* 定义输出分区名为.preinit_array */
    {
    . = ALIGN(4); /* 位置计数器(.)设置为空闲存储区域首个4字节对齐地址 */
    PROVIDE_HIDDEN (__preinit_array_start = .);  /* 定义__preinit_array_start值为位置计数器(.)的值,仅当C中无定义时有效 */
    KEEP (*(.preinit_array*)) /* 将所有输入文件中的包含.preinit_array关键字的段保护起来,避免被当做垃圾优化掉 */
    PROVIDE_HIDDEN (__preinit_array_end = .); /* 定义__preinit_array_end值为位置计数器(.)的值,仅当C中无定义时有效 */
    . = ALIGN(4); /* 位置计数器(.)设置为空闲存储区域首个4字节对齐地址 */
    } >RAM /* 将输出分区的LMA(加载内存地址)分配到RAM中 */

    .init_array : /* 定义输出分区名为.init_array */
    {
    . = ALIGN(4); /* 位置计数器(.)设置为空闲存储区域首个4字节对齐地址 */
    PROVIDE_HIDDEN (__init_array_start = .); /* 定义__init_array_start值为位置计数器(.)的值,仅当C中无定义时有效 */
    KEEP (*(SORT(.init_array.*))) /* 将所有输入文件中的包含.init_array关键字的段按名称升序排序然后保护起来,避免被当做垃圾优化掉 */
    KEEP (*(.init_array*)) /* 将所有输入文件中的包含.init_array关键字的段保护起来,避免被当做垃圾优化掉 */
    PROVIDE_HIDDEN (__init_array_end = .); /* 定义__init_array_end值为位置计数器(.)的值,仅当C中无定义时有效 */
    . = ALIGN(4); /* 位置计数器(.)设置为空闲存储区域首个4字节对齐地址 */
    } >RAM /* 将输出分区的LMA(加载内存地址)分配到RAM中 */

    .fini_array : /* 定义输出分区名为.fini_array */
    {
    . = ALIGN(4); /* 位置计数器(.)设置为空闲存储区域首个4字节对齐地址 */
    PROVIDE_HIDDEN (__fini_array_start = .); /* 定义__fini_array_start值为位置计数器(.)的值,仅当C中无定义时有效 */
    KEEP (*(SORT(.fini_array.*))) /* 将所有输入文件中的包含.fini_array.关键字的段按名称升序排序然后保护起来,避免被当做垃圾优化掉 */
    KEEP (*(.fini_array*)) /* 将所有输入文件中的包含.fini_array关键字的段保护起来,避免被当做垃圾优化掉 */
    PROVIDE_HIDDEN (__fini_array_end = .); /* 定义__fini_array_end值为位置计数器(.)的值,仅当C中无定义时有效 */
    . = ALIGN(4); /* 位置计数器(.)设置为空闲存储区域首个4字节对齐地址 */
    } >RAM /* 将输出分区的LMA(加载内存地址)分配到RAM中 */

   /* Used by the startup to initialize data */
    __DATA_ROM = .; /* 定义__DATA_ROM值为位置计数器(.)的值 */
    _sidata = LOADADDR(.data); /* 定义_sidata的值设为.data输出分区的LMA(加载内存地址)[绝对地址] */

    .data . : { /* 定义输出分区名为.data,且将VMA(虚拟内存地址)设置为位置计数器(.)的值 */
        RW_DATA = .; /* 定义RW_DATA值为位置计数器(.)的值 */
        _sdata = .; /* 定义_sdata值为位置计数器(.)的值 */
        *(.data*) /* 将任意输入文件的包含.data关键字的段添加到输入分区 */
        _edata = .; /* 定义_edata值为位置计数器(.)的值 */
        __DATA_END__ = .; /* 定义__DATA_END__值为位置计数器(.)的值 */
    } >RAM /* 将输出分区的LMA(加载内存地址)分配到RAM中 */

    /*
     * The .bss section gets initialised to 0 at runtime.
     * Its base address must be 16-byte aligned.
     */
    .bss : ALIGN(32) { /* 定义输出分区名为.bss,且VMA(虚拟内存地址)首地址32字节对齐 */
    	_sbss = .;         /* 定义RW_DATA值为位置计数器(.)的值 */
        ZI_DATA = .; /* 定义ZI_DATA值为位置计数器(.)的值 */
        *(SORT_BY_ALIGNMENT(.bss*)) /* 将所有输入文件中的包含.bss关键字的段按名称升序排序添加到输入分区 */
        *(COMMON) /* 将所有输入文件中的公共符号放到输入分区 */
        . = ALIGN(32); /* 位置计数器(.)设置为空闲存储区域首个32字节对齐地址 */
        _ebss = .;         /* 定义_ebss值为位置计数器(.)的值 */
        __BSS_END__ = .; /* 定义__BSS_END__值为位置计数器(.)的值 */
    } >RAM /* 将输出分区的LMA(加载内存地址)分配到RAM中 */

    __BSS_SIZE__ = SIZEOF(.bss); /* 将__BSS_SIZE__的值设置为.bss输出分区大小(单位:字节) */

/* 1 TTB Level 1 table */
/* 4 TTB Level 2 tables */
    ._TTB : ALIGN(16384) { /* 定义输出分区名为._TTB,且VMA(虚拟内存地址)首地址16384字节对齐 */
       TTB = .; /* 定义TTB值为位置计数器(.)的值 */
       . = . + TTB_L1_SIZE; /* 位置计数器(.)值+=TTB_L1_SIZE */
       . = . + TTB_L2_SIZE * 4; /* 位置计数器(.)值+=TTB_L2_SIZE * 4 */
    } > RAM /* 将输出分区的LMA(加载内存地址)分配到RAM中 */


    /* User_heap_stack section, used to check that there is enough RAM left */
    ._user_heap_stack : /* 定义输出分区名为._user_heap_stack */
    {
        . = ALIGN(8); /* 位置计数器(.)设置为空闲存储区域首个8字节对齐地址 */
        PROVIDE ( end = . ); /* 定义end值为位置计数器(.)的值,仅当C中无定义时有效 */
        PROVIDE ( _end = . ); /* 定义_end值为位置计数器(.)的值,仅当C中无定义时有效 */
        . = . + _Min_Heap_Size; /* 位置计数器(.)的值+=_Min_Heap_Size */
        . = . + _Min_Stack_Size; /* 位置计数器(.)的值+=_Min_Stack_Size */
        . = ALIGN(8); /* 位置计数器(.)设置为空闲存储区域首个8字节对齐地址 */
    } >SRAM1 /* 将输出分区的LMA(加载内存地址)分配到SRAM中 */

  .SRAM1 (NOLOAD): /* 定义输出分区名为.SRAM1,不加载到存储空间 */
  {
    . = ALIGN(4); /* 位置计数器(.)设置为空闲存储区域首个4字节对齐地址 */
    *(.SRAM1); /* 将所有输入文件的包含.SRAM1关键字的段添加到输入分区 */
    . = ALIGN(4); /* 位置计数器(.)设置为空闲存储区域首个4字节对齐地址 */
  } >SRAM1 /* 将输出分区的LMA(加载内存地址)分配到SRAM1中 */

  .SRAM2 (NOLOAD): /* 定义输出分区名为.SRAM2,不加载到存储空间 */
  {
    . = ALIGN(4); /* 位置计数器(.)设置为空闲存储区域首个4字节对齐地址 */
    *(.SRAM2);  /* 将所有输入文件的包含.SRAM2关键字的段添加到输入分区 */
    . = ALIGN(4); /* 位置计数器(.)设置为空闲存储区域首个4字节对齐地址 */
  } >SRAM2 /* 将输出分区的LMA(加载内存地址)分配到SRAM2中 */

  .SRAM3 (NOLOAD): /* 定义输出分区名为.SRAM3,不加载到存储空间 */
  {
    . = ALIGN(4); /* 位置计数器(.)设置为空闲存储区域首个4字节对齐地址 */
    *(.SRAM3); /* 将所有输入文件的包含.SRAM3关键字的段添加到输入分区 */
    . = ALIGN(4); /* 位置计数器(.)设置为空闲存储区域首个4字节对齐地址 */
  } >SRAM3 /* 将输出分区的LMA(加载内存地址)分配到SRAM3中 */

  /* Remove information from the compiler libraries */
  /DISCARD/ : /* 特殊输出分区:用来丢弃输入分区 */
  {
    libc.a ( * ) /* 添加libc.a到输入分区 */
    libm.a ( * ) /* 添加libm.a到输入分区 */
    libgcc.a ( * ) /* 添加libgcc.a到输入分区 */
  }
}

我们按照输出分区一个个解析。

2.8.1 .RESET输出分区定义

. = __MEM_START__;  /* 将位置计数器值设置为__MEM_START__,也就是0x2FFE0000 */
    .RESET . : {        /* 定义输出分区名为.RESET,且将VMA(虚拟内存地址)设置为.也就是0x2FFE0000 */
        __TEXT_START__ = .; /* 将__TEXT_START__符号的值设置为.,也就是0x2FFE0000 */
        *(.text.Reset_Handler) /* 定义输入分区为任意输入文件中的Reset_Handler段,也就是复位中断服务函数 */
        * (RESET)  /* 定义输入分区为任意输入文件中的RESET段 */
        *(.text*) /* 定义输入分区为任意输入文件中的.text段 */

        /* .init is used by libc_nano */  
        /* 保护.init段,.init段会被libc_nano(精简版C库)使用到,避免被优化掉 */
        /* 保护‌‌fini段是‌ELF文件格式中的一个组成部分,用于在程序执行完毕后执行清理操作 */
        KEEP (*(.init))
        KEEP (*(.fini))

        __TEXT_END_UNALIGNED__ = .; /* 将__TEXT_END_UNALIGNED__符号的值设置为. */

        /*
         * Memory page(s) mapped to this section will be marked as
         * read-only, executable.  No non-executable data from the next section must
         * creep in.  Ensure the rest of the current memory page is unused.
         */
        __TEXT_END__ = .; /* 将__TEXT_END__符号的值设置为. */

        . = NEXT(4096); /* 将.的值修改为下一个是4096字节倍数的地址 */

        __RO_START__ = .; /* 将__RO_START__的值设置为. */
        *(.rodata*)  /* 定义输入分区为任意输入文件的rodata段 */

        __RO_END_UNALIGNED__ = .; /* 将__RO_END_UNALIGNED__的值设置为. */
        /*
         * Memory page(s) mapped to this section will be marked as
         * read-only, non-executable.  No RW data from the next section must
         * creep in.  Ensure the rest of the current memory page is unused.
         */
        . = NEXT(4096); /* 将.的值修改为下一个是4096字节倍数的地址 */
        __RO_END__ = .; /* 将__RO_END__的值设置为. */
    } >RAM :text /* 将输出分区的LMA(加载内存地址)分配到RAM中,按照PHDRS中指定的text加载方式 */

这部分内容过多,直接看注释部分即可。.text.Reset_Handler的含义是将Reset_Handler函数包含到输入分区。有关.text.Reset_Handler的含义可以查看.map文件加深认识(实际上就是函数的段,格式统一为.text.func(func为函数名)):
在这里插入图片描述
简单来说.text.symbol表示包含symbol函数到输入分区。

2.8.2 .ARM.extab输出分区定义

	.ARM.extab :   /* 定义输出分区名为.ARM.extab */
    {
    	. = ALIGN(4); /* 位置计数器(.)设置为空闲存储区域首个4字节对齐地址 */
    	*(.ARM.extab* .gnu.linkonce.armextab.*) /* 将任意输入文件的包含.ARM.extab、.gnu.linkonce.armextab.关键字的段添加到输入分区 */
    	. = ALIGN(4); /* 位置计数器(.)设置为空闲存储区域首个4字节对齐地址 */
    } >RAM /* 将输出分区的LMA(加载内存地址)分配到RAM中 */

将任意输入文件中包含.ARM.extab、.gnu.linkonce.armextab.关键字的段添加到.ARM.extab输出分区。
注:

.ARM.extab段通常用于存放函数的跳转表,让处理器能够快速定位函数地址。它存储的是每个指令的偏移量,便于实现动态链接及调用。

查看.map文件,其实没有任何符合要求的输入文件被添加到该输出分区:
在这里插入图片描述

2.8.3 .ARM输出分区定义

	.ARM :  /* 定义输出分区名为.ARM */
    {
    	. = ALIGN(4); /* 位置计数器(.)设置为空闲存储区域首个4字节对齐地址 */
    	__exidx_start = .; /* __exidx_start值设置为位置计数器(.)的值 */
    	*(.ARM.exidx*) /* 将任意输入文件的包含.ARM.exidx关键字的段添加到输入分区 */
    	__exidx_end = .; /* __exidx_end值设置为位置计数器(.)的值 */
    	. = ALIGN(4); /* 位置计数器(.)设置为空闲存储区域首个4字节对齐地址 */
    } >RAM /* 将输出分区的LMA(加载内存地址)分配到RAM中 */

将任意输入文件中包含.ARM.exidx关键字的段添加到.ARM输出分区。
注:

.ARM.exidx是执行索引表,包含了代码段的入口地址和可能需要的数据地址,有助于优化程序执行效率

查看.map文件,符合要求的段如下:
在这里插入图片描述

2.8.3 .preinit_array输出分区定义

	.preinit_array : /* 定义输出分区名为.preinit_array */
    {
    	. = ALIGN(4); /* 位置计数器(.)设置为空闲存储区域首个4字节对齐地址 */
    	PROVIDE_HIDDEN (__preinit_array_start = .);  /* 定义__preinit_array_start值为位置计数器(.)的值,仅当C中无定义时有效 */
    	KEEP (*(.preinit_array*)) /* 将所有输入文件中的包含.preinit_array关键字的段保护起来,避免被当做垃圾优化掉 */
    	PROVIDE_HIDDEN (__preinit_array_end = .); /* 定义__preinit_array_end值为位置计数器(.)的值,仅当C中无定义时有效 */
    	. = ALIGN(4); /* 位置计数器(.)设置为空闲存储区域首个4字节对齐地址 */
    } >RAM /* 将输出分区的LMA(加载内存地址)分配到RAM中 */

preinit_array段是编译器生成的初始化函数表,由于preinit_array段不会直接被程序所使用,为了避免被当做垃圾优化掉需要添加KEEP命令保护起来。
实际上.preinit_array这个输出分区也是空的:
在这里插入图片描述

2.8.4 .data输出分区定义

/* Used by the startup to initialize data */
    __DATA_ROM = .; /* 定义__DATA_ROM值为位置计数器(.)的值 */
    _sidata = LOADADDR(.data); /* 定义_sidata的值设为.data输出分区的LMA(加载内存地址)[绝对地址] */

    .data . : { /* 定义输出分区名为.data,且将VMA(虚拟内存地址)设置为位置计数器(.)的值 */
        RW_DATA = .; /* 定义RW_DATA值为位置计数器(.)的值 */
        _sdata = .; /* 定义_sdata值为位置计数器(.)的值 */
        *(.data*) /* 将任意输入文件的包含.data关键字的段添加到输入分区 */
        _edata = .; /* 定义_edata值为位置计数器(.)的值 */
        __DATA_END__ = .; /* 定义__DATA_END__值为位置计数器(.)的值 */
    } >RAM /* 将输出分区的LMA(加载内存地址)分配到RAM中 */

查看.map文件,.data输出分区分配如下:
在这里插入图片描述

2.8.4 .bss输出分区定义

/*
     * The .bss section gets initialised to 0 at runtime.
     * Its base address must be 16-byte aligned.
     */
    .bss : ALIGN(32) { /* 定义输出分区名为.bss,且VMA(虚拟内存地址)首地址32字节对齐 */
    	_sbss = .;         /* 定义RW_DATA值为位置计数器(.)的值 */
        ZI_DATA = .; /* 定义ZI_DATA值为位置计数器(.)的值 */
        *(SORT_BY_ALIGNMENT(.bss*)) /* 将所有输入文件中的包含.bss关键字的段按名称升序排序添加到输入分区 */
        *(COMMON) /* 将所有输入文件中的公共符号放到输入分区 */
        . = ALIGN(32); /* 位置计数器(.)设置为空闲存储区域首个32字节对齐地址 */
        _ebss = .;         /* 定义_ebss值为位置计数器(.)的值 */
        __BSS_END__ = .; /* 定义__BSS_END__值为位置计数器(.)的值 */
    } >RAM /* 将输出分区的LMA(加载内存地址)分配到RAM中 */

    __BSS_SIZE__ = SIZEOF(.bss); /* 将__BSS_SIZE__的值设置为.bss输出分区大小(单位:字节) */

查看.map文件,.bss输出分区分配如下:
在这里插入图片描述

2.8.5 ._TTB输出分区定义

/* 1 TTB Level 1 table */
/* 4 TTB Level 2 tables */
    ._TTB : ALIGN(16384) { /* 定义输出分区名为._TTB,且VMA(虚拟内存地址)首地址16384字节对齐 */
       TTB = .; /* 定义TTB值为位置计数器(.)的值 */
       . = . + TTB_L1_SIZE; /* 位置计数器(.)值+=TTB_L1_SIZE */
       . = . + TTB_L2_SIZE * 4; /* 位置计数器(.)值+=TTB_L2_SIZE * 4 */
    } > RAM /* 将输出分区的LMA(加载内存地址)分配到RAM中 */

这里为1、2级页表分配存储空间。本例未开启MMU,因此该输出分区为空。.map文件关于该输出分区分配情况如下:
在这里插入图片描述

2.8.6 ._user_heap_stack输出分区定义

/* User_heap_stack section, used to check that there is enough RAM left */
    ._user_heap_stack : /* 定义输出分区名为._user_heap_stack */
    {
        . = ALIGN(8); /* 位置计数器(.)设置为空闲存储区域首个8字节对齐地址 */
        PROVIDE ( end = . ); /* 定义end值为位置计数器(.)的值,仅当C中无定义时有效 */
        PROVIDE ( _end = . ); /* 定义_end值为位置计数器(.)的值,仅当C中无定义时有效 */
        . = . + _Min_Heap_Size; /* 位置计数器(.)的值+=_Min_Heap_Size */
        . = . + _Min_Stack_Size; /* 位置计数器(.)的值+=_Min_Stack_Size */
        . = ALIGN(8); /* 位置计数器(.)设置为空闲存储区域首个8字节对齐地址 */
    } >SRAM1 /* 将输出分区的LMA(加载内存地址)分配到SRAM中 */

._user_heap_stack输出分区为用户堆栈分配内存空间。.map文件中该输出分区分配如下:
在这里插入图片描述

2.8.7 ._user_heap_stack输出分区定义

  .SRAM1 (NOLOAD): /* 定义输出分区名为.SRAM1,不加载到存储空间 */
  {
    . = ALIGN(4); /* 位置计数器(.)设置为空闲存储区域首个4字节对齐地址 */
    *(.SRAM1); /* 将所有输入文件的包含.SRAM1关键字的段添加到输入分区 */
    . = ALIGN(4); /* 位置计数器(.)设置为空闲存储区域首个4字节对齐地址 */
  } >SRAM1 /* 将输出分区的LMA(加载内存地址)分配到SRAM1中 */

  .SRAM2 (NOLOAD): /* 定义输出分区名为.SRAM2,不加载到存储空间 */
  {
    . = ALIGN(4); /* 位置计数器(.)设置为空闲存储区域首个4字节对齐地址 */
    *(.SRAM2);  /* 将所有输入文件的包含.SRAM2关键字的段添加到输入分区 */
    . = ALIGN(4); /* 位置计数器(.)设置为空闲存储区域首个4字节对齐地址 */
  } >SRAM2 /* 将输出分区的LMA(加载内存地址)分配到SRAM2中 */

  .SRAM3 (NOLOAD): /* 定义输出分区名为.SRAM3,不加载到存储空间 */
  {
    . = ALIGN(4); /* 位置计数器(.)设置为空闲存储区域首个4字节对齐地址 */
    *(.SRAM3); /* 将所有输入文件的包含.SRAM3关键字的段添加到输入分区 */
    . = ALIGN(4); /* 位置计数器(.)设置为空闲存储区域首个4字节对齐地址 */
  } >SRAM3 /* 将输出分区的LMA(加载内存地址)分配到SRAM3中 */

以上3个分区分别分配到SRAM1、SRAM2、SRAM3,且属性为NOLOAD(不加载到镜像文件)、输出首地址和尾地址4字节对齐。上面操作相当于将存储区域划分了一部分供我们使用__attribute__((section(“xxx”)))关键字修饰的变量使用(需要注意这部分变量需要自行初始化为0x0,无法赋其它非0初值(除非修改链接脚本写法))。
以上写法也是我们实现指定变量存储区域的链接器脚本写法,假如我们想把一个变量定义在SRAM1,则C语言中可以这么写:

int g_val __attribute__((section(".SRAM1"))) = {0};

打开.map文件,可以看到g_val被分配到了SRAM1(SRM1地址范围:0x30000000-0x0x30003FFF)中:
在这里插入图片描述

2.8.8 /DISCARD/输出分区定义

/* Remove information from the compiler libraries */
  /DISCARD/ : /* 特殊输出分区:用来丢弃输入分区。丢弃的是以下库中未使用的部分 */
  {
    libc.a ( * ) /* 添加libc.a到输入分区 */
    libm.a ( * ) /* 添加libm.a到输入分区 */
    libgcc.a ( * ) /* 添加libgcc.a到输入分区 */
  }

/DISCARD/是个特殊的输出分区,任何该输出分区下的输入分区都不会被添加到输出文件。这里将libc.a、libm.a、libgcc.a排除在输出文件之外,这里所说的排除在外是排除那些没被程序使用到的部分以及不必要的部分(如.foo、.bar、.debug等)。
有关libc.a、libm.a、libgcc.a的介绍如下:
(1)libc.a:‌libc.a‌是一个静态库文件,它包含了多个C语言源文件编译成的目标文件(.o文件),这些目标文件以集合的形式存在,构成了libc.a库文件。
(2)libm.a:‌libm.a‌是一个静态库文件,它是C语言数学库的静态库。这个文件包含了许多数学函数的实现,如三角函数、指数函数、对数函数等,这些函数在编写C语言程序时可能会被用到。
(3)libgcc.a:‌libgcc.a是GCC编译器的内部库,用于处理复杂运算、提供弱符号和异常处理,支持语言特性,并在跨平台编译中起关键作用。‌

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

NW嵌入式开发

感谢您的支持,让我们一起进步!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值