LINUX-Linker scripts
Linker command scripts
下面範例將討論每個章節的細節
概括的說它定義了四個記憶體區塊( vect, rom, ram and cache)跟五個區段(vect, text, bss, init, and stack)
在採用段式記憶體管理的架構中,BSS段(bss segment)通常是指用來存放程序中未初始化的全局變數的一塊記憶體區域。
BSS是英文Block Started by Symbol的簡稱。BSS段屬於靜態記憶體分配。
在採用段式記憶體管理的架構中,數據段(data segment)通常是指用來存放程序中已初始化的全局變數的一塊記憶體區域。
數據段屬於靜態記憶體分配。
範例
/* a list of files to link (others may be supplied on the command line) */
INPUT(libc.a libg.a libgcc.a libc.a libgcc.a)
/* output format (can be overridden on command line) */
OUTPUT_formAT("coff-sh")
/* output filename (can be overridden on command line) */
OUTPUT_FILENAME("main.out")
/* our program’s entry point; not useful for much except to make sure the S7 record
is proper, because the reset vector actually defines the "entrypoint" in most embedded systems */
ENTRY(_start)
/* list of our memory sections */
MEMORY
{
vect : o = 0, l = 1k
rom : o = 0x400, l = 127k
ram : o = 0x400000, l = 128k
cache : o = 0xfffff000, l = 4k
}
/* how we’re organizing memory sections defined in each module */
SECTIONS
{
/* the interrupt vector table */
.vect :
{
__vect_start = .;
*(.vect);
__vect_end = .;
} > vect
/* code and constants */
.text :
{
__text_start = .;
*(.text)
*(.strings)
__text_end = .;
} > rom
/* uninitialized data */
.bss :
{
__bss_start = . ;
*(.bss)
*(COMMON)
__bss_end = . ;
} > ram
/* initialized data */
.init : AT (__text_end)
{
__data_start = .;
*(.data)
__data_end = .;
} > ram
/* application stack */
.stack :
{
__stack_start = .;
*(.stack)
__stack_end = .;
} > ram
}
注意!
ld將使用預設的命令檔,除非你告訴它使用其他方法
去指示ld去使用你的命令檔則給gcc -Wl,T OUTPUT_formAT命令
支援多種格式的輸出檔,包括S-records (srec), binary (binary), Intel Hex (ihex), 跟數個 debug-aware formats
像是COFF (coff-sh for SH-2 targets, coff-m68k for CPU32, 等等.)
使用objdump工具去找到你的linker版本有支援的格式
MEMORY命令
MEMORY命令描述目標系統的記憶體映射
典型簡單的語法如下
MEMORY {
name : o = origin, l = length
name : o = origin, l = length
...
}
SECTIONS命令
在SECTIONS命令的述語(Statements)是描述每一個輸出區段的配置而且詳細述語輸入區段
你可以只允許一個SECTIONS述語(Statements)在每個命令檔
但如果必要的話也可以有很多述語(Statements)在裡面
在這個例子,述語(statement)如下
/* code and constants */
.text :
/*一開始定義一個區段叫'.text'然後將述語(Statements)包含在{}裡*/
{
__text_start = .;
/*建立一個符號 叫__text_start 而且配置區段起始點*/
*(.strings)
*(.text)
/*合併全部輸入檔的.text 和 .strings 到這各個區段*/
__text_end = .;
/*建立一個符號 叫__text_end 而且配置區段結束點*/
/*最後結束述語*/
} > rom
/*告訴連結器(linker)定這個記憶體區段叫'rom' 根據MEMORY命令知道起始位址為0X400*/
輸入區段的列表也可以被歸成特定的檔案.如你增加一個像 foo.o (.specialsection)
到.text區段定義,然而連結器也將檔案foo.o的.specialsection區段合併到.text
bss/ data / code 相關概念 如下圖所示
AT指令
AT指令去告訴連結器(linker)去載入區段的資料到何處,
最好的了解AT指令的方法就是用例子解釋
所以考慮一個應用在只有一個初始全域變數
int a_global = 102;
在編譯的時候,gcc將宣告一個整數物件 a_global 跟 值=102 在模組的.data區段
但在連結.data區段時藉由一個AT指令,我們告訴連結器去分配a_global在一個位置(如RAM)的位址.但配置初始值到其他的地方(如 __text_end , 通常是 ROM)
跟隨著code初始a_global(跟其他初始的全域資料)
這個code使用符號_text_end, _data_start , 跟 _data_end 去找到初始值,決定它的大小跟配置它到RAM適當的位置
extern const char _text_start,
_text_end;
extern char _data_start,
_data_end;
memcpy( &_data_start, &_text_end,
&_data_end - &_data_start );
現在把它全部放在一起,跟隨命令列告訴gcc去編譯一個檔案 main.c 而且連結它使用 連結命令檔案 main.cmd gcc -g -Wl,-Tmain.cmd main.c
//-----------------------------------------------------------------------------------------
範例 u-boot lds
下面,結合u-boot.lds看看一個正式的連接腳本檔案。這個檔案的基本功能還能看明白,雖然上面分析了好多,但其中那些GNU風格的符號還是著實讓我感到迷惑,好菜啊,怪不得連被3家公司鄙視,自己鄙視自己。。。
OUTPUT_FORMAT("elf32littlearm", "elf32littlearm", "elf32littlearm")
;指定輸出可執行檔案是elf格式,32位ARM指令,小端
OUTPUT_ARCH(arm)
;指定輸出可執行檔案的平台為ARM
ENTRY(_start)
;指定輸出可執行檔案的起始代碼段為_start.
SECTIONS
{
. = 0x00000000 ; 從0x0位置開始
. = ALIGN(4) ; 代碼以4位元組對齊
.text : ;指定代碼段
{
cpu/arm920t/start.o (.text) ; 代碼的第一個代碼部分
*(.text) ;其它代碼部分
}
. = ALIGN(4)
.rodata : { *(.rodata) } ;指定只讀數據段
. = ALIGN(4);
.data : { *(.data) } ;指定讀/寫數據段
. = ALIGN(4);
.got : { *(.got) } ;指定got段, got段式是uboot自定義的一個段, 非標準段
__u_boot_cmd_start = . ;把__u_boot_cmd_start賦值為當前位置, 即起始位置
.u_boot_cmd : { *(.u_boot_cmd) } ;指定u_boot_cmd段, uboot把所有的uboot命令放在該段.
__u_boot_cmd_end = .;把__u_boot_cmd_end賦值為當前位置,即結束位置
. = ALIGN(4);
__bss_start = .; 把__bss_start賦值為當前位置,即bss段的開始位置
.bss : { *(.bss) }; 指定bss段
_end = .; 把_end賦值為當前位置,即bss段的結束位置
}
相關連結
http://www.delorie.com/gnu/docs/binutils/ld.html#SEC_Top
节:节名称,大小,节数据
输出节:输出文件中的节
加载节:节内数据,在运行时,被加载到内存
重加载节:节内无数据,在内存中要预留一个空间
调试节:含调试信息的节
符号名:带双引号, 不带双引号(不能同已有的关键字冲突)
在输入节中,未处理的孤儿节,被链接器放置在具有相同属性节的后面,若放不下,则放在文件尾部
ALIGN(exp,align) 按指定数据对齐 等同于ALIGN(.,align)
ORIGIN(ram) 计算内存区域的起址地址
LENGTH(ram) 计算内存区域的长度
. 表示定位计数器
VMA(virtual memory address):运行时的地址
LMA(load memory address):存储地址
输出节
SECTION [ADDRESS] [(TYPE)] : [AT(LMA)]
{
OUTPUT-SECTION-COMMAND
OUTPUT-SECTION-COMMAND
...
} [>REGION] [AT>LMA_REGION] [:PHDR :PHDR ...] [=FILLEXP]
SECTION输出节名
ADDRESS:运行的地址,若无,则用REGION设置它,若也无REGION,则用当前定位计数器
TYPE:默认即可, 也可用NOLOAD表示,这个节,运行时不加载
>REGION 将这个输入节放到哪个内存中
LMA_REGION
=FILLEXP 填充空白的数据
MEMORY
{
name [(attr)] : ORIGIN = origin, LENGTH = len
…
}
name 内存区名称
attr 属性 R W X A(Allocatable section) I(Initialized section) L(Same as 'I') !(插入以前的属性)
ORIGIN 开始地址
LENGTH 区域长度
程序头
PHDRS
{
name type [ FILEHDR ] [ PHDRS ] [ AT ( address ) ]
[ FLAGS ( flags ) ] ;
}
留言列表