u-boot.lds檔案詳解
網上大部分u-boot.lds檔案的分析大部分都是千遍一律,例如下面就是本人在網上找到的關於u-boot.lds的資料。
OUTPUT_FORMAT("elf32-littlearm","elf32-littlearm","elf32-littlearm")
/*指定輸出可執行檔案是elf格式,32位ARM指令,小端*/OUTPUT_ARCH(arm)
/*指定輸出可執行檔案的平臺為ARM*/ENTRY(_start)
/*指定輸出可執行檔案的起始程式碼段為_start*/SECTIONS{
/*指定可執行image檔案的全域性入口點,通常這個地址都放在ROM(flash)0x0
/*程式碼的第一個程式碼部分*/*(.text)
/*下面依次為各個text段函式*/}.= ALIGN(4);
/*程式碼以4位元組對齊*/.rodata :{*(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*)))}
/*指定只讀資料段*/.= ALIGN(4);
/*程式碼以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);
/*程式碼以4位元組對齊*/ __bss_start
/*把__bss_start賦值為當前位置,即bss段的開始位置*/.bss (NOLOAD):{*(.bss).= ALIGN(4);}
/*指定bss段,告訴載入器不要載入這個段*/ __bss_end =.;
/*把_end賦值為當前位置,即bss段的結束位置*/}
看完上面的解析思路本來應該是很清晰的,於是乎編譯u-boot,檢視一下System.map,
30100000 T _start
30100020 t _undefined_instruction
30100024 t _software_interrupt
30100028 t _prefetch_abort
3010002c t _data_abort
30100030 t _not_used
30100034 t _irq
30100038 t _fiq
發現 _start 的連結地址不是u-boot.lds中.text 的當前地址0x00000000,而是0x30100000,這就產生很多疑問了:
(1) 為什麼u-boot.lds指定的 .text 的首地址不起作用?
(2) 0x30100000是什麼地址,由誰指定.text的首地址是0x30100000的呢?
(3) 假如有其他動作改變了 .text 的首地址,那麼該動作跟u-boot.lds的優先順序又是怎麼決定的呢?
其實這三個問題都在Makefile的LDFLAGS 變數和u-boot.lds 中找到答案。我們不妨試著修改一下u-boot.lds,把u-boot.lds修改成如下(紅色字型部分為修改過部分):
OUTPUT_FORMAT("elf32-littlearm","elf32-littlearm","elf32-littlearm")
/*指定輸出可執行檔案是elf格式,32位ARM指令,小端*/OUTPUT_ARCH(arm)
/*指定輸出可執行檔案的平臺為ARM*/ENTRY(_start)
/*指定輸出可執行檔案的起始程式碼段為_start*/SECTIONS{
/*指定可執行image檔案的全域性入口點,通常這個地址都放在ROM(flash)0x0位置。必須使編譯器知道這個地址,通常都是修改此處來完成*/.= 0x30000000;/*;從0x0位置開始*/.= ALIGN(4);/*程式碼以4位元組對齊*/
.rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) }.= ALIGN(4);
/*程式碼以4位元組對齊*/
.text : { cpu/arm920t/start.o (.text)
/*程式碼的第一個程式碼部分*/ *(.text)
/*下面依次為各個text段函式*/ }
/*指定只讀資料段*/.= ALIGN(4);
/*程式碼以4位元組對齊*/.data :{*(.data)}.= ALIGN(4);
/*程式碼以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);
/*程式碼以4位元組對齊*/ __bss_start =.;
/*把__bss_start賦值為當前位置,即bss段的開始位置*/.bss (NOLOAD):{*(.bss).= ALIGN(4);}
/*指定bss段,告訴載入器不要載入這個段*/ __bss_end =.;
/*把_end賦值為當前位置,即bss段的結束位置*/}
上面對u-boot.lds主要做了兩點修改
(1) 把0x00000000 改成 0x30000000。
(2) 把 .text 和 .rodata 存放的地址調換了位置。
重新編譯 u-boot, 檢視System.map
30000000 R version_string
30000028 r C.27.2365
.
.
.
30100000 T _start
30100020 t _undefined_instruction
.
.
.
從上面的System.map部分內容可以看出:
(1) u-boot.lds設定的地址(0x00000000或0x30000000)是有效的。
(2) .text的地址仍然是30100000
跟著我們檢視Makefile中的LDFLAGS變數,發現一條指令
LDFLAGS += -Ttext $(TEXT_BASE) 其中TEXT_BASE 是在u-boot根目錄的board資料夾的對應的開發板名字的子目錄下的config.mk檔案中定義的
TEXT_BASE = 0x30100000
看到這裡我們應該明白為什麼_start,也就是.text的首地址總是等於0x30100000了,在連線的時候ld命令會把引數-Ttext指定的地址賦給.text,所以.text在u-boot.lds中的預設地址(當前地址)不起作用了。
連線指令碼的格式
====================
連線指令碼是文字檔案.
你寫了一系列的命令作為一個連線指令碼. 每一個命令是一個帶有引數的關鍵字,或者是一個對符號的賦值. 你可
以用分號分隔命令. 空格一般被忽略.
檔名或格式名之類的字串一般可以被直接鍵入. 如果檔名含有特殊字元,比如一般作為分隔檔名用的逗
號, 你可以把檔名放到雙引號中. 檔名中間無法使用雙引號.
你可以象在C語言中一樣,在連線指令碼中使用註釋, 用'/*'和'*/'隔開. 就像在C中,註釋在語法上等同於空格.
簡單的連線指令碼示例
============================
許多指令碼是相當的簡單的.
可能的最簡單的指令碼只含有一個命令: 'SECTIONS'. 你可以使用'SECTIONS'來描述輸出檔案的記憶體佈局.
'SECTIONS'是一個功能很強大的命令. 這裡這們會描述一個很簡單的使用. 讓我們假設你的程式只有程式碼節,
初始化過的資料節, 和未初始化過的資料節. 這些會存在於'.text','.data'和'.bss'節, 另外, 讓我們進一
步假設在你的輸入檔案中只有這些節.
對於這個例子, 我們說程式碼應當被載入到地址'0x10000'處, 而資料應當從0x8000000處開始. 下面是一個實現
這個功能的指令碼:
SECTIONS
{
. = 0x10000;
.text : { *(.text) }
. = 0x8000000;
.data : { *(.data) }
.bss : { *(.bss) }
}
你使用關鍵字'SECTIONS'寫了這個SECTIONS命令, 後面跟有一串放在花括號中的符號賦值和輸出節描述的內容.
上例中, 在'SECTIONS'命令中的第一行是對一個特殊的符號'.'賦值, 這是一個定位計數器. 如果你沒有以其
它的方式指定輸出節的地址(其他方式在後面會描述), 那地址值就會被設為定位計數器的現有值. 定位計數器
然後被加上輸出節的尺寸. 在'SECTIONS'命令的開始處, 定位計數器擁有值'0'.
第二行定義一個輸出節,'.text'. 冒號是語法需要,現在可以被忽略. 節名後面的花括號中,你列出所有應當被
放入到這個輸出節中的輸入節的名字. '*'是一個萬用字元,匹配任何檔名. 表示式'*(.text)'意思是所有的輸
入檔案中的'.text'輸入節.
因為當輸出節'.text'定義的時候, 定位計數器的值是'0x10000',聯結器會把輸出檔案中的'.text'節的地址設
為'0x10000'.
餘下的內容定義了輸出檔案中的'.data'節和'.bss'節. 聯結器會把'.data'輸出節放到地址'0x8000000'處. 連線
器放好'.data'輸出節之後, 定位計數器的值是'0x8000000'加上'.data'輸出節的長度. 得到的結果是聯結器會
把'.bss'輸出節放到緊接'.data'節後面的位置.
聯結器會通過在必要時增加定位計數器的值來保證每一個輸出節具有它所需的對齊. 在這個例子中, 為'.text'
和'.data'節指定的地址會滿足對齊約束, 但是聯結器可能會需要在'.data'和'.bss'節之間建立一個小的缺口.
就這樣,這是一個簡單但完整的連線指令碼.
簡單的連線指令碼命令.
=============================
在本章中,我們會描述一些簡單的指令碼命令.
設定入口點.
-----------------------
在執行一個程式時第一個被執行到的指令稱為"入口點". 你可以使用'ENTRY'連線指令碼命令來設定入口點.引數
是一個符號名:
ENTRY(SYMBOL)
有多種不同的方法來設定入口點.聯結器會通過按順序嘗試以下的方法來設定入口點, 如果成功了,就會停止.
* `-e'入口命令列選項;
* 連線指令碼中的`ENTRY(SYMBOL)'命令;
* 如果定義了start, 就使用start的值;
* 如果存在,就使用'.text'節的首地址;
* 地址`0'.
處理檔案的命令.
---------------------------
有幾個處理檔案的連線指令碼命令.
`INCLUDE FILENAME'
在當前點包含連線指令碼檔案FILENAME. 在當前路徑下或用'-L'選項指定的所有路徑下搜尋這個檔案,
你可以巢狀使用'INCLUDE'達10層.
`INPUT(FILE, FILE, ...)'
`INPUT(FILE FILE ...)'
'INPUT'命令指示聯結器在連線時包含檔案, 就像它們是在命令列上指定的一樣.
比如,如果你在連線的時候總是要包含檔案'subr.o',但是你對每次連線時要在命令列上輸入感到厭煩
, 你就可以在你的連線指令碼中輸入'INPUT (subr.o).
事實上,如果你喜歡,你可以把你所有的輸入檔案列在連線指令碼中, 然後在連線的時候什麼也不需要,
只要一個'-T'選項就夠了.
在一個'系統根字首'被配置的情況下, 一個檔名如果以'/'字元打頭, 並且指令碼也存放在系統根
字首的某個子目錄下, 檔名就會被在系統根字首下搜尋. 否則聯結器就會企圖開啟當前目錄下的文
件. 如果沒有發現, 聯結器會通過檔案庫搜尋路徑進行搜尋.
如果你使用了'INPUT (-lFILE)', 'ld'會把檔名轉換為'libFILE.a', 就象命令列引數'-l'一樣.
當你在一個隱式連線指令碼中使用'INPUT'命令的時候, 檔案就會在連線時連線指令碼檔案被包含的點上
被包含進來. 這會影響到檔案搜尋.
`GROUP(FILE, FILE, ...)'
`GROUP(FILE FILE ...)'
除了檔案必須全是檔案檔案之外, 'GROUP'命令跟'INPUT'相似, 它們會被反覆搜尋,直至沒有未定義
的引用被建立.
`OUTPUT(FILENAME)'
'OUTPUT'命令命名輸出檔案. 在連線指令碼中使用'OUTPUT(FILENAME)'命令跟在命令列中使用'-o
FILENAME'命令是完全等效的. 如果兩個都使用了, 那命令列選項優先.
你可以使用'OUTPUT'命令為輸出檔案建立一個預設的檔名,而不是常用的'a.out'.
`SEARCH_DIR(PATH)'
`SEARCH_DIR'命令給'ld'用於搜尋檔案檔案的路徑中再增加新的路徑. 使用`SEARCH_DIR(PATH)'跟在
命令列上使用'-L PATH'選項是完全等效的. 如果兩個都使用了, 那聯結器會兩個路徑都搜尋. 用命
令行選項指定的路徑首先被搜尋.
`STARTUP(FILENAME)'
除了FILENAME會成為第一個被連線的輸入檔案, 'STARTUP'命令跟'INPUT'命令完全相似, 就象這個文
件是在命令列上第一個被指定的檔案一樣. 如果在一個系統中, 入口點總是存在於第一個檔案中,那
這個就很有用.
處理目標檔案格式的命令.
-----------------------------------------
有兩個處理目標檔案格式的連線指令碼命令.
`OUTPUT_formAT(BFDNAME)'
`OUTPUT_formAT(DEFAULT, BIG, LITTLE)'
`OUTPUT_formAT'命令為輸出檔案使用的BFD格式命名. 使用`OUTPUT_formAT(BFDNAME)'跟在命令列上
使用'-oformat BFDNAME'是完全等效的. 如果兩個都使用了, 命令列選項優先.
你可在使用`OUTPUT_formAT'時帶有三個引數以使用不同的基於'-EB'和'-EL'的命令列選項的格式.
如果'-EB'和'-EL'都沒有使用, 那輸出格式會是第一個引數DEFAULT, 如果使用了'-EB',輸出格式會是
第二個引數BIG, 如果使用了'-EL', 輸出格式會是第三個引數, LITTLE.
比如, 預設的基於MIPS ELF平臺連線指令碼使用如下命令:
OUTPUT_formAT(elf32-bigmips, elf32-bigmips, elf32-littlemips)
這表示預設的輸出檔案格式是'elf32-bigmips', 但是當用戶使用'-EL'命令列選項的時候, 輸出檔案就會
被以`elf32-littlemips'格式建立.
`TARGET(BFDNAME)'
'TARGET'命令在讀取輸入檔案時命名BFD格式. 它會影響到後來的'INPUT'和'GROUP'命令. 這個命令跟
在命令列上使用`-b BFDNAME'相似. 如果使用了'TARGET'命令但`OUTPUT_formAT'沒有指定, 最後的
'TARGET'命令也被用來設定輸出檔案的格式.
其它的連線指令碼命令.
----------------------------
還有一些其它的連線指令碼命令.
`ASSERT(EXP, MESSAGE)'
確保EXP不等於零,如果等於零, 聯結器就會返回一個錯誤碼退出,並打印出MESSAGE.
`EXTERN(SYMBOL SYMBOL ...)'
強制SYMBOL作為一個無定義的符號輸入到輸出檔案中去. 這樣做了,可能會引發從標準庫中連線一些
節外的庫. 你可以為每一個EXTERN'列出幾個符號, 而且你可以多次使用'EXTERN'. 這個命令跟'-u'
命令列選項具有相同的效果.
`FORCE_COMMON_ALLOCATION'
這個命令跟命令列選項'-d'具有相同的效果: 就算指定了一個可重定位的輸出檔案('-r'),也讓'ld'
為普通符號分配空間.
`INHIBIT_COMMON_ALLOCATION'
這個命令跟命令列選項`--no-define-common'具有相同的效果: 就算是一個不可重位輸出檔案, 也讓
'ld'忽略為普通符號分配的空間.
`NOCROSSREFS(SECTION SECTION ...)'
這個命令在遇到在某些特定的節之間引用的時候會產生一條錯誤資訊.
在某些特定的程式中, 特別是在使用覆蓋技術的嵌入式系統中, 當一個節被載入記憶體時,另外一個節
就不會在記憶體中. 任何在兩個節之間的直接引用都會是一個錯誤. 比如, 如果節1中的程式碼呼叫了另
一個節中的一個函式,這就會產生一個錯誤.
`NOCROSSREFS'命令帶有一個輸出節名字的列表. 如果'ld'遇到任何在這些節之間的交叉引用, 它就
會報告一個錯誤,並返回一個非零退出碼. 注意, `NOCROSSREFS'命令使用輸出節名,而不是輸入節名.
`OUTPUT_ARCH(BFDARCH)'
指定一個特定的輸出機器架構. 這個引數是BFD庫中使用的一個名字. 你可以通過使用帶有'-f'選項
的'objdump'程式來檢視一個目標檔案的架構.
為符號賦值.
===========================
你可以在一個連線指令碼中為一個符號賦一個值. 這會把一個符號定義為一個全域性符號.
簡單的賦值.
------------------
你可以使用所有的C賦值符號為一個符號賦值.
`SYMBOL = EXPRESSION ;'
`SYMBOL += EXPRESSION ;'
`SYMBOL -= EXPRESSION ;'
`SYMBOL *= EXPRESSION ;'
`SYMBOL /= EXPRESSION ;'
`SYMBOL <<= EXPRESSION ;'
`SYMBOL >>= EXPRESSION ;'
`SYMBOL &= EXPRESSION ;'
`SYMBOL |= EXPRESSION ;'
第一個情況會把SYMBOL定義為值EXPRESSION. 其它情況下, SYMBOL必須是已經定義了的, 而值會作出相應的調
整.
特殊符號名'.'表示定位計數器. 你只可以在'SECTIONS'命令中使用它.
EXPRESSION後面的分號是必須的.
表示式下面會定義.
你在寫表示式賦值的時候,可以把它們作為單獨的部分,也可以作為'SECTIONS'命令中的一個語句,或者作為
'SECTIONS'命令中輸出節描述的一個部分.
符號所在的節會被設定成表示式所在的節.
下面是一個關於在三處地方使用符號賦值的例子:
floating_point = 0;
SECTIONS
{
.text :
{
*(.text)
_etext = .;
}
_bdata = (. + 3) & ~ 3;
.data : { *(.data) }
}
在這個例子中, 符號`floating_point'被定義為零. 符號'-etext'會被定義為前面一個'.text'節尾部的地址.
而符號'_bdata'會被定義為'.text'輸出節後面的一個向上對齊到4位元組邊界的一個地址值.
PROVIDE
-------
在某些情況下, 一個符號被引用到的時候只在連線指令碼中定義,而不在任何一個被連線進來的目標檔案中定
義. 這種做法是比較明智的. 比如, 傳統的聯結器定義了一個符號'etext'. 但是, ANSI C需要使用者能夠把
'etext'作為一個函式使用而不會產生錯誤. 'PROVIDE'關鍵字可以被用來定義一個符號, 比如'etext', 這個
定義只在它被引用到的時候有效,而在它被定義的時候無效.語法是 `PROVIDE(SYMBOL = EXPRESSION)'.
下面是一個關於使用'PROVIDE'定義'etext'的例子:
SECTIONS
{
.text :
{
*(.text)
_etext = .;
PROVIDE(etext = .);
}
}
在這個例子中, 如果程式定義了一個'_etext'(帶有一個前導下劃線), 聯結器會給出一個重定義錯誤. 如果,
程式定義了一個'etext'(不帶前導下劃線), 聯結器會預設使用程式中的定義. 如果程式引用了'etext'但不
定義它, 聯結器會使用連線指令碼中的定義.
SECTIONS命令
================
'SECTIONS'命令告訴聯結器如何把輸入節對映到輸出節, 並如何把輸出節放入到記憶體中.
'SECTIONS'命令的格式如下:
SECTIONS
{
SECTIONS-COMMAND
SECTIONS-COMMAND
...
}
每一個SECTIONS-COMMAND可能是如下的一種:
* 一個'ENTRY'命令.
* 一個符號賦值.
* 一個輸出節描述.
* 一個重疊描述.
'ENTRY'命令和符號賦值在'SECTIONS'命令中是允許的, 這是為了方便在這些命令中使用定位計數器. 這也可
以讓連線指令碼更容易理解, 因為你可以在更有意義的地方使用這些命令來控制輸出檔案的佈局.
輸出節描述和重疊描述在下面描述.
如果你在連線指令碼中不使用'SECTIONS'命令, 聯結器會按在輸入檔案中遇到的節的順序把每一個輸入節放到同
名的輸出節中. 如果所有的輸入節都在第一個檔案中存在,那輸出檔案中的節的順序會匹配第一個輸入檔案中
的節的順序. 第一個節會在地址零處.
輸出節描述
--------------------------
一個完整的輸出節的描述應該是這個樣子的:
SECTION [ADDRESS] [(TYPE)] : [AT(LMA)]
{
OUTPUT-SECTION-COMMAND
OUTPUT-SECTION-COMMAND
...
} [>REGION] [AT>LMA_REGION] [:PHDR :PHDR ...] [=FILLEXP]
大多數輸出節不使用這裡的可選節屬性.
SECTION邊上的空格是必須的, 所以節名是明確的. 冒號跟花括號也是必須的. 斷行和其他的空格是可選的.
每一個OUTPUT-SECTION-COMMAND可能是如下的情況:
* 一個符號賦值.
* 一個輸入節描述.
* 直接包含的資料值.
* 一個特定的輸出節關鍵字.
輸出節名.
-------------------
輸出節的名字是SECTION. SECTION必須滿足你的輸出格式的約束. 在一個只支援限制數量的節的格式中,比如
'a.out',這個名字必須是格式支援的節名中的一個(比如, 'a.out'只允許'.text', '.data'或'.bss').如果
輸出格式支援任意數量的節, 但是隻支援數字,而沒有名字(就像Oasys中的情況), 名字應當以一個雙引號中的
數值串的形式提供.一個節名可以由任意數量的字元組成,但是一個含有任意非常用字元(比如逗號)的字句必須
用雙引號引起來.
輸出節描述
--------------------------
ADDRESS是關於輸出節中VMS的一個表