/* * * */ MEMORY { flash : ORIGIN = 0x00000000, LENGTH = 512k sram : ORIGIN = 0x1FFF0000, LENGTH = 128k } OUTPUT_FORMAT("elf32-littlearm", "elf32-bigarm","elf32-littlearm") OUTPUT_ARCH(arm) SEARCH_DIR(.) GROUP(-lgcc -lc -lm -lcs3 -lcs3unhosted) ENTRY(system_start) /* These force the linker to search for particular symbols from * the start of the link process and thus ensure the user's * overrides are picked up */ EXTERN(__cs3_start_c main __cs3_stack __cs3_heap_end) /* force exit to be picked up in a hosted or os environment */ /* EXTERN(exit atexit) */ PROVIDE(__cs3_heap_start = _end); PROVIDE(__cs3_heap_end = __cs3_region_start_ram + __cs3_region_size_ram); PROVIDE(__cs3_region_num = (__cs3_regions_end - __cs3_regions) / 20); /*__libc_fini = _fini;*/ PROVIDE(__cs3_stack = __cs3_region_start_ram + __cs3_region_size_ram); SECTIONS { . = ALIGN(4); /* .text */ .text : { __text = . ; KEEP(*(.vector)) *(.text*) *(.glue_7t) *(.glue_7) } > sram .eh_frame_hdr : ALIGN (4) { KEEP (*(.eh_frame_hdr)) } > sram .eh_frame : ALIGN (4) { KEEP (*(.eh_frame)) } > sram /* .ARM.exidx is sorted, so has to go in its own output section. */ __exidx_start = .; .ARM.exidx : { *(.ARM.exidx* .gnu.linkonce.armexidx.*) } > sram __exidx_end = .; .rodata : ALIGN(4) { *(.rodata .rodata.* .gnu.linkonce.r.*) . = ALIGN(4); __cs3_regions = .; LONG (0) LONG (__cs3_region_init_ram) LONG (__cs3_region_start_ram) LONG (__cs3_region_init_size_ram) LONG (__cs3_region_zero_size_ram) __cs3_regions_end = .; . = ALIGN (8); _etext = .; } > sram . = ALIGN(4); __idata_start = . ; .data : ALIGN(8) { __cs3_region_start_ram = .; __data_start = . ; *(vtable) *(.data*) _edata = .; } > sram __idata_end = __idata_start + SIZEOF(.data); _edata = . ; PROVIDE (edata = .); .bss : { __bss_start = . ; *(.shbss) *(.bss .bss.* .gnu.linkonce.b.*) *(COMMON) . = ALIGN (8); *(.sram.b .bss.sram) } > sram _end = . ; __bss_end = .; PROVIDE (end = .) ; /* __cs3_region_end_ram is deprecated */ __onchip_ram_end = 0x1FFF0000 + LENGTH(sram); __cs3_region_end_ram = 0x1FFF0000 + LENGTH(sram); __cs3_region_size_ram = __cs3_region_end_ram - __cs3_region_start_ram; __cs3_region_init_ram = LOADADDR (.data); __cs3_region_init_size_ram = _edata - ADDR (.data); __cs3_region_zero_size_ram = _end - _edata; .stab 0 (NOLOAD) : { *(.stab) } .stabstr 0 (NOLOAD) : { *(.stabstr) } /* DWARF debug sections. Symbols in the DWARF debugging sections are relative to the beginning of the section so we begin them at 0. */ /* DWARF 1 */ .debug 0 : { *(.debug) } .line 0 : { *(.line) } /* GNU DWARF 1 extensions */ .debug_srcinfo 0 : { *(.debug_srcinfo) } .debug_sfnames 0 : { *(.debug_sfnames) } /* DWARF 1.1 and DWARF 2 */ .debug_aranges 0 : { *(.debug_aranges) } .debug_pubnames 0 : { *(.debug_pubnames) } /* DWARF 2 */ .debug_info 0 : { *(.debug_info) } .debug_abbrev 0 : { *(.debug_abbrev) } .debug_line 0 : { *(.debug_line) } .debug_frame 0 : { *(.debug_frame) } .debug_str 0 : { *(.debug_str) } .debug_loc 0 : { *(.debug_loc) } .debug_macinfo 0 : { *(.debug_macinfo) } /* SGI/MIPS DWARF 2 extensions */ .debug_weaknames 0 : { *(.debug_weaknames) } .debug_funcnames 0 : { *(.debug_funcnames) } .debug_typenames 0 : { *(.debug_typenames) } .debug_varnames 0 : { *(.debug_varnames) } .note.gnu.arm.ident 0 : { KEEP (*(.note.gnu.arm.ident)) } .ARM.attributes 0 : { KEEP (*(.ARM.attributes)) } /DISCARD/ : { *(.note.GNU-stack) } }
以上是hcx学长完成的链接脚本的相关内容,本文基于这个链接脚本并结合《GNU-ld链接脚本浅析》和GUN ld 在线手册对链接脚本进行相关的学习。
(1)内存区域命令
MEMORY命令的文法如下:
MEMORY {
NAME1 [(ATTR)] : ORIGIN = ORIGIN1, LENGTH = LEN2
NAME2 [(ATTR)] : ORIGIN = ORIGIN2, LENGTH = LEN2
...
}
其中NAME指定存储区域的名字,ATTR指定存储区域的属性,对应的属性有:
R 只读section
W 读/写section
X 可执行section
A ‘可分配的’section
I 初始化了的section
L 同I
! 不满足该字符之后的任何一个属性的section
ORIGIN :关键字,区域的开始地址,可简写成org或o
LENGTH :关键字,区域的大小,可简写成len或l
所以上面的代码:
MEMORY { flash : ORIGIN = 0x00000000, LENGTH = 512k sram : ORIGIN = 0x1FFF0000, LENGTH = 128k }
指定了两块存储区域,即flash区域和sram区域,因为对应的芯片为:MK60DN512ZVLQ10,所以对应的flash空间的大小为512k,对应的sram的大小为128k。
sram内存区域的起点描述参见K60系列学习(一)
(2)
OUTPUT_FORMAT("elf32-littlearm", "elf32-bigarm","elf32-littlearm") OUTPUT_ARCH(arm)
OUTPUT_FORMAT(
default,
big,
little)
使用3个的OUTPUT_FORMAT命令,可以通过-EB和-EL命令选项确定出书文件的不同的格式。
如果两个命令选项均没有使用,则输出的目标文件使用default确定的格式,本例子中即使用elf32-littlearm的格式,如果使用了-EB命令选项,则使用big对应的格式进行输出,如果使用了-EL选项则使用little确定的格式进行输出。
OUTPUT_ARCH()命令用于指定一个特定的输出文件的体系结构,这个参数是BFD库文件使用名字。可以通过objdump -f 命令通过
这里需要脑补一下BFD库的知识。关于bfd库的部分转自http://blog.csdn.net/zxremail/article/details/5192400
什么是BFD?
Binary format descriptor, 即二进制文件格式描述符,它是连接工具(ld)和二进制文件操作工具(bin-util)实现对于目标文件操作的标准接口,ld和bin-util通过调用实现BFD接口的库libbfd
来实现它们的目标文件操作功能。
BFD的结构
BFD整体上简单地可分为前端和后端(就象gcc一样),这样做的目的主要出于可移植性的考虑,前端向应用层提供统一的调用接口,是目标文件格式无关的,后端实现目标格式相关的部分,前端通过调用后端的相关函数实现真正的具体目标文件格式操作功能。所以如果要支持一种新嵌入式目标文件格式(就象uclinux支持的简化类coff文件格式BFLT)就只要修改BFD的后端就行了。
BFD的结构
前端的结构和一些主要的功能是:段操作,符号表处理,重定位管理,库操作,及其为了方便用的函数。后端涉及具体的文件格式操作方法的支持,典型的是a.out,coff,和elf.
BFD工作过程简述
每当bin-util工具如(objdump)打开一个目标文件时,工具通过调用BFD库里相关目标文件格式的信息判定该文件的二进制格式。然后抽取库里相关例程的信息建立对应的二进制格式操作描述符表(实际上诗歌函数指针表,有点象COM的VTABLE),利用这些指针,工具读取分析和操作该目标文件。我们利用objcopy(操作),objdump(读取、分析),readelf(读取),就是全仰仗BFD的功能。我刚才讲了现成目标文件的工作过程,那么目标文件的形成过程又是怎样的呢?如上所述,编译器/连接器在处理目标文件符号表等时先会调用前端函数(前端是统一和抽象的过程),然后通过后端处理具体文件格式的操作函数将符号表等相关信息写到输出文件,
这些任务的就是通过调用内存中的BFD描述符的函数实现的。
因为BFD库文件支持的差异,所以会造成工具链Sourcery_G++_Lite和gcc之间的差别,对于我们最终生成的main.elf文件如果使用gcc对应的objdump文件,所得到的文件头如下:
main.elf: 文件格式 elf32-little 体系结构:UNKNOWN!, 标志 0x00000112: EXEC_P, HAS_SYMS, D_PAGED 起始地址 0x00000441
使用Sourcery_G++对应的objdump工具arm-none-eabi-objdump得到的输出结果为:
main.elf: file format elf32-littlearm architecture: arm, flags 0x00000112: EXEC_P, HAS_SYMS, D_PAGED start address 0x00000441
这是因为如上面所描述的那样,BFD文件的后端存在差异,所以Sourcery_G++和gcc支持的文件格式有差异。
objdump:支持的目标: elf32-i386 a.out-i386-linux pei-i386 elf32-little elf32-big elf64-x86-64 elf32-x86-64 pei-x86-64 elf64-l1om elf64-k1om elf64-little elf64-big plugin srec symbolsrec verilog tekhex binary ihex trad-core
objdump:支持的体系结构: i386 i386:x86-64 i386:x64-32 i8086 i386:intel i386:x86-64:intel i386:x64-32:intel l1om l1om:intel k1om k1om:intel plugin
arm-none-eabi-objdump: supported targets: elf32-littlearm elf32-bigarm elf32-little elf32-big srec symbolsrec verilog tekhex binary ihex
arm-none-eabi-objdump: supported architectures: arm armv2 armv2a armv3 armv3m armv4 armv4t armv5 armv5t armv5te xscale ep9312 iwmmxt iwmmxt2
这也从另一种层面描述了我们需要使用Sourcery_G++这套工具链的原因。
(3)SEARCH_DIR(.)
SEARCH_DIR(path)命令将路径path添加到ld命令查找静态链接库archieve库文件的路径中。
(4)GROUP(-lgcc -lc -lm -lcs3 -lcs3unhosted)
GROUP命令作用和INPUT命令相似,差别在于GROUP命令指定的文件必须是archives静态链接库文件。INPUT命令用于指导链接器将指定的文件包含进link当中,就好象在命令行中输入一般。
另外如果使用INPUT(-lfile),ld链接器将自动将文件转变成libfile.a文件。
故此脚本中该命令行的含义是:将libgcc.a libc.a libm.a lincs3.a libcd3unhosted.a 这些静态库文件包含到link当中。
(5)ENTRY(system_start)
该命令用于设定入口点,指定用户程序执行的第一条指令。
(6)EXTERN(symbol,symbol...)
该命令强制符号symbol作为一个未定义的符号输入到输出文件中。
EXTERN(__cs3_start_c main __cs3_stack __cs3_heap_end)
使用readelf工具查看生成的main.elf文件可以看到这些符号。
(7)PROVIDE(__cs3_heap_start = _end)
PROVIDE命令用来定义一个符号,这个符号仅仅是被referenced,而没有在链接相关的任何的object文件中定义。含义接近定义输出目标文件内部的本地变量。