链接文件分析 一、链接文件基础知识 什么是链接文件?有何作用? 当我们编写了多个C文件时,我们需要将它们编译链接成一个可执行的文件xxx.exe,此时就需要用到链接文件(.ld)。链接文件的主要功能就是:将多个目标文件(.o)和库文件(.a)链接成一个可执行文件。
链接脚本文件主要有什么内容?
链接配置(可不配置)
如一些符号变量的定义、入口地址、输出格式等
1 2 3 4 STACK_SIZE = 0X200 OUTPUT_FORMAT(elf32-littlearm) OUTPUT_ARCH(arm) ENTRY(_start)
内存布局定义
链接文件中以MEMORY命令 定义了存储空间,其中以ORIGIN 定义地址空间的起始地址,LENGTH 定义地址空间的长度
1 2 3 4 MEMORY { FLASH (rx) : ORIGIN = 0, LENGTH = 64K }
段链接定义
链接文件中以SECTIONS命令 定义一些段(text、data、bss段 等)链接分布
1 2 3 4 5 6 7 SECTIONS { .text: { *(.text*) } > FLASH }
.text段即代码段, *(.text*)指示将工程中所有目标文件的.text段链接到FLASH中
二、常用关键字、命令
MEMORY命令
1.1 使用MEMORY来定义内存如下:
1 2 3 4 5 6 MEMORY { NAME [(ATTR)] : ORIGIN = ORIGIN1, LENGTH = LEN1 NAME [(ATTR)] : ORIGIN = ORIGIN2, LENGTH = LEN2 ... }
1.2 参数介绍
NAME : 存储区域的名字 。(命名无要求)
ATTR :定义改存储区域的属性(权限)。ATTR属性内可以出现以下7个字符(可出现多个):
· R 只读section
· W 读/写section
· X 可执行section
· A 可分配的section
· I 初始化了的section
· L 与I相同
· ! 除该字符以外的任何一个属性的section
ORIGIN(关键字) : 区域的开始地址,可简写成org或o
LENGTH(关键字) : 区域的大小,可简写成len或l
1.3 KL36——BIOS示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 /* 定义内存区域的起始地址和长度 */ MEMORY { /*中断向量区*/ m_interrupts (RX) : ORIGIN = MCU_SECTORSIZE * GEC_BIOS_SECTOR_START +0x00000000, LENGTH = 0x00000100 /*配置区*/ m_flash_config (RX) : ORIGIN = MCU_SECTORSIZE * GEC_BIOS_SECTOR_START +0x00000400, LENGTH = 0x00000010 /*Flash代码区*/ m_text (RX) : ORIGIN = MCU_SECTORSIZE * GEC_BIOS_SECTOR_START +0x00000410, LENGTH = MCU_SECTORSIZE *(GEC_BIOS_SECTOR_END +0x01) - MCU_SECTORSIZE * GEC_BIOS_SECTOR_START - 0x0410 /*统命令代码放置处。*/ DY_CMD (RX) : ORIGIN = MCU_SECTORSIZE * GEC_DYNAMIC_START, LENGTH =MCU_SECTORSIZE * ( GEC_DYNAMIC_END - GEC_DYNAMIC_START + 1 ) /*函数列表放置处。*/ CPT_LST (RX) : ORIGIN = MCU_SECTORSIZE * GEC_COMPONENT_LST_START, LENGTH =MCU_SECTORSIZE * ( GEC_COMPONENT_LST_END - GEC_COMPONENT_LST_START + 1 ) /*函数代码放置处。*/ CPT_FUN (RX) : ORIGIN = MCU_SECTORSIZE * GEC_COMPONENT_FUN_START, LENGTH =MCU_SECTORSIZE * ( GEC_COMPONENT_FUN_END - GEC_COMPONENT_FUN_START + 1 ) /*RAM区*/ m_data (RW) : ORIGIN = GEC_BIOS_RAM_START, LENGTH = GEC_BIOS_RAM_END - GEC_BIOS_RAM_START }
SECTIONS命令
2.1 SECTIONS基本的命令语法如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 SECTIONS { ... secname start BLOCK(align) (NOLOAD) : AT ( ldadr ) { contents } >region :phdr =fill ... } /* 这么多参数,secname和contents是必须的,即可简写成: */ SECTIONS { ... secname : { contents } ... }
2.2 参数解析
链接脚本本质就是描述输入和输出。
secname表示输出文件的段,即输出文件中有那些段。
contents描述输出文件的这个段从哪些文件里抽取出来,即输入文件,一般就是目标文件。
2.3 示例
如下,将目标文件的数据段 链接到输出文件的数据段 (段名可以自己定义,段名前后必须要有空格)
1 2 3 4 5 6 7 8 9 10 SECTIONS { ... .data : /*输出文件中的data*/ { main.o(.data) /*输入文件中的data*/ *(.data) } ... }
其中 *(.data) 表示将所有的目标的.data段链接到输出文件.data段中, 特别注意的是,之前链接的就不会再链接,这样做的目的是可以将某些特殊的目标文件链接到地址前面。
2.4 其他参数的介绍
· start: 表示将某个段强制链接到的地址
· AT(addr) : 实现存放地址和加载地址不一致的功能,AT表示在文件中存放的位置,而在内存里呢,按照普通方式存储。
· region:这个region就是前面说的MEMORY命令定义的位置信息。
2.5 KL36——BIOS示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 /* Define output sections */ SECTIONS { /* The startup code goes first into internal flash */ .interrupts : { __VECTOR_TABLE = .; . = ALIGN(4); /* isr_vector在start.S中定义:.section .isr_vector, "a",按照MEMORY命令说明, isr_vector由于没有指定输出section,因此会创建与输入section同名的输出section,且会按照 isr_vector的属性放到合适的内存区域,此处KEEP是保证isr_vector的输出section不会被删除 */ KEEP(*(.isr_vector)) /* Startup code */ . = ALIGN(4); } > m_interrupts .flash_config : { . = ALIGN(4); KEEP(*(.FlashConfig)) /* Flash Configuration Field (FCF) */ . = ALIGN(4); } > m_flash_config /* The program code and other data goes into internal flash */ .text : { . = ALIGN(4); *(.text) /* .text sections (code) */ *(.text*) /* .text* sections (code) */ *(.rodata) /* .rodata sections (constants, strings, etc.) */ *(.rodata*) /* .rodata* sections (constants, strings, etc.) */ *(.glue_7) /* glue arm to thumb code */ *(.glue_7t) /* glue thumb to arm code */ *(.eh_frame) KEEP (*(.init)) KEEP (*(.fini)) . = ALIGN(4); } > m_text .ARM.extab : { *(.ARM.extab* .gnu.linkonce.armextab.*) } > m_text .ARM : { __exidx_start = .; *(.ARM.exidx*) __exidx_end = .; } > m_text .ctors : { __CTOR_LIST__ = .; /* gcc uses crtbegin.o to find the start of the constructors, so we make sure it is first. Because this is a wildcard, it doesn't matter if the user does not actually link against crtbegin.o; the linker won't look for a file to match a wildcard. The wildcard also means that it doesn't matter which directory crtbegin.o is in. */ KEEP (*crtbegin.o(.ctors)) KEEP (*crtbegin?.o(.ctors)) /* We don't want to include the .ctor section from from the crtend.o file until after the sorted ctors. The .ctor section from the crtend file contains the end of ctors marker and it must be last */ KEEP (*(EXCLUDE_FILE(*crtend?.o *crtend.o) .ctors)) KEEP (*(SORT(.ctors.*))) KEEP (*(.ctors)) __CTOR_END__ = .; } > 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 = .); 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 .dynamic_command : { . = ALIGN(1); KEEP(*(.dynamic_command)) . = ALIGN(1); } > DY_CMD .component_list : { . = ALIGN(1); KEEP(*(.component_list)) . = ALIGN(1); } > CPT_LST /* Flash中的构件函数区 */ .component_fun : { . = ALIGN(1); KEEP(*(.component_fun)) . = ALIGN(1); } > CPT_FUN __etext = .; /* define a global symbol at end of code */ __DATA_ROM = .; /* Symbol is used by startup for data initialization */ /* RAM中的变量区 */ .data : AT(__DATA_ROM) /*存储在FLASH中,上电后拷贝至RAM区*/ { . = ALIGN(4); __DATA_RAM = .; __data_start__ = .; /* create a global symbol at data start */ *(.data) /* .data sections */ *(.data*) /* .data* sections */ KEEP(*(.jcr*)) . = ALIGN(4); __data_end__ = .; /* define a global symbol at data end */ } > m_data __DATA_END = __DATA_ROM + (__data_end__ - __data_start__); .bss : { /* This is used by the startup in order to initialize the .bss section */ . = ALIGN(4); __START_BSS = .; __bss_start__ = .; *(.bss) *(.bss*) *(COMMON) . = ALIGN(4); __bss_end__ = .; __END_BSS = .; } > m_data .heap : { . = ALIGN(8); __end__ = .; PROVIDE(end = .); __HeapBase = .; __HeapLimit = .; } > m_data .stack : { . = ALIGN(8); } > m_data /* Initializes stack on the end of block */ __StackTop = ORIGIN(m_data) + LENGTH(m_data); PROVIDE(__stack = __StackTop); .ARM.attributes 0 : { *(.ARM.attributes) } }
各关键字
3.1 定位符号’.’的使用
‘.’表示当前地址,它可以被赋值也可以赋值给某个变量。
如下为将当前地址赋值给某个变量(链接器链接是按照SECTIONS里的段顺序排列的,前面的排列完之后就能计算出当前地址)—我们可以看到上述SECTIONS中频繁出现 . = ALIGN(x)或者 xxx = .;那么这个.的地址是会不断更新的。比如上一段.的地址 为 0x80,上一段大小有20,那么下一段的时候.的地址则为0x100
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 /*将.赋值给RAM_START*/ RAM_START = .; SECTIONS { . = 0×10000;/*将0x10000赋值给.*/ .text : { *(.text) } . = 0×8000000;/*将0x8000000赋值给.*/ .data : { *(.data) } }
“. = 0×10000;”该语句表示将当前地址设置为0x10000。如上代码中,意思是将所有目标文件的text段从0x10000地址开始存放。
3.2 PROVIDE关键字
该关键字定义一个(目标文件内被引用但没定义)符号。相当于定义一个全局变量的符号表,其他C文件可以通过该符号来操作对应的存储内存。
1 2 3 4 5 6 7 8 9 SECTIONS { .text : { *(.text) _etext = .; PROVIDE(etext = .); } }
如上,在链接脚本中声明了_etext 符号。特别注意的是_etext 只是一个符号,没有存储内存,并不是一个变量,该符对应(映射)的是一个地址,,其地址为.text section之后的第一个字节的地址。C文件中引用用法如下。
1 2 3 4 5 6 7 int main() { //引用该变量 extern char _etext; char *p = &_etext; //... }
若在链接脚本中 “ _etext = 0x100; “,即表示符号_etext对应的地址为0X100, 此时 & _etext的值为 0x100, char a= *p;表示为 从0X100地址取值存储的值赋值给变量a。
3.3 KEEP关键字
在连接命令行内使用了选项–gc-sections后,连接器可能将某些它认为没用的section过滤掉,此时就有必要强制连接器保留一些特定的 section,可用KEEP()关键字达此目的。如KEEP(* (.text))或KEEP(SORT(*)(.text))。说的通俗易懂就是:防止被优化。
3.4 ALIGN关键字
表示字节对齐, 如 “ . = ALIGN(4);”表示从该地址开始后面的存储进行4字节对齐。
后续解析具体实例