基本概念

.ld文件相关内容整理

  1. /* Entry Point */
  2. ENTRY(Reset_Handler)
  3. //1、设置了用户ram的最高地址
  4. /* Highest address of the user mode stack */
  5. _estack = 0x20020000; /* end of RAM */
  6. //2、设置用户栈空间以及堆空间的大小
  7. /* Generate a link error if heap and stack don't fit into RAM */
  8. _Min_Heap_Size = 0x4000; /* required amount of heap */
  9. _Min_Stack_Size = 0x1000; /* required amount of stack */
  10. //3、指定了RAM与ROM的起始地址,以及大小
  11. /* Specify the memory areas */
  12. MEMORY
  13. {
  14. RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 128K
  15. CCMRAM (rw) : ORIGIN = 0x10000000, LENGTH = 64K
  16. FLASH (rx) : ORIGIN = 0x8000000, LENGTH = 1024K
  17. }
  18. //4、将相关内容放到对应段中
  19. /* Define output sections */
  20. SECTIONS
  21. {
  22. //中断向量表放在.isr_vector段中
  23. /* The startup code goes first into FLASH */
  24. .isr_vector :
  25. {
  26. . = ALIGN(4);
  27. KEEP(*(.isr_vector)) /* Startup code */
  28. . = ALIGN(4);
  29. } >FLASH
  30. //程序代码和其他部分数据存放在.text段中
  31. /* The program code and other data goes into FLASH */
  32. .text :
  33. {
  34. . = ALIGN(4);
  35. *(.text) /* .text sections (code) */
  36. *(.text*) /* .text* sections (code) */
  37. *(.glue_7) /* glue arm to thumb code */
  38. *(.glue_7t) /* glue thumb to arm code */
  39. *(.eh_frame)
  40. KEEP (*(.init))
  41. KEEP (*(.fini))
  42. . = ALIGN(4);
  43. _etext = .; /* define a global symbols at end of code */
  44. } >FLASH
  45. //.rodata 存放常量(只读数据)
  46. /* Constant data goes into FLASH */
  47. .rodata :
  48. {
  49. . = ALIGN(4);
  50. *(.rodata) /* .rodata sections (constants, strings, etc.) */
  51. *(.rodata*) /* .rodata* sections (constants, strings, etc.) */
  52. . = ALIGN(4);
  53. } >FLASH
  54. .ARM.extab : { *(.ARM.extab* .gnu.linkonce.armextab.*) } >FLASH
  55. .ARM : {
  56. __exidx_start = .;
  57. *(.ARM.exidx*)
  58. __exidx_end = .;
  59. } >FLASH
  60. //.preinit_array 和 .init_array 保存程序或共享对象加载时的初始化函数指针
  61. .preinit_array :
  62. {
  63. PROVIDE_HIDDEN (__preinit_array_start = .);
  64. KEEP (*(.preinit_array*))
  65. PROVIDE_HIDDEN (__preinit_array_end = .);
  66. } >FLASH
  67. .init_array :
  68. {
  69. PROVIDE_HIDDEN (__init_array_start = .);
  70. KEEP (*(SORT(.init_array.*)))
  71. KEEP (*(.init_array*))
  72. PROVIDE_HIDDEN (__init_array_end = .);
  73. } >FLASH
  74. //.fini_array 保存程序或共享对象退出时的退出函数地址
  75. .fini_array :
  76. {
  77. PROVIDE_HIDDEN (__fini_array_start = .);
  78. KEEP (*(SORT(.fini_array.*)))
  79. KEEP (*(.fini_array*))
  80. PROVIDE_HIDDEN (__fini_array_end = .);
  81. } >FLASH
  82. /* used by the startup to initialize data */
  83. _sidata = LOADADDR(.data);
  84. //.data 存放了经过初始化的全局变量和静态变量
  85. /* Initialized data sections goes into RAM, load LMA copy after code */
  86. .data :
  87. {
  88. . = ALIGN(4);
  89. _sdata = .; /* create a global symbol at data start */
  90. *(.data) /* .data sections */
  91. *(.data*) /* .data* sections */
  92. . = ALIGN(4);
  93. _edata = .; /* define a global symbol at data end */
  94. } >RAM AT> FLASH
  95. _siccmram = LOADADDR(.ccmram);
  96. /* CCM-RAM section
  97. *
  98. * IMPORTANT NOTE!
  99. * If initialized variables will be placed in this section,
  100. * the startup code needs to be modified to copy the init-values.
  101. */
  102. //.ccmram 是STM32中只允许内核访问的空间,可以将一些内核放在这里使用
  103. .ccmram :
  104. {
  105. . = ALIGN(4);
  106. _sccmram = .; /* create a global symbol at ccmram start */
  107. *(.ccmram)
  108. *(.ccmram*)
  109. . = ALIGN(4);
  110. _eccmram = .; /* create a global symbol at ccmram end */
  111. } >CCMRAM AT> FLASH
  112. //.bss 保存了那些用到但未被初始化的数据
  113. /* Uninitialized data section */
  114. . = ALIGN(4);
  115. .bss :
  116. {
  117. /* This is used by the startup in order to initialize the .bss secion */
  118. _sbss = .; /* define a global symbol at bss start */
  119. __bss_start__ = _sbss;
  120. *(.bss)
  121. *(.bss*)
  122. *(COMMON)
  123. . = ALIGN(4);
  124. _ebss = .; /* define a global symbol at bss end */
  125. __bss_end__ = _ebss;
  126. } >RAM
  127. //._user_heap_stack 用户的堆栈段
  128. /* User_heap_stack section, used to check that there is enough RAM left */
  129. ._user_heap_stack :
  130. {
  131. . = ALIGN(4);
  132. PROVIDE ( end = . );
  133. PROVIDE ( _end = . );
  134. . = . + _Min_Heap_Size;
  135. . = . + _Min_Stack_Size;
  136. . = ALIGN(4);
  137. } >RAM
  138. /* Remove information from the standard libraries */
  139. /DISCARD/ :
  140. {
  141. libc.a ( * )
  142. libm.a ( * )
  143. libgcc.a ( * )
  144. }
  145. .ARM.attributes 0 : { *(.ARM.attributes) }
  146. }

