/*
*
*
*/
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)
如果兩個命令選項均沒有使用,則輸出的目標文件使用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文件中定義。含義接近定義輸出目標文件內部的本地變量。