LD 文件理解

发布时间 2023-04-20 17:42:54作者: lance9527

参考:https://www.404bugs.com/index.php/details/1084978780534788096

在介绍SECTIONS的用法之前,我们先对之前提到的LMA和VMA进行说明:每个output section都有一个LMA和一个VMA,LMA是其存储地址(即.data数据存储在fls中的地址),而VMA是其运行时地址(加载到ram中的地址),例如将全局变量g_Data所在数据段.data的LMA设为0x80000020(属于ROM地址),VMA设为0xD0004000(属于RAM地址),那么g_Data的值将存储在ROM中的0x80000020处,而程序运行时,用到g_Data的程序会到RAM中的0xD0004000处寻找它。

 

ENTRY(Reset_Handler) //设置入口地址,ENTRY是 LD文件的关键字

HEAP_SIZE = DEFINED(__heap_size__) ? __heap_size__ : 0x0400; //设置堆大小 ;DEFINED是 LD文件的关键字,类似ifdef
STACK_SIZE = DEFINED(__stack_size__) ? __stack_size__ : 0x0400; //设置栈大小 ;DEFINED是 LD文件的关键字

MEMORY //定义链接地址空间
{
  /*定义只读空间m_interrupts ,起始地址0x00000000, 大小0x00000400 */,这里是对存储空间的命名,这里定义了4片空间,取了4个名字
  m_interrupts (RX) : ORIGIN = 0x00000000, LENGTH = 0x00000400
  /*定义只读空间m_text,起始地址0x00000400, 大小0x0001FC00*/
  m_text (RX) : ORIGIN = 0x00000400, LENGTH = 0x0001FC00
  /*定义读写空间m_data,起始地址0x20000000, 大小0x00020000*/
  m_data (RW) : ORIGIN = 0x20000000, LENGTH = 0x00020000
  /*定义读写空间m_data2s ,起始地址0x20200000, 大小0x00000400*/
  m_data2 (RW) : ORIGIN = 0x20200000, LENGTH = 0x00040000
}

SECTIONS //定义输出段
{
  __NCACHE_REGION_START = ORIGIN(m_data2); //将m_data2的起始地址赋值给__NCACHE_REGION_START, ORIGIN是LD文件的关键字,取原始值之意; = 就是赋值语句
  __NCACHE_REGION_SIZE = 0;//__NCACHE_REGION_SIZE赋值0

  .interrupts : //输出段描述,表示这一段是中断向量表,就是一个名字,大家可以自己取,是其下{}内所有内容的别名
  {
    __VECTOR_TABLE = .; //把当前位置计数器的值赋值给__VECTOR_TABLE ,位置计数器的值一开始默认为0
    __Vectors = .;//把当前位置计数器的值赋值给__VECTOR_TABLE ,位置计数器的值一开始默认为0
    . = ALIGN(4); //这句话不是再给位置计数器赋值,而是给它增加限制条件,表示其增加一次增加4,在内存中的表现即为4字节对齐
    KEEP(*(.isr_vector))//放置所有文件中的.isr_vector section,*是通配符,表示所有,就跟我们搜索文件使用*时一样的。使用KEEP的意思就是告诉编译器这段数据非常重要,不要把它当成垃圾优化掉了
    . = ALIGN(4);
  } > m_interrupts //将这一输出段链接至m_interrupts区域处,所以中断向量表的链接地址就是m_interrupts的起始地址,m_interrupts是上面MEMORY定义的空间名称之一

  // > m_interrupts 只有这个表示 LMA 和VMA是一个地址,不需要从fls搬到ram,如果是 > m_interrupts  AT > m_data 的形式就表示LMA 和 VMA不是一个地址,VMA位于m_interrupts中,LMA位于m_data中

  .text : //定义输出段,就是一个名字,大家可以自己取
  {
    . = ALIGN(4); //4字节对齐
    *(.text) /*放置所有文件的.text段(code) */
    *(.text*) /*放置所有文件的.text*段(code) */
    *(.rodata) /*放置所有文件的.rodata段(constants, strings, etc.) */
    *(.rodata*) /*放置所有文件的.rodata*段(constants, strings, etc.) */
    *(.glue_7) /*同上*/
    *(.glue_7t) /*同上*/
    *(.eh_frame) /*同上*/
    KEEP (*(.init))
    KEEP (*(.fini))
    . = ALIGN(4);
  } > m_text //将这一输出段链接至m_text区域处

  .ARM.extab : //定义输出段,就是一个名字,大家可以自己取
  {
    *(.ARM.extab* .gnu.linkonce.armextab.*) //
  } > m_text //将这一输出段链接至m_text区域处

  .ARM : //定义输出段,就是一个名字,大家可以自己取
  {
    __exidx_start = .;
    *(.ARM.exidx*)
    __exidx_end = .;
  } > m_text //将这一输出段链接至m_text区域处

  .ctors : //定义输出段,就是一个名字,大家可以自己取
  {
    __CTOR_LIST__ = .;
    KEEP (*crtbegin.o(.ctors)) //放置*crtbegin.o中的.ctors段,并保证不被优化
    KEEP (*crtbegin?.o(.ctors)) //同上

    /*下面的语句中出现了EXCLUDE_FILE函数,这个函数的意思就是把括号里面的除外,
    意思就是说放置所有文件除了*crtend?.o *crtend.o文件的 .ctors段,
    因为在上面已经放置过了*/
    KEEP (*(EXCLUDE_FILE(*crtend?.o *crtend.o) .ctors))
    /*下面的语句中出现了SPORT函数,SOPT是SORT_BY_NAME的别名,
    意思是放置.ctors.*段的时候,按照名字的排列顺序来放置*/
    KEEP (*(SORT(.ctors.*)))
    KEEP (*(.ctors))
    __CTOR_END__ = .;
  } > m_text //将这一输出段链接至m_text区域处

  .dtors : //定义输出段,就是一个名字,大家可以自己取
  {
    __DTOR_LIST__ = .;
    KEEP (*crtbegin.o(.dtors))
    KEEP (*crtbegin?.o(.dtors))
    KEEP (*(EXCLUDE_FILE(*crtend?.o *crtend.o) .dtors))
    KEEP (*(SORT(.dtors.*)))
    KEEP (*(.dtors))
    __DTOR_END__ = .;
  } > m_text

  .preinit_array : //定义输出段,就是一个名字,大家可以自己取
  {
    /*下面的出现了PROVIDE_HIDDEN, 意思就是后面的这个符号__preinit_array_start 只能在
    链接器中被使用,外部文件是不能调用的,与它相反的还有PROVIDE, PROVIDE表示这个符号可以
    被外部调用,而且如果外部文件也定义了同样的符号也不会发生冲突,优先使用外部定义值,
    后面会出现很多PROVIDE*/
    PROVIDE_HIDDEN (__preinit_array_start = .);
    KEEP (*(.preinit_array*))
    PROVIDE_HIDDEN (__preinit_array_end = .);
  } > m_text

  .init_array : //定义输出段,就是一个名字,大家可以自己取
  {
    PROVIDE_HIDDEN (__init_array_start = .);
    KEEP (*(SORT(.init_array.*)))
    KEEP (*(.init_array*))
  PROVIDE_HIDDEN (__init_array_end = .);
  } > m_text

  .fini_array : //定义输出段,就是一个名字,大家可以自己取
  {
    PROVIDE_HIDDEN (__fini_array_start = .);
    KEEP (*(SORT(.fini_array.*)))
    KEEP (*(.fini_array*))
    PROVIDE_HIDDEN (__fini_array_end = .);
  } > m_text

  __etext = .;
  __DATA_ROM = .;

  __VECTOR_RAM = ORIGIN(m_interrupts);
  __RAM_VECTOR_TABLE_SIZE_BYTES = 0x0;

  .data : AT(__DATA_ROM) //AT的作用就是给当前输出段指定加载地址
  {
    . = ALIGN(4);
    __DATA_RAM = .;
    __data_start__ = .;
    *(m_usb_dma_init_data)
    *(.data)
    *(.data*)
    KEEP(*(.jcr*))
    . = ALIGN(4);
    __data_end__ = .;
  } > m_data
  __NDATA_ROM = __DATA_ROM + (__data_end__ - __data_start__);
  .ncache.init : AT(__NDATA_ROM)
  {
    __noncachedata_start__ = .;
    *(NonCacheable.init)
    . = ALIGN(4);
    __noncachedata_init_end__ = .;
  } > m_data
  . = __noncachedata_init_end__;
  .ncache :
  {
    *(NonCacheable)
    . = ALIGN(4);
    __noncachedata_end__ = .;
  } > m_data

  __DATA_END = __NDATA_ROM + (__noncachedata_init_end__ - __noncachedata_start__);
  text_end = ORIGIN(m_text) + LENGTH(m_text);
  ASSERT(__DATA_END <= text_end, "region m_text overflowed with text and data")

  /* Uninitialized data section */
  .bss :
  {
    /* This is used by the startup in order to initialize the .bss section */
    . = ALIGN(4);
    __START_BSS = .;
    __bss_start__ = .;
    *(m_usb_dma_noninit_data)
    *(.bss)
    *(.bss*)
    /*放置COMMON块,关于COMMON块是链接器为弱符号所制定的编译解决方案,
    本质上其实就是bass段,感兴趣的小伙伴可以自行去搜一搜*/
    *(COMMON)
    . = ALIGN(4);
    __bss_end__ = .;
    __END_BSS = .;
  } > m_data

  .heap :
  {
    . = ALIGN(8);
    __end__ = .;
    PROVIDE(end = .);
    __HeapBase = .;
    . += HEAP_SIZE;
    __HeapLimit = .;
    __heap_limit = .;
  } > m_data

  .stack :
  {
    . = ALIGN(8);
    . += STACK_SIZE;
  } > m_data

  __StackTop = ORIGIN(m_data) + LENGTH(m_data);
  __StackLimit = __StackTop - STACK_SIZE;
  PROVIDE(__stack = __StackTop);

  .ARM.attributes 0 : { *(.ARM.attributes) }

  /*ASSERT表示断言,跟C中的assert功能是一摸一样的*/
  ASSERT(__StackLimit >= __HeapLimit, "region m_data overflowed with stack and heap")
}