简单的链接器脚本命令

常用关键字

  1. ENTRY(symbol) 用来指定程序执行的入口点
  2. MEMORY 内存分配命令
  3. SECTIONS 段命令 描述输出文件的内存和布局
  4. .text 程序代码段
  5. .rodata 只读数据
  6. .data 可读写且需要初始化的数据
  7. .bss 可读写的清零初始化数据
  8. ASSERT 断言
  9. PROVIDE(symbol=expression) 定义一个符号
  10. AT 后跟MEMORY定义的内存区域或者地址
  11. ALIGN 字节对齐

设置入口点

  1. ENTRY( _start ) /* 入口地址 */
  2. __stack_size = 2048; /* 定义栈大小 */
  3. PROVIDE( _stack_size = __stack_size );/* 定义_stack_size符号,类似于全局变量 */

有几种方式设置入口点,链接器将按照如下顺序设置入口点,一旦某个步骤成功,则停止:

  1. -e 命令行选项
  2. 链接器脚本中的ENTRY命令
  3. 如果定义了start符号,则使用这个符号的值
  4. 如果存在.text section,则使用其第一个字节地址。
  5. 地址0

    内存布局定义

    对MCU的Flash及RAM空间进行分配,其中以ORIGIN定义地址空间的起始地址,LENGTH定义地址空间的长度。

    这里的attr只能由以下特性组成 ‘R’ - Read-only section ‘W’ - Read/write section ‘X’ - Executable section ‘A’ - Allocatable section ‘I’ - Initialized section ‘L’ - Same as I ‘!’ - Invert the sense of any of the attributes that follow origin是一个数字表达式,代表了内存区域的起始地址。表达式必须等价于一个常数并且不能含有任何符号。关键字ORIGIN缩短为org或者o(但不能写成ORG)。

  1. MEMORY
  2. {
  3. name[(attr)] : ORIGIN = origin, LENGTH = length
  4. ...
  5. }
  6. MEMORY
  7. {
  8. rom (rx) : ORIGIN = 0, LENGTH = 256K
  9. ram (!rx) : org = 0x40000000, l = 4M
  10. }

段链接定义

用于定义目标文件(.o)textdatabss等段的链接分布。语法如下:

  1. SECTIONS
  2. {
  3. section [address] [(type)] :
  4. [AT(lma)]
  5. [ALIGN(section_align) | ALIGN_WITH_INPUT]
  6. [SUBALIGN(subsection_align)]
  7. [constraint]
  8. {
  9. output-section-command
  10. output-section-command
  11. ...
  12. } [>region] [AT>lma_region] [:phdr :phdr ...] [=fillexp] [,]
  13. ...
  14. }
  15. /* 大多数的段仅使用了上述部分属性,可以简写成如下形式 */
  16. SECTIONS
  17. {
  18. ...
  19. secname :
  20. {
  21. output-section-command
  22. }
  23. ...
  24. }

链接脚本详解

在脚本中经常看到类似“. = 0×10000;”的语句,该语句表示将当前地址设置为0x10000。在链接开始处没有定位器符号,则表示将输入段定位到MEMORY定义的空间最开始处,即offset为0。

  1. OUTPUT_ARCH(arm)//输出的程序指令集架构为arm
  2. ENTRY(_start) //程序入口
  3. SECTIONS {
  4. . = 0x0;//.代表当前地址,代表程序起始链接的地址
  5. .text :
  6. {
  7. *(.text) //所有文件的代码
  8. }
  9. .data :
  10. {
  11. *(.data)
  12. }
  13. bss_start = . ; // . 表示当前地址
  14. .bss :
  15. {
  16. *(.bss)
  17. }
  18. bss_end = . ;
  19. }
  20. ~

SECTIONS 命令

SECTIONS 命令告诉ld 如何把输入文件的sections 映射到输出文件的各个section: 如何将输入section 合为输出section; 如何把输出section 放入程序地址空间
secname:命名这个段
contents:用来确定代码的什么部分放在这个段
start:是这个段的重定位地址,也叫运行地址。如果代码中有位置无关的指令,程序在运行时必须放在这个地址上。
ALIGN(align):虽然指定了运行地址,但仍可以使用ALIGN(align)来指定对齐的要求—-这个对齐的地址才是真正的地址
(NOLOAD):来告诉加载器,在运行时不用加载这个段
AT(ldadr):指定这个段在编译出来的映像文件中的地址——加载地址(存储地址)

简单脚本命令

ENTRY(SYMBOL) :将符号SYMBOL的值设置成入口地址。
入口地址(entry point)是指进程执行的第一条用户空间的指令在进程地址空间的地址
ld有多种方法设置进程入口地址, 按一下顺序: (编号越前, 优先级越高)
1, ld命令行的-e选项
2, 连接脚本的ENTRY(SYMBOL)命令
3, 如果定义了start符号, 使用start符号值
4, 如果存在.text section, 使用.text section的第一字节的位置值
5, 使用值0
INCLUDE filename : 包含其他名为filename的链接脚本
相当于c程序内的的#include指令, 用以包含另一个链接脚本.
脚本搜索路径由-L选项指定. INCLUDE指令可以嵌套使用, 最大深度为10. 即: 文件1内INCLUDE文件2, 文件2内INCLUDE文件3… , 文件10内INCLUDE文件11. 那么文件11内不能再出现 INCLUDE指令了.
INPUT(files): 将括号内的文件做为链接过程的输入文件
ld首先在当前目录下寻找该文件, 如果没找到, 则在由-L指定的搜索路径下搜索. file可以为 -lfile形式,就象命令行的-l选项一样. 如果该命令出现在暗含的脚本内, 则该命令内的file在链接过程中的顺序由该暗含的脚本在命令行内的顺序决定.
GROUP(files) : 指定需要重复搜索符号定义的多个输入文件
file必须是库文件, 且file文件作为一组被ld重复扫描,直到不在有新的未定义的引用出现。
OUTPUT(FILENAME) : 定义输出文件的名字
同ld的-o选项, 不过-o选项的优先级更高. 所以它可以用来定义默认的输出文件名. 如a.out
SEARCH_DIR(PATH) :定义搜索路径,
同ld的-L选项, 不过由-L指定的路径要比它定义的优先被搜索。
STARTUP(filename) : 指定filename为第一个输入文件
在链接过程中, 每个输入文件是有顺序的. 此命令设置文件filename为第一个输入文件。
OUTPUT_FORMAT(BFDNAME) : 设置输出文件使用的BFD格式
同ld选项-o format BFDNAME, 不过ld选项优先级更高.
OUTPUT_FORMAT(DEFAULT,BIG,LITTLE) : 定义三种输出文件的格式(大小端)
若有命令行选项-EB, 则使用第2个BFD格式; 若有命令行选项-EL,则使用第3个BFD格式.否则默认选第一个BFD格式.
TARGET(BFDNAME):设置输入文件的BFD格式
同ld选项-b BFDNAME. 若使用了TARGET命令, 但未使用OUTPUT_FORMAT命令, 则最用一个TARGET命令设置的BFD格式将被作为输出文件的BFD格式.
ASSERT(EXP, MESSAGE):如果EXP不为真,终止连接过程
EXTERN(SYMBOL SYMBOL …):在输出文件中增加未定义的符号,如同连接器选项-u
FORCE_COMMON_ALLOCATION:为common symbol(通用符号)分配空间,即使用了-r连接选项也为其分配
NOCROSSREFS(SECTION SECTION …):检查列出的输出section,如果发现他们之间有相互引用,则报错。对于某些系统,特别是内存较紧张的嵌入式系统,某些section是不能同时存在内存中的,所以他们之间不能相互引用。
OUTPUT_ARCH(BFDARCH):设置输出文件的machine architecture(体系结构),BFDARCH为被BFD库使用的名字之一。可以用命令objdump -f查看。
可通过 man -S 1 ld查看ld的联机帮助, 里面也包括了对这些命令的介绍.

运行地址与存储地址

链接器脚本 - 图1程序从片内地址0开始,但为什么链接地址又设0x30000000,那不就从0x30000000开始了,反汇编可以看到不是从0开始的?
韦老大回答:

  1. 裸板程序烧在FLASH上 一上电,肯定从0地址运行
  2. 但是,0地址要么对应NOR FLASH,要么对应只有4K的片内内存
  3. 程序要读写数据,或是程序大于4K,怎么办?
  4. 程序就要复制到SDRAM里去执行
  5. SDRAM那么大,复制到哪个地址去?能随便选择地址吗
  6. 不能,要复制到它的链接地址去
  7. 为什么一定要复制到它的链接地址去?
  8. 因为这个链接地址是程序运行时“应该位于的地方”,比如要访问某个全局变量时,就是访问这个全局变量的链接地址
  9. 既然链接地址是SDRAM的地址,那为什么一开始程序可以从0地址运行
  10. 因为一开始的程序是“位置无关码”

总之:运行地址=-T指定的连接地址+相对偏移地址;存储地址=由oflash指定某存储器件的起始地址+由链接文件中AT指定的加载地址。
运行地址:程序在SRAM、SDRAM中执行时的地址。就是执行这条指令时,PC应该等于这个地址,换句话说,PC等于这个地址时,这条指令应该保存在这个地址内。
加载地址:程序保存在Nand flash中的地址。
位置无关码:B、BL、MOV都是位置位置无关码。
加载时地址就是程序放置的地址,运行地址就是程序定位的绝对地址,也即在编译连接时定位的地址。

  1. SECTIONS {
  2. firtst 0x00000000 : { head.o init.o }
  3. second 0x30000000 : AT(4096) { main.o }
  4. }

以上,head.o放在0x00000000地址开始处,init.o放在head.o后面,他们的运行地址也是0x00000000,即连接和存储地址相同(没有AT指定);
main.o放在4096(0x1000,是AT指定的,存储地址)开始处,但是它的运行地址在0x30000000,运行之前需要从0x1000(加载处)复制到0x30000000(运行处),
此过程也就用到了读取Nand flash。这就是存储地址和连接(运行)地址的不同,称为加载时域和运行时域,可以在.lds连接脚本文件中分别指定。
编写好的.lds文件,在用arm-linux-ld连接命令时带-Tfilename来调用执行,如arm-linux-ld –Tnand.lds x.o y.o –o xy.o。也用-Ttext参数直接指定连接地址,