日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

u-boot.lds文件详解

發(fā)布時(shí)間:2024/9/3 编程问答 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 u-boot.lds文件详解 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

網(wǎng)上大部分u-boot.lds文件的分析大部分都是千遍一律,例如下面就是本人在網(wǎng)上找到的關(guān)于u-boot.lds的資料。

OUTPUT_FORMAT("elf32-littlearm",?"elf32-littlearm",?"elf32-littlearm")

/*指定輸出可執(zhí)行文件是elf格式,32ARM指令,小端*/
OUTPUT_ARCH(arm)

/*指定輸出可執(zhí)行文件的平臺(tái)為ARM*/
ENTRY(_start)

/*指定輸出可執(zhí)行文件的起始代碼段為_start*/
SECTIONS
{

/*指定可執(zhí)行image文件的全局入口點(diǎn),通常這個(gè)地址都放在ROM(flash)0x0位置。必須使編譯器知道這個(gè)地址,通常都是修改此處來完成*/
?.?=?0x00000000;/*;0x0位置開始*/
?.?=?ALIGN(4);/*代碼以4字節(jié)對齊*/
?.text?:
?{
??cpu/arm920t/start.o?(.text)?

?? ?/*代碼的第一個(gè)代碼部分*/??
??*(.text)

??/*下面依次為各個(gè)text段函數(shù)*/
?}
?.?=?ALIGN(4);

/*代碼以4字節(jié)對齊*/
?.rodata?:?{?*(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*)))?}

?/*指定只讀數(shù)據(jù)段*/
?.?=?ALIGN(4);

/*代碼以4字節(jié)對齊*/
?.data?:?{?*(.data)?}
?.?=?ALIGN(4);

/*代碼以4字節(jié)對齊*/
?.got?:?{?*(.got)?}

/*指定got, got段是uboot自定義的一個(gè)段,?非標(biāo)準(zhǔn)段*/
?.?=?.;
?__u_boot_cmd_start?=?.;

/*__u_boot_cmd_start賦值為當(dāng)前位置,?即起始位置*/
?.u_boot_cmd?:?{?*(.u_boot_cmd)?}

?/*指定u_boot_cmd, uboot把所有的uboot命令放在該段.*/
?__u_boot_cmd_end?=?.;

?/*__u_boot_cmd_end賦值為當(dāng)前位置,即結(jié)束位置*/
?.?=?ALIGN(4);

/*代碼以4字節(jié)對齊*/
?__bss_start?=?.;

?/*__bss_start賦值為當(dāng)前位置,bss段的開始位置*/
?.bss?(NOLOAD)?:?{?*(.bss)?.?=?ALIGN(4);?}

/*指定bss,告訴加載器不要加載這個(gè)段*/
?__bss_end?=?.;

/*_end賦值為當(dāng)前位置,bss段的結(jié)束位置*/
}

?

看完上面的解析思路本來應(yīng)該是很清晰的,于是乎編譯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

?

發(fā)現(xiàn)?_start?的鏈接地址不是u-boot.lds中.text?的當(dāng)前地址0x00000000,而是0x30100000,這就產(chǎn)生很多疑問了:

(1)?????為什么u-boot.lds指定的?.text?的首地址不起作用?

(2)?????0x30100000是什么地址,由誰指定.text的首地址是0x30100000的呢?

(3)?????假如有其他動(dòng)作改變了?.text?的首地址,那么該動(dòng)作跟u-boot.lds的優(yōu)先級又是怎么決定的呢?

其實(shí)這三個(gè)問題都在Makefile的LDFLAGS?變量和u-boot.lds?中找到答案。我們不妨試著修改一下u-boot.lds,把u-boot.lds修改成如下(紅色字體部分為修改過部分):

OUTPUT_FORMAT("elf32-littlearm",?"elf32-littlearm",?"elf32-littlearm")

/*指定輸出可執(zhí)行文件是elf格式,32ARM指令,小端*/
OUTPUT_ARCH(arm)

/*指定輸出可執(zhí)行文件的平臺(tái)為ARM*/
ENTRY(_start)

/*指定輸出可執(zhí)行文件的起始代碼段為_start*/
SECTIONS
{

/*指定可執(zhí)行image文件的全局入口點(diǎn),通常這個(gè)地址都放在ROM(flash)0x0位置。必須使編譯器知道這個(gè)地址,通常都是修改此處來完成*/
?.?=?0x30000000;/*;0x0位置開始*/
?.?=?ALIGN(4);/*代碼以4字節(jié)對齊*/

.rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) }
?.?=?ALIGN(4);

/*代碼以4字節(jié)對齊*/

?.text :
?{
??cpu/arm920t/start.o (.text)?

?? ?/*代碼的第一個(gè)代碼部分*/??
??*(.text)

??/*下面依次為各個(gè)text段函數(shù)*/
?}?

?/*指定只讀數(shù)據(jù)段*/
?.?=?ALIGN(4);

/*代碼以4字節(jié)對齊*/
?.data?:?{?*(.data)?}
?.?=?ALIGN(4);

/*代碼以4字節(jié)對齊*/
?.got?:?{?*(.got)?}

/*指定got, got段是uboot自定義的一個(gè)段,?非標(biāo)準(zhǔn)段*/
?.?=?.;
?__u_boot_cmd_start?=?.;

/*__u_boot_cmd_start賦值為當(dāng)前位置,?即起始位置*/
?.u_boot_cmd?:?{?*(.u_boot_cmd)?}

?/*指定u_boot_cmd, uboot把所有的uboot命令放在該段.*/
?__u_boot_cmd_end?=?.;

?/*__u_boot_cmd_end賦值為當(dāng)前位置,即結(jié)束位置*/
?.?=?ALIGN(4);

/*代碼以4字節(jié)對齊*/
?__bss_start?=?.;

?/*__bss_start賦值為當(dāng)前位置,bss段的開始位置*/
?.bss?(NOLOAD)?:?{?*(.bss)?.?=?ALIGN(4);?}

/*指定bss,告訴加載器不要加載這個(gè)段*/
?__bss_end?=?.;

/*_end賦值為當(dāng)前位置,bss段的結(jié)束位置*/
}

?

上面對u-boot.lds主要做了兩點(diǎn)修改

(1)?????把0x00000000?改成?0x30000000。

(2)?????把?.text?和?.rodata?存放的地址調(diào)換了位置。

重新編譯?u-boot,?查看System.map

30000000 R version_string

30000028 r C.27.2365

.

.

.

30100000 T _start

30100020 t _undefined_instruction

.

.

.

從上面的System.map部分內(nèi)容可以看出:

(1)?????u-boot.lds設(shè)定的地址(0x00000000或0x30000000)是有效的。

(2)?????.text的地址仍然是30100000

?

跟著我們查看Makefile中的LDFLAGS變量,發(fā)現(xiàn)一條指令

LDFLAGS += -Ttext $(TEXT_BASE)??其中TEXT_BASE?是在u-boot根目錄的board文件夾的對應(yīng)的開發(fā)板名字的子目錄下的config.mk文件中定義的

TEXT_BASE = 0x30100000

看到這里我們應(yīng)該明白為什么_start,也就是.text的首地址總是等于0x30100000了,在連接的時(shí)候ld命令會(huì)把參數(shù)-Ttext指定的地址賦給.text,所以.text在u-boot.lds中的默認(rèn)地址(當(dāng)前地址)不起作用了。








連接腳本的格式
====================

連接腳本是文本文件.

你寫了一系列的命令作為一個(gè)連接腳本. 每一個(gè)命令是一個(gè)帶有參數(shù)的關(guān)鍵字,或者是一個(gè)對符號(hào)的賦值. 你可
以用分號(hào)分隔命令. 空格一般被忽略.

文件名或格式名之類的字符串一般可以被直接鍵入. 如果文件名含有特殊字符,比如一般作為分隔文件名用的逗
號(hào), 你可以把文件名放到雙引號(hào)中. 文件名中間無法使用雙引號(hào).

你可以象在C語言中一樣,在連接腳本中使用注釋, 用'/*'和'*/'隔開. 就像在C中,注釋在語法上等同于空格.

簡單的連接腳本示例
============================

許多腳本是相當(dāng)?shù)暮唵蔚?

可能的最簡單的腳本只含有一個(gè)命令: 'SECTIONS'. 你可以使用'SECTIONS'來描述輸出文件的內(nèi)存布局.

'SECTIONS'是一個(gè)功能很強(qiáng)大的命令. 這里這們會(huì)描述一個(gè)很簡單的使用. 讓我們假設(shè)你的程序只有代碼節(jié),
初始化過的數(shù)據(jù)節(jié), 和未初始化過的數(shù)據(jù)節(jié). 這些會(huì)存在于'.text','.data'和'.bss'節(jié), 另外, 讓我們進(jìn)一
步假設(shè)在你的輸入文件中只有這些節(jié).

對于這個(gè)例子, 我們說代碼應(yīng)當(dāng)被載入到地址'0x10000'處, 而數(shù)據(jù)應(yīng)當(dāng)從0x8000000處開始. 下面是一個(gè)實(shí)現(xiàn)
這個(gè)功能的腳本:

??? SECTIONS
??? {
????? . = 0x10000;
????? .text : { *(.text) }
????? . = 0x8000000;
????? .data : { *(.data) }
????? .bss : { *(.bss) }
??? }

你使用關(guān)鍵字'SECTIONS'寫了這個(gè)SECTIONS命令, 后面跟有一串放在花括號(hào)中的符號(hào)賦值和輸出節(jié)描述的內(nèi)容.

上例中, 在'SECTIONS'命令中的第一行是對一個(gè)特殊的符號(hào)'.'賦值, 這是一個(gè)定位計(jì)數(shù)器. 如果你沒有以其
它的方式指定輸出節(jié)的地址(其他方式在后面會(huì)描述), 那地址值就會(huì)被設(shè)為定位計(jì)數(shù)器的現(xiàn)有值. 定位計(jì)數(shù)器
然后被加上輸出節(jié)的尺寸. 在'SECTIONS'命令的開始處, 定位計(jì)數(shù)器擁有值'0'.

第二行定義一個(gè)輸出節(jié),'.text'. 冒號(hào)是語法需要,現(xiàn)在可以被忽略. 節(jié)名后面的花括號(hào)中,你列出所有應(yīng)當(dāng)被
放入到這個(gè)輸出節(jié)中的輸入節(jié)的名字. '*'是一個(gè)通配符,匹配任何文件名. 表達(dá)式'*(.text)'意思是所有的輸
入文件中的'.text'輸入節(jié).

因?yàn)楫?dāng)輸出節(jié)'.text'定義的時(shí)候, 定位計(jì)數(shù)器的值是'0x10000',連接器會(huì)把輸出文件中的'.text'節(jié)的地址設(shè)
為'0x10000'.

余下的內(nèi)容定義了輸出文件中的'.data'節(jié)和'.bss'節(jié). 連接器會(huì)把'.data'輸出節(jié)放到地址'0x8000000'處. 連接
器放好'.data'輸出節(jié)之后, 定位計(jì)數(shù)器的值是'0x8000000'加上'.data'輸出節(jié)的長度. 得到的結(jié)果是連接器會(huì)
把'.bss'輸出節(jié)放到緊接'.data'節(jié)后面的位置.

連接器會(huì)通過在必要時(shí)增加定位計(jì)數(shù)器的值來保證每一個(gè)輸出節(jié)具有它所需的對齊. 在這個(gè)例子中, 為'.text'
和'.data'節(jié)指定的地址會(huì)滿足對齊約束, 但是連接器可能會(huì)需要在'.data'和'.bss'節(jié)之間創(chuàng)建一個(gè)小的缺口.

就這樣,這是一個(gè)簡單但完整的連接腳本.

簡單的連接腳本命令.
=============================

在本章中,我們會(huì)描述一些簡單的腳本命令.

設(shè)置入口點(diǎn).
-----------------------

在運(yùn)行一個(gè)程序時(shí)第一個(gè)被執(zhí)行到的指令稱為"入口點(diǎn)". 你可以使用'ENTRY'連接腳本命令來設(shè)置入口點(diǎn).參數(shù)
是一個(gè)符號(hào)名:
??? ENTRY(SYMBOL)

有多種不同的方法來設(shè)置入口點(diǎn).連接器會(huì)通過按順序嘗試以下的方法來設(shè)置入口點(diǎn), 如果成功了,就會(huì)停止.

? * `-e'入口命令行選項(xiàng);

? * 連接腳本中的`ENTRY(SYMBOL)'命令;

? * 如果定義了start, 就使用start的值;

? * 如果存在,就使用'.text'節(jié)的首地址;

? * 地址`0'.

處理文件的命令.
---------------------------

有幾個(gè)處理文件的連接腳本命令.

`INCLUDE FILENAME'
在當(dāng)前點(diǎn)包含連接腳本文件FILENAME. 在當(dāng)前路徑下或用'-L'選項(xiàng)指定的所有路徑下搜索這個(gè)文件,
你可以嵌套使用'INCLUDE'達(dá)10層.

`INPUT(FILE, FILE, ...)'
`INPUT(FILE FILE ...)'
'INPUT'命令指示連接器在連接時(shí)包含文件, 就像它們是在命令行上指定的一樣.

比如,如果你在連接的時(shí)候總是要包含文件'subr.o',但是你對每次連接時(shí)要在命令行上輸入感到厭煩
, 你就可以在你的連接腳本中輸入'INPUT (subr.o).

事實(shí)上,如果你喜歡,你可以把你所有的輸入文件列在連接腳本中, 然后在連接的時(shí)候什么也不需要,
只要一個(gè)'-T'選項(xiàng)就夠了.

在一個(gè)'系統(tǒng)根前綴'被配置的情況下, 一個(gè)文件名如果以'/'字符打頭, 并且腳本也存放在系統(tǒng)根
前綴的某個(gè)子目錄下, 文件名就會(huì)被在系統(tǒng)根前綴下搜索. 否則連接器就會(huì)企圖打開當(dāng)前目錄下的文
件. 如果沒有發(fā)現(xiàn), 連接器會(huì)通過檔案庫搜索路徑進(jìn)行搜索.

如果你使用了'INPUT (-lFILE)', 'ld'會(huì)把文件名轉(zhuǎn)換為'libFILE.a', 就象命令行參數(shù)'-l'一樣.

當(dāng)你在一個(gè)隱式連接腳本中使用'INPUT'命令的時(shí)候, 文件就會(huì)在連接時(shí)連接腳本文件被包含的點(diǎn)上
被包含進(jìn)來. 這會(huì)影響到檔案搜索.

`GROUP(FILE, FILE, ...)'
`GROUP(FILE FILE ...)'
除了文件必須全是檔案文件之外, 'GROUP'命令跟'INPUT'相似, 它們會(huì)被反復(fù)搜索,直至沒有未定義
的引用被創(chuàng)建.

`OUTPUT(FILENAME)'
'OUTPUT'命令命名輸出文件. 在連接腳本中使用'OUTPUT(FILENAME)'命令跟在命令行中使用'-o?
FILENAME'命令是完全等效的. 如果兩個(gè)都使用了, 那命令行選項(xiàng)優(yōu)先.

你可以使用'OUTPUT'命令為輸出文件創(chuàng)建一個(gè)缺省的文件名,而不是常用的'a.out'.

`SEARCH_DIR(PATH)'
`SEARCH_DIR'命令給'ld'用于搜索檔案文件的路徑中再增加新的路徑. 使用`SEARCH_DIR(PATH)'跟在
命令行上使用'-L PATH'選項(xiàng)是完全等效的. 如果兩個(gè)都使用了, 那連接器會(huì)兩個(gè)路徑都搜索. 用命
令行選項(xiàng)指定的路徑首先被搜索.

`STARTUP(FILENAME)'
除了FILENAME會(huì)成為第一個(gè)被連接的輸入文件, 'STARTUP'命令跟'INPUT'命令完全相似, 就象這個(gè)文
件是在命令行上第一個(gè)被指定的文件一樣. 如果在一個(gè)系統(tǒng)中, 入口點(diǎn)總是存在于第一個(gè)文件中,那
這個(gè)就很有用.

處理目標(biāo)文件格式的命令.
-----------------------------------------

有兩個(gè)處理目標(biāo)文件格式的連接腳本命令.

`OUTPUT_formAT(BFDNAME)'
`OUTPUT_formAT(DEFAULT, BIG, LITTLE)'
`OUTPUT_formAT'命令為輸出文件使用的BFD格式命名. 使用`OUTPUT_formAT(BFDNAME)'跟在命令行上
使用'-oformat BFDNAME'是完全等效的. 如果兩個(gè)都使用了, 命令行選項(xiàng)優(yōu)先.

你可在使用`OUTPUT_formAT'時(shí)帶有三個(gè)參數(shù)以使用不同的基于'-EB'和'-EL'的命令行選項(xiàng)的格式.

如果'-EB'和'-EL'都沒有使用, 那輸出格式會(huì)是第一個(gè)參數(shù)DEFAULT, 如果使用了'-EB',輸出格式會(huì)是
第二個(gè)參數(shù)BIG, 如果使用了'-EL', 輸出格式會(huì)是第三個(gè)參數(shù), LITTLE.

比如, 缺省的基于MIPS ELF平臺(tái)連接腳本使用如下命令:

??????? OUTPUT_formAT(elf32-bigmips, elf32-bigmips, elf32-littlemips)
??? 這表示缺省的輸出文件格式是'elf32-bigmips', 但是當(dāng)用戶使用'-EL'命令行選項(xiàng)的時(shí)候, 輸出文件就會(huì)
??? 被以`elf32-littlemips'格式創(chuàng)建.

`TARGET(BFDNAME)'
'TARGET'命令在讀取輸入文件時(shí)命名BFD格式. 它會(huì)影響到后來的'INPUT'和'GROUP'命令. 這個(gè)命令跟
在命令行上使用`-b BFDNAME'相似. 如果使用了'TARGET'命令但`OUTPUT_formAT'沒有指定, 最后的
'TARGET'命令也被用來設(shè)置輸出文件的格式.

其它的連接腳本命令.
----------------------------

還有一些其它的連接腳本命令.

`ASSERT(EXP, MESSAGE)'
確保EXP不等于零,如果等于零, 連接器就會(huì)返回一個(gè)錯(cuò)誤碼退出,并打印出MESSAGE.

`EXTERN(SYMBOL SYMBOL ...)'
強(qiáng)制SYMBOL作為一個(gè)無定義的符號(hào)輸入到輸出文件中去. 這樣做了,可能會(huì)引發(fā)從標(biāo)準(zhǔn)庫中連接一些
節(jié)外的庫. 你可以為每一個(gè)EXTERN'列出幾個(gè)符號(hào), 而且你可以多次使用'EXTERN'. 這個(gè)命令跟'-u'
命令行選項(xiàng)具有相同的效果.

`FORCE_COMMON_ALLOCATION'
這個(gè)命令跟命令行選項(xiàng)'-d'具有相同的效果: 就算指定了一個(gè)可重定位的輸出文件('-r'),也讓'ld'
為普通符號(hào)分配空間.

`INHIBIT_COMMON_ALLOCATION'
這個(gè)命令跟命令行選項(xiàng)`--no-define-common'具有相同的效果: 就算是一個(gè)不可重位輸出文件, 也讓
'ld'忽略為普通符號(hào)分配的空間.

`NOCROSSREFS(SECTION SECTION ...)'
這個(gè)命令在遇到在某些特定的節(jié)之間引用的時(shí)候會(huì)產(chǎn)生一條錯(cuò)誤信息.

在某些特定的程序中, 特別是在使用覆蓋技術(shù)的嵌入式系統(tǒng)中, 當(dāng)一個(gè)節(jié)被載入內(nèi)存時(shí),另外一個(gè)節(jié)
就不會(huì)在內(nèi)存中. 任何在兩個(gè)節(jié)之間的直接引用都會(huì)是一個(gè)錯(cuò)誤. 比如, 如果節(jié)1中的代碼調(diào)用了另
一個(gè)節(jié)中的一個(gè)函數(shù),這就會(huì)產(chǎn)生一個(gè)錯(cuò)誤.

`NOCROSSREFS'命令帶有一個(gè)輸出節(jié)名字的列表. 如果'ld'遇到任何在這些節(jié)之間的交叉引用, 它就
會(huì)報(bào)告一個(gè)錯(cuò)誤,并返回一個(gè)非零退出碼. 注意, `NOCROSSREFS'命令使用輸出節(jié)名,而不是輸入節(jié)名.

`OUTPUT_ARCH(BFDARCH)'
指定一個(gè)特定的輸出機(jī)器架構(gòu). 這個(gè)參數(shù)是BFD庫中使用的一個(gè)名字. 你可以通過使用帶有'-f'選項(xiàng)
的'objdump'程序來查看一個(gè)目標(biāo)文件的架構(gòu).

為符號(hào)賦值.
===========================

你可以在一個(gè)連接腳本中為一個(gè)符號(hào)賦一個(gè)值. 這會(huì)把一個(gè)符號(hào)定義為一個(gè)全局符號(hào).

簡單的賦值.
------------------

你可以使用所有的C賦值符號(hào)為一個(gè)符號(hào)賦值.

`SYMBOL = EXPRESSION ;'
`SYMBOL += EXPRESSION ;'
`SYMBOL -= EXPRESSION ;'
`SYMBOL *= EXPRESSION ;'
`SYMBOL /= EXPRESSION ;'
`SYMBOL <<= EXPRESSION ;'
`SYMBOL >>= EXPRESSION ;'
`SYMBOL &= EXPRESSION ;'
`SYMBOL |= EXPRESSION ;'

第一個(gè)情況會(huì)把SYMBOL定義為值EXPRESSION. 其它情況下, SYMBOL必須是已經(jīng)定義了的, 而值會(huì)作出相應(yīng)的調(diào)
整.

特殊符號(hào)名'.'表示定位計(jì)數(shù)器. 你只可以在'SECTIONS'命令中使用它.

EXPRESSION后面的分號(hào)是必須的.

表達(dá)式下面會(huì)定義.

你在寫表達(dá)式賦值的時(shí)候,可以把它們作為單獨(dú)的部分,也可以作為'SECTIONS'命令中的一個(gè)語句,或者作為
'SECTIONS'命令中輸出節(jié)描述的一個(gè)部分.

符號(hào)所在的節(jié)會(huì)被設(shè)置成表達(dá)式所在的節(jié).

下面是一個(gè)關(guān)于在三處地方使用符號(hào)賦值的例子:

??? floating_point = 0;
??? SECTIONS
??? {
????? .text :
??????? {
????????? *(.text)
????????? _etext = .;
??????? }
????? _bdata = (. + 3) & ~ 3;
????? .data : { *(.data) }
??? }

在這個(gè)例子中, 符號(hào)`floating_point'被定義為零. 符號(hào)'-etext'會(huì)被定義為前面一個(gè)'.text'節(jié)尾部的地址.
而符號(hào)'_bdata'會(huì)被定義為'.text'輸出節(jié)后面的一個(gè)向上對齊到4字節(jié)邊界的一個(gè)地址值.

PROVIDE
-------

在某些情況下, 一個(gè)符號(hào)被引用到的時(shí)候只在連接腳本中定義,而不在任何一個(gè)被連接進(jìn)來的目標(biāo)文件中定
義. 這種做法是比較明智的. 比如, 傳統(tǒng)的連接器定義了一個(gè)符號(hào)'etext'. 但是, ANSI C需要用戶能夠把
'etext'作為一個(gè)函數(shù)使用而不會(huì)產(chǎn)生錯(cuò)誤. 'PROVIDE'關(guān)鍵字可以被用來定義一個(gè)符號(hào), 比如'etext', 這個(gè)
定義只在它被引用到的時(shí)候有效,而在它被定義的時(shí)候無效.語法是 `PROVIDE(SYMBOL = EXPRESSION)'.

下面是一個(gè)關(guān)于使用'PROVIDE'定義'etext'的例子:

??? SECTIONS
??? {
????? .text :
??????? {
????????? *(.text)
????????? _etext = .;
????????? PROVIDE(etext = .);
??????? }
??? }

在這個(gè)例子中, 如果程序定義了一個(gè)'_etext'(帶有一個(gè)前導(dǎo)下劃線), 連接器會(huì)給出一個(gè)重定義錯(cuò)誤. 如果,
程序定義了一個(gè)'etext'(不帶前導(dǎo)下劃線), 連接器會(huì)默認(rèn)使用程序中的定義. 如果程序引用了'etext'但不
定義它, 連接器會(huì)使用連接腳本中的定義.

SECTIONS命令
================

'SECTIONS'命令告訴連接器如何把輸入節(jié)映射到輸出節(jié), 并如何把輸出節(jié)放入到內(nèi)存中.

'SECTIONS'命令的格式如下:

??? SECTIONS
??? {
????? SECTIONS-COMMAND
????? SECTIONS-COMMAND
????? ...
??? }

每一個(gè)SECTIONS-COMMAND可能是如下的一種:

? * 一個(gè)'ENTRY'命令.

? * 一個(gè)符號(hào)賦值.

? * 一個(gè)輸出節(jié)描述.

? * 一個(gè)重疊描述.

'ENTRY'命令和符號(hào)賦值在'SECTIONS'命令中是允許的, 這是為了方便在這些命令中使用定位計(jì)數(shù)器. 這也可
以讓連接腳本更容易理解, 因?yàn)槟憧梢栽诟幸饬x的地方使用這些命令來控制輸出文件的布局.

輸出節(jié)描述和重疊描述在下面描述.

如果你在連接腳本中不使用'SECTIONS'命令, 連接器會(huì)按在輸入文件中遇到的節(jié)的順序把每一個(gè)輸入節(jié)放到同
名的輸出節(jié)中. 如果所有的輸入節(jié)都在第一個(gè)文件中存在,那輸出文件中的節(jié)的順序會(huì)匹配第一個(gè)輸入文件中
的節(jié)的順序. 第一個(gè)節(jié)會(huì)在地址零處.

輸出節(jié)描述
--------------------------

一個(gè)完整的輸出節(jié)的描述應(yīng)該是這個(gè)樣子的:

??? SECTION [ADDRESS] [(TYPE)] : [AT(LMA)]
????? {
??????? OUTPUT-SECTION-COMMAND
??????? OUTPUT-SECTION-COMMAND
??????? ...
????? } [>REGION] [AT>LMA_REGION] [:PHDR :PHDR ...] [=FILLEXP]

大多數(shù)輸出節(jié)不使用這里的可選節(jié)屬性.

SECTION邊上的空格是必須的, 所以節(jié)名是明確的. 冒號(hào)跟花括號(hào)也是必須的. 斷行和其他的空格是可選的.

每一個(gè)OUTPUT-SECTION-COMMAND可能是如下的情況:

? * 一個(gè)符號(hào)賦值.

? * 一個(gè)輸入節(jié)描述.

? * 直接包含的數(shù)據(jù)值.

? * 一個(gè)特定的輸出節(jié)關(guān)鍵字.
??
輸出節(jié)名.
-------------------

輸出節(jié)的名字是SECTION. SECTION必須滿足你的輸出格式的約束. 在一個(gè)只支持限制數(shù)量的節(jié)的格式中,比如
'a.out',這個(gè)名字必須是格式支持的節(jié)名中的一個(gè)(比如, 'a.out'只允許'.text', '.data'或'.bss').如果
輸出格式支持任意數(shù)量的節(jié), 但是只支持?jǐn)?shù)字,而沒有名字(就像Oasys中的情況), 名字應(yīng)當(dāng)以一個(gè)雙引號(hào)中的
數(shù)值串的形式提供.一個(gè)節(jié)名可以由任意數(shù)量的字符組成,但是一個(gè)含有任意非常用字符(比如逗號(hào))的字句必須
用雙引號(hào)引起來.

輸出節(jié)描述
--------------------------

ADDRESS是關(guān)于輸出節(jié)中VMS的一個(gè)表達(dá)式. 如果你不提供ADDRESS, 連接器會(huì)基于REGION(如果存在)設(shè)置它,或
者基于定位計(jì)數(shù)器的當(dāng)前值.

如果你提供了ADDRESS, 那輸出節(jié)的地址會(huì)被精確地設(shè)為這個(gè)值. 如果你既不提供ADDRESS也不提供REGION, 那
輸出節(jié)的地址會(huì)被設(shè)為當(dāng)前的定位計(jì)數(shù)器向上對齊到輸出節(jié)需要的對齊邊界的值. 輸出節(jié)的對齊要求是所有輸
入節(jié)中含有的對齊要求中最嚴(yán)格的一個(gè).

比如:
??? .text . : { *(.text) }


??? .text : { *(.text) }

有細(xì)微的不同. 第一個(gè)會(huì)把'.text'輸出節(jié)的地址設(shè)為當(dāng)前定位計(jì)數(shù)器的值. 第二個(gè)會(huì)把它設(shè)為定位計(jì)數(shù)器的
當(dāng)前值向上對齊到'.text'輸入節(jié)中對齊要求最嚴(yán)格的一個(gè)邊界.

ADDRESS可以是任意表達(dá)式; 比如,如果你需要把節(jié)對齊對0x10字節(jié)邊界,這樣就可以讓低四字節(jié)的節(jié)地址值為
零, 你可以這樣做:

??? .text ALIGN(0x10) : { *(.text) }

這個(gè)語句可以正常工作,因?yàn)?#39;ALIGN'返回當(dāng)前的定位計(jì)數(shù)器,并向上對齊到指定的值.

指定一個(gè)節(jié)的地址會(huì)改變定位計(jì)數(shù)器的值.

輸入節(jié)描述
-------------------------

最常用的輸出節(jié)命令是輸入節(jié)描述.

輸入節(jié)描述是最基本的連接腳本操作. 你使用輸出節(jié)來告訴連接器在內(nèi)存中如何布局你的程序. 你使用輸入節(jié)
來告訴連接器如何把輸入文件映射到你的內(nèi)存中.

輸入節(jié)基礎(chǔ)
---------------------------

一個(gè)輸入節(jié)描述由一個(gè)文件名后跟有可選的括號(hào)中的節(jié)名列表組成.

文件名和節(jié)名可以通配符形式出現(xiàn), 這個(gè)我們以后再介紹.

最常用的輸入節(jié)描述是包含在輸出節(jié)中的所有具有特定名字的輸入節(jié). 比如, 包含所有輸入'.text'節(jié),你可以
這樣寫:

??? *(.text)

這里,'*'是一個(gè)通配符,匹配所有的文件名. 為把一部分文件排除在匹配的名字通配符之外, EXCLUDE_FILE可
以用來匹配所有的除了在EXCLUDE_FILE列表中指定的文件.比如:

??? (*(EXCLUDE_FILE (*crtend.o *otherfile.o) .ctors))

會(huì)讓除了`crtend.o'文件和`otherfile.o'文件之外的所有的文件中的所有的.ctors節(jié)被包含進(jìn)來.

有兩種方法包含多于一個(gè)的節(jié):

??? *(.text .rdata)
??? *(.text) *(.rdata)

上面兩句的區(qū)別在于'.text'和'.rdata'輸入節(jié)的輸出節(jié)中出現(xiàn)的順序不同. 在第一個(gè)例子中, 兩種節(jié)會(huì)交替
出現(xiàn),并以連接器的輸入順序排布. 在第二個(gè)例子中,所有的'.text'輸入節(jié)會(huì)先出現(xiàn),然后是所有的'.rdata'節(jié).

你可以指定文件名,以從一個(gè)特定的文件中包含節(jié). 如果一個(gè)或多個(gè)你的文件含有特殊的數(shù)據(jù)在內(nèi)存中需要特
殊的定位,你可以這樣做. 比如:

??? data.o(.data)

如果你使用一個(gè)不帶有節(jié)列表的文件名, 那輸入文件中的所有的節(jié)會(huì)被包含到輸出節(jié)中. 通常不會(huì)這樣做, 但
是在某些場合下這個(gè)可能非常有用. 比如:

??? data.o
????
當(dāng)你使用一個(gè)不含有任何通配符的文件名時(shí), 連接器首先會(huì)查看你是否在連接命令行上指定了文件名或者在
'INPUT'命令中. 如果你沒有, 連接器會(huì)試圖把這個(gè)文件作為一個(gè)輸入文件打開, 就像它在命令行上出現(xiàn)一樣.
注意這跟'INPUT'命令不一樣, 因?yàn)檫B接器會(huì)在檔案搜索路徑中搜索文件.

輸入節(jié)通配符
---------------------------------

在一個(gè)輸入節(jié)描述中, 文件名或者節(jié)名,或者兩者同時(shí)都可以是通配符形式.

文件名通配符'*'在很多例子中都可以看到,這是一個(gè)簡單的文件名通配符形式.

通配符形式跟Unix Shell中使用的一樣.

`*'
匹配任意數(shù)量的字符.

`?'
匹配單個(gè)字符.

`[CHARS]'
匹配CHARS中的任意單個(gè)字符; 字符'-'可以被用來指定字符的方訌, 比如[a-z]匹配任意小字字符.

`\'
轉(zhuǎn)義其后的字符.

當(dāng)一個(gè)文件名跟一個(gè)通配符匹配時(shí), 通配符字符不會(huì)匹配一個(gè)'/'字符(在UNIX系統(tǒng)中用來分隔目錄名), 一個(gè)
含有單個(gè)'*'字符的形式是個(gè)例外; 它總是匹配任意文件名, 不管它是否含有'/'. 在一個(gè)節(jié)名中, 通配符字
符會(huì)匹配'/'字符.

文件名通配符只匹配那些在命令行或在'INPUT'命令上顯式指定的文件. 連接器不會(huì)通過搜索目錄來展開通配
符.

如果一個(gè)文件名匹配多于一個(gè)通配符, 或者如果一個(gè)文件名顯式出現(xiàn)同時(shí)又匹配了一個(gè)通配符, 連接器會(huì)使用
第一次匹配到的連接腳本. 比如, 下面的輸入節(jié)描述序列很可能就是錯(cuò)誤的,因?yàn)?#39;data.o'規(guī)則沒有被使用:

??? .data : { *(.data) }
??? .data1 : { data.o(.data) }

通常, 連接器會(huì)把匹配通配符的文件和節(jié)按在連接中被看到的順序放置. 你可以通過'SORT'關(guān)鍵字改變它, 它
出現(xiàn)在括號(hào)中的通配符之前(比如, 'SORT(.text*)'). 當(dāng)'SORT'關(guān)鍵字被使用時(shí), 連接器會(huì)在把文件和節(jié)放到
輸出文件中之前按名字順序重新排列它們.

如果你對于輸入節(jié)被放置到哪里去了感到很困惑, 那可以使用'-M'連接選項(xiàng)來產(chǎn)生一個(gè)位圖文件. 位圖文件會(huì)
精確顯示輸入節(jié)是如何被映射到輸出節(jié)中的.

這個(gè)例子顯示了通配符是如何被用來區(qū)分文件的. 這個(gè)連接腳本指示連接器把所有的'.text'節(jié)放到'.text'中, 把所有的'.bss'節(jié)放到'.bss'. 連接器會(huì)把所有的來自文件名以一個(gè)大寫字母開始的文件中的'.data'節(jié)放進(jìn)'.DATA'節(jié)中; 對于所有其他文件, 連接器會(huì)把'.data'節(jié)放進(jìn)'.data'節(jié)中.

??? SECTIONS {
????? .text : { *(.text) }
????? .DATA : { [A-Z]*(.data) }
????? .data : { *(.data) }
????? .bss : { *(.bss) }
??? }

輸入節(jié)中的普通符號(hào).
-----------------------------------

對于普通符號(hào),需要一個(gè)特殊的標(biāo)識(shí), 因?yàn)樵诤芏嗄繕?biāo)格式中, 普通符號(hào)沒有一個(gè)特定的輸入節(jié). 連接器會(huì)把
普通符號(hào)處理成好像它們在一個(gè)叫做'COMMON'的節(jié)中.

你可能像使用帶有其他輸入節(jié)的文件名一樣使用帶有'COMMON'節(jié)的文件名。你可以通過這個(gè)把來自一個(gè)特定輸
入文件的普通符號(hào)放入一個(gè)節(jié)中,同時(shí)把來自其它輸入文件的普通符號(hào)放入另一個(gè)節(jié)中。

在大多數(shù)情況下,輸入文件中的普通符號(hào)會(huì)被放到輸出文件的'.bss'節(jié)中。比如:

??? .bss { *(.bss) *(COMMON) }

有些目標(biāo)文件格式具有多于一個(gè)的普通符號(hào)。比如,MIPS ELF目標(biāo)文件格式區(qū)分標(biāo)準(zhǔn)普通符號(hào)和小普通符號(hào)。
在這種情況下,連接器會(huì)為其他類型的普通符號(hào)使用一個(gè)不同的特殊節(jié)名。 在MIPS ELF的情況中, 連接器
為標(biāo)準(zhǔn)普通符號(hào)使用'COMMON',并且為小普通符號(hào)使用'.common'。這就允許你把不同類型的普通符號(hào)映射到
內(nèi)存的不同位置。

在一些老的連接腳本上,你有時(shí)會(huì)看到'[COMMON]'。這個(gè)符號(hào)現(xiàn)在已經(jīng)過時(shí)了, 它等效于'*(COMMON)'。

輸入節(jié)和垃圾收集
---------------------------------------

當(dāng)連接時(shí)垃圾收集正在使用中時(shí)('--gc-sections'),這在標(biāo)識(shí)那些不應(yīng)該被排除在外的節(jié)時(shí)非常有用。這
是通過在輸入節(jié)的通配符入口外面加上'KEEP()'實(shí)現(xiàn)的,比如'KEEP(*(.init))'或者'KEEP(SORT(*)(.sorts))
'。

輸入節(jié)示例
---------------------

接下來的例子是一個(gè)完整的連接腳本。它告訴連接器去讀取文件'all.o'中的所有節(jié),并把它們放到輸出節(jié)
'outputa'的開始位置處, 該輸出節(jié)是從位置'0x10000'處開始的。 從文件'foo.o'中來的所有節(jié)'.input1'
在同一個(gè)輸出節(jié)中緊密排列。 從文件'foo.o'中來的所有節(jié)'.input2'全部放入到輸出節(jié)'outputb'中,后面
跟上從'foo1.o'中來的節(jié)'.input1'。來自所有文件的所有余下的'.input1'和'.input2'節(jié)被寫入到輸出節(jié)
'outputc'中。

??? SECTIONS {
????? outputa 0x10000 :
??????? {
??????? all.o
??????? foo.o (.input1)
??????? }
????? outputb :
??????? {
??????? foo.o (.input2)
??????? foo1.o (.input1)
??????? }
????? outputc :
??????? {
??????? *(.input1)
??????? *(.input2)
??????? }
??? }
????
輸出節(jié)數(shù)據(jù)
-------------------

你可以通過使用輸出節(jié)命令'BYTE','SHORT','LONG','QUAD',或者'SQUAD'在輸出節(jié)中顯式包含幾個(gè)字節(jié)的數(shù)據(jù)
每一個(gè)關(guān)鍵字后面都跟上一個(gè)圓括號(hào)中的要存入的值。表達(dá)式的值被存在當(dāng)前的定位計(jì)數(shù)器的值處。

‘BYTE’,‘SHORT’,‘LONG’‘QUAD’命令分別存儲(chǔ)一個(gè),兩個(gè),四個(gè),八個(gè)字節(jié)。存入字節(jié)后,定位計(jì)
數(shù)器的值加上被存入的字節(jié)數(shù)。

比如,下面的命令會(huì)存入一字節(jié)的內(nèi)容1,后面跟上四字節(jié),其內(nèi)容是符號(hào)'addr'的值。

??? BYTE(1)
??? LONG(addr)

當(dāng)使用64位系統(tǒng)時(shí),‘QUAD’和‘SQUAD’是相同的;它們都會(huì)存儲(chǔ)8字節(jié),或者說是64位的值。而如果軟硬件
系統(tǒng)都是32位的,一個(gè)表達(dá)式就會(huì)被作為32位計(jì)算。在這種情況下,‘QUAD’存儲(chǔ)一個(gè)32位值,并把它零擴(kuò)展
到64位, 而‘SQUAD’會(huì)把32位值符號(hào)擴(kuò)展到64位。

如果輸出文件的目標(biāo)文件格式有一個(gè)顯式的endianness,它在正常的情況下,值就會(huì)被以這種endianness存儲(chǔ)
當(dāng)一個(gè)目標(biāo)文件格式?jīng)]有一個(gè)顯式的endianness時(shí), 值就會(huì)被以第一個(gè)輸入目標(biāo)文件的endianness存儲(chǔ)。

注意, 這些命令只在一個(gè)節(jié)描述內(nèi)部才有效,而不是在它們之間, 所以,下面的代碼會(huì)使連接器產(chǎn)生一個(gè)錯(cuò)
誤信息:

??? SECTIONS { .text : { *(.text) } LONG(1) .data : { *(.data) } }

而這個(gè)才是有效的:

??? SECTIONS { .text : { *(.text) ; LONG(1) } .data : { *(.data) } }

你可能使用‘FILL’命令來為當(dāng)前節(jié)設(shè)置填充樣式。它后面跟有一個(gè)括號(hào)中的表達(dá)式。任何未指定的節(jié)內(nèi)內(nèi)存
區(qū)域(比如,因?yàn)檩斎牍?jié)的對齊要求而造成的裂縫)會(huì)以這個(gè)表達(dá)式的值進(jìn)行填充。一個(gè)'FILL'語句會(huì)覆蓋到
它本身在節(jié)定義中出現(xiàn)的位置后面的所有內(nèi)存區(qū)域;通過引入多個(gè)‘FILL’語句,你可以在輸出節(jié)的不同位置
擁有不同的填充樣式。

這個(gè)例子顯示如何在未被指定的內(nèi)存區(qū)域填充'0x90':

??? FILL(0x90909090)

‘FILL’命令跟輸出節(jié)的‘=FILLEXP’屬性相似,但它只影響到節(jié)內(nèi)跟在‘FILL’命令后面的部分,而不是
整個(gè)節(jié)。如果兩個(gè)都用到了,那‘FILL’命令優(yōu)先。

輸出節(jié)關(guān)鍵字
-----------------------

有兩個(gè)關(guān)鍵字作為輸出節(jié)命令的形式出現(xiàn)。

`CREATE_OBJECT_SYMBOLS'
這個(gè)命令告訴連接器為每一個(gè)輸入文件創(chuàng)建一個(gè)符號(hào)。而符號(hào)的名字正好就是相關(guān)輸入文件的名字。
而每一個(gè)符號(hào)的節(jié)就是`CREATE_OBJECT_SYMBOLS'命令出現(xiàn)的那個(gè)節(jié)。

這個(gè)命令一直是a.out目標(biāo)文件格式特有的。 它一般不為其它的目標(biāo)文件格式所使用。

`CONSTRUCTORS'
當(dāng)使用a.out目標(biāo)文件格式進(jìn)行連接的時(shí)候, 連接器使用一組不常用的結(jié)構(gòu)以支持C++的全局構(gòu)造函
數(shù)和析構(gòu)函數(shù)。當(dāng)連接不支持專有節(jié)的目標(biāo)文件格式時(shí), 比如ECOFF和XCOFF,連接器會(huì)自動(dòng)辯識(shí)C++
全局構(gòu)造函數(shù)和析構(gòu)函數(shù)的名字。對于這些目標(biāo)文件格式,‘CONSTRUCTORS’命令告訴連接器把構(gòu)造
函數(shù)信息放到‘CONSTRUCTORS’命令出現(xiàn)的那個(gè)輸出節(jié)中。對于其它目標(biāo)文件格式,‘CONSTRUCTORS’
命令被忽略。

符號(hào)`__CTOR_LIST__'標(biāo)識(shí)全局構(gòu)造函數(shù)的開始,而符號(hào)`__DTOR_LIST'標(biāo)識(shí)結(jié)束。這個(gè)列表的第一個(gè)
WORD是入口的數(shù)量,緊跟在后面的是每一個(gè)構(gòu)造函數(shù)和析構(gòu)函數(shù)的地址,再然后是一個(gè)零WORD。編譯
器必須安排如何實(shí)際運(yùn)行代碼。對于這些目標(biāo)文件格式,GNU C++通常從一個(gè)`__main'子程序中調(diào)用
構(gòu)造函數(shù),而對`__main'的調(diào)用自動(dòng)被插入到`main'的啟動(dòng)代碼中。GNU C++通常使用'atexit'運(yùn)行
析構(gòu)函數(shù),或者直接從函數(shù)'exit'中運(yùn)行。

對于像‘COFF’或‘ELF’這樣支持專有節(jié)名的目標(biāo)文件格式,GNU C++通常會(huì)把全局構(gòu)造函數(shù)與析構(gòu)
函數(shù)的地址值放到'.ctors'和'.dtors'節(jié)中。把下面的代碼序列放到你的連接腳本中去,這樣會(huì)構(gòu)建
出GNU C++運(yùn)行時(shí)代碼希望見到的表類型。

????????????? __CTOR_LIST__ = .;
????????????? LONG((__CTOR_END__ - __CTOR_LIST__) / 4 - 2)
????????????? *(.ctors)
????????????? LONG(0)
????????????? __CTOR_END__ = .;
????????????? __DTOR_LIST__ = .;
????????????? LONG((__DTOR_END__ - __DTOR_LIST__) / 4 - 2)
????????????? *(.dtors)
????????????? LONG(0)
????????????? __DTOR_END__ = .;

如果你正使用GNU C++支持來進(jìn)行優(yōu)先初始化,那它提供一些可以控制全局構(gòu)造函數(shù)運(yùn)行順序的功能,
你必須在連接時(shí)給構(gòu)造函數(shù)排好序以保證它們以正確的順序被執(zhí)行。當(dāng)使用'CONSTRUCTORS'命令時(shí),
替代為`SORT(CONSTRUCTORS)'。當(dāng)使用'.ctors'和'dtors'節(jié)時(shí),使用`*(SORT(.ctors))'和
`*(SORT(.dtors))' 而不是`*(.ctors)'和`*(.dtors)'。

通常,編譯器和連接器會(huì)自動(dòng)處理這些事情,并且你不必親自關(guān)心這些事情。但是,當(dāng)你正在使用
C++,并自己編寫連接腳本時(shí),你可能就要考慮這些事情了。

輸出節(jié)的丟棄。
-------------------------

連接器不會(huì)創(chuàng)建那些不含有任何內(nèi)容的輸出節(jié)。這是為了引用那些可能出現(xiàn)或不出現(xiàn)在任何輸入文件中的輸入
節(jié)時(shí)方便。比如:

??? .foo { *(.foo) }

如果至少在一個(gè)輸入文件中有'.foo'節(jié),它才會(huì)在輸出文件中創(chuàng)建一個(gè)'.foo'節(jié)

如果你使用了其它的而不是一個(gè)輸入節(jié)描述作為一個(gè)輸出節(jié)命令,比如一個(gè)符號(hào)賦值,那這個(gè)輸出節(jié)總是被
創(chuàng)建,即使沒有匹配的輸入節(jié)也會(huì)被創(chuàng)建。

一個(gè)特殊的輸出節(jié)名`/DISCARD/'可以被用來丟棄輸入節(jié)。任何被分配到名為`/DISCARD/'的輸出節(jié)中的輸入
節(jié)不包含在輸出文件中。

輸出節(jié)屬性
-------------------------

上面,我們已經(jīng)展示了一個(gè)完整的輸出節(jié)描述,看下去就象這樣:

??? SECTION [ADDRESS] [(TYPE)] : [AT(LMA)]
????? {
??????? OUTPUT-SECTION-COMMAND
??????? OUTPUT-SECTION-COMMAND
??????? ...
????? } [>REGION] [AT>LMA_REGION] [:PHDR :PHDR ...] [=FILLEXP]

我們已經(jīng)介紹了SECTION, ADDRESS, 和OUTPUT-SECTION-COMMAND. 在這一節(jié)中,我們將介紹余下的節(jié)屬性。

輸出節(jié)類型
...................

每一個(gè)輸出節(jié)可以有一個(gè)類型。類型是一個(gè)放在括號(hào)中的關(guān)鍵字,已定義的類型如下所示:

`NOLOAD'
這個(gè)節(jié)應(yīng)當(dāng)被標(biāo)式詎不可載入,所以當(dāng)程序運(yùn)行時(shí),它不會(huì)被載入到內(nèi)存中。

`DSECT'
`COPY'
`INFO'
`OVERLAY'
支持這些類型名只是為了向下兼容,它們很少使用。它們都具有相同的效果:這個(gè)節(jié)應(yīng)當(dāng)被標(biāo)式詎不
可分配,所以當(dāng)程序運(yùn)行時(shí),沒有內(nèi)存為這個(gè)節(jié)分配。

連接器通常基于映射到輸出節(jié)的輸入節(jié)來設(shè)置輸出節(jié)的屬性。你可以通過使用節(jié)類型來重設(shè)這個(gè)屬性,
比如,在下面的腳本例子中,‘ROM’節(jié)被定址在內(nèi)存地址零處,并且在程序運(yùn)行時(shí)不需要被載入。
‘ROM’節(jié)的內(nèi)容會(huì)正常出現(xiàn)在連接輸出文件中。

??? SECTIONS {
????? ROM 0 (NOLOAD) : { ... }
????? ...
??? }

輸出節(jié)LMA
..................

每一個(gè)節(jié)有一個(gè)虛地址(VMA)和一個(gè)載入地址(LMA);出現(xiàn)在輸出節(jié)描述中的地址表達(dá)式設(shè)置VMS

連接器通常把LMA跟VMA設(shè)成相等。你可以通過使用‘AT’關(guān)鍵字改變這個(gè)。跟在關(guān)鍵字‘AT’后面的表達(dá)式
LMA指定節(jié)的載入地址。或者,通過`AT>LMA_REGION'表達(dá)式, 你可以為節(jié)的載入地址指定一個(gè)內(nèi)存區(qū)域。

這個(gè)特性是為了便于建立ROM映像而設(shè)計(jì)的。比如,下面的連接腳本創(chuàng)建了三個(gè)輸出節(jié):一個(gè)叫做‘.text’
從地址‘0x1000’處開始,一個(gè)叫‘.mdata’,盡管它的VMA是'0x2000',它會(huì)被載入到'.text'節(jié)的后面,最
后一個(gè)叫做‘.bss’是用來放置未初始化的數(shù)據(jù)的,其地址從'0x3000'處開始。符號(hào)'_data'被定義為值
'0x2000', 它表示定位計(jì)數(shù)器的值是VMA的值,而不是LMA。

??? SECTIONS
????? {
????? .text 0x1000 : { *(.text) _etext = . ; }
????? .mdata 0x2000 :
??????? AT ( ADDR (.text) + SIZEOF (.text) )
??????? { _data = . ; *(.data); _edata = . ;? }
????? .bss 0x3000 :
??????? { _bstart = . ;? *(.bss) *(COMMON) ; _bend = . ;}
??? }

這個(gè)連接腳本產(chǎn)生的程序使用的運(yùn)行時(shí)初始化代碼會(huì)包含象下面所示的一些東西,以把初始化后的數(shù)據(jù)從ROM
映像中拷貝到它的運(yùn)行時(shí)地址中去。注意這節(jié)代碼是如何利用好連接腳本定義的符號(hào)的。

??? extern char _etext, _data, _edata, _bstart, _bend;
??? char *src = &_etext;
??? char *dst = &_data;
????
??? /* ROM has data at end of text; copy it. */
??? while (dst < &_edata) {
????? *dst++ = *src++;
??? }
????
??? /* Zero bss */
??? for (dst = &_bstart; dst< &_bend; dst++)
????? *dst = 0;

輸出節(jié)區(qū)域
.....................

你可以通過使用`>REGION'把一個(gè)節(jié)賦給前面已經(jīng)定義的一個(gè)內(nèi)存區(qū)域。

這里有一個(gè)簡單的例子:

??? MEMORY { rom : ORIGIN = 0x1000, LENGTH = 0x1000 }
??? SECTIONS { ROM : { *(.text) } >rom }

輸出節(jié)Phdr
...................

你可以通過使用`:PHDR'把一個(gè)節(jié)賦給前面已定義的一個(gè)程序段。如果一個(gè)節(jié)被賦給一個(gè)或多個(gè)段,那后來分
配的節(jié)都會(huì)被賦給這些段,除非它們顯式使用了':PHDR'修飾符。你可以使用':NONE'來告訴連接器不要把節(jié)
放到任何一個(gè)段中。

這兒有一個(gè)簡單的例子:

??? PHDRS { text PT_LOAD ; }
??? SECTIONS { .text : { *(.text) } :text }

輸出段填充
...................

你可以通過使用'=FILLEXP'為整個(gè)節(jié)設(shè)置填充樣式。FILLEXP是一個(gè)表達(dá)式。任何沒有指定的輸出段內(nèi)的內(nèi)存
區(qū)域(比如,因?yàn)檩斎攵蔚膶R要求而產(chǎn)生的裂縫)會(huì)被填入這個(gè)值。如果填充表達(dá)式是一個(gè)簡單的十六進(jìn)制
值,比如,一個(gè)以'0x'開始的十六進(jìn)制數(shù)字組成的字符串,并且尾部不是'k'或'M',那一個(gè)任意的十六進(jìn)制數(shù)
字長序列可以被用來指定填充樣式;前導(dǎo)零也變?yōu)闃邮降囊徊糠帧τ谒衅渌那闆r,包含一個(gè)附加的括號(hào)
或一元操作符'+',那填充樣式是表達(dá)式的最低四字節(jié)的值。在所有的情況下,數(shù)值是big-endian.

你還可以通過在輸出節(jié)命令中使用'FILL'命令來改變填充值。

這里是一個(gè)簡單的例子:
??? SECTIONS { .text : { *(.text) } =0x90909090 }

覆蓋描述
-------------------

一個(gè)覆蓋描述提供一個(gè)簡單的描述辦法,以描述那些要被作為一個(gè)單獨(dú)內(nèi)存映像的一部分載入內(nèi)存,但是卻要
在同一內(nèi)存地址運(yùn)行的節(jié)。在運(yùn)行時(shí),一些覆蓋管理機(jī)制會(huì)把要被覆蓋的節(jié)按需要拷入或拷出運(yùn)行時(shí)內(nèi)存地址,
并且多半是通過簡單地處理內(nèi)存位。 這個(gè)方法可能非常有用,比如在一個(gè)特定的內(nèi)存區(qū)域比另一個(gè)快時(shí)。

覆蓋是通過‘OVERLAY’命令進(jìn)行描述。‘OVERLAY’命令在‘SECTIONS’命令中使用,就像輸出段描述一樣。
‘OVERLAY’命令的完整語法如下:

??? OVERLAY [START] : [NOCROSSREFS] [AT ( LDADDR )]
????? {
??????? SECNAME1
????????? {
??????????? OUTPUT-SECTION-COMMAND
??????????? OUTPUT-SECTION-COMMAND
??????????? ...
????????? } [:PHDR...] [=FILL]
??????? SECNAME2
????????? {
??????????? OUTPUT-SECTION-COMMAND
??????????? OUTPUT-SECTION-COMMAND
??????????? ...
????????? } [:PHDR...] [=FILL]
??????? ...
????? } [>REGION] [:PHDR...] [=FILL]

除了‘OVERLAY’關(guān)鍵字,所有的都是可選的,每一個(gè)節(jié)必須有一個(gè)名字(上面的SECNAME1和SECNAME2)。在
‘OVERLAY’結(jié)構(gòu)中的節(jié)定義跟通常的‘SECTIONS’結(jié)構(gòu)中的節(jié)定義是完全相同的,除了一點(diǎn),就是在‘OVERLAY’
中沒有地址跟內(nèi)存區(qū)域的定義。

節(jié)都被定義為同一個(gè)開始地址。所有節(jié)的載入地址都被排布,使它們在內(nèi)存中從整個(gè)'OVERLAY'的載入地址開
始都是連續(xù)的(就像普通的節(jié)定義,載入地址是可選的,缺省的就是開始地址;開始地址也是可選的,缺省的
是當(dāng)前的定位計(jì)數(shù)器的值。)

如果使用了關(guān)鍵字`NOCROSSREFS', 并且在節(jié)之間存在引用,連接器就會(huì)報(bào)告一個(gè)錯(cuò)誤。因?yàn)楣?jié)都運(yùn)行在同一
個(gè)地址上,所以一個(gè)節(jié)直接引用另一個(gè)節(jié)中的內(nèi)容是錯(cuò)誤的。

對于'OVERLAY'中的每一個(gè)節(jié),連接器自動(dòng)定義兩個(gè)符號(hào)。符號(hào)`__load_start_SECNAME'被定義為節(jié)的開始載
入地址。符號(hào)`__load_stop_SECNAME'被定義為節(jié)的最后載入地址。SECNAME中的不符合C規(guī)定的任何字符都將
被刪除。C(或者匯編語言)代碼可能使用這些符號(hào)在必要的時(shí)間搬移覆蓋代碼。

在覆蓋區(qū)域的最后,定位計(jì)數(shù)器的值被設(shè)為覆蓋區(qū)域的開始地址加上最大的節(jié)的長度。

這里是一個(gè)例子。記住這只會(huì)出現(xiàn)在‘SECTIONS’結(jié)構(gòu)的內(nèi)部。

????? OVERLAY 0x1000 : AT (0x4000)
????? {
??????? .text0 { o1/*.o(.text) }
??????? .text1 { o2/*.o(.text) }
????? }

這段代碼會(huì)定義'.text0'和'.text1',它們都從地址0x1000開始。‘.text0'會(huì)被載入到地址0x4000處,而
'.text1'會(huì)被載入到緊隨'.text0'后的位置。下面的幾個(gè)符號(hào)會(huì)被定義:`__load_start_text0',?
`__load_stop_text0', `__load_start_text1', `__load_stop_text1'.

拷貝'.text1'到覆蓋區(qū)域的C代碼看上去可能會(huì)像下面這樣:

????? extern char __load_start_text1, __load_stop_text1;
????? memcpy ((char *) 0x1000, &__load_start_text1,
????????????? &__load_stop_text1 - &__load_start_text1);

注意'OVERLAY'命令只是為了語法上的便利,因?yàn)樗龅乃惺虑槎伎梢杂酶踊镜拿罴右源妗I厦?br /> 的例子可以用下面的完全特效的寫法:

????? .text0 0x1000 : AT (0x4000) { o1/*.o(.text) }
????? __load_start_text0 = LOADADDR (.text0);
????? __load_stop_text0 = LOADADDR (.text0) + SIZEOF (.text0);
????? .text1 0x1000 : AT (0x4000 + SIZEOF (.text0)) { o2/*.o(.text) }
????? __load_start_text1 = LOADADDR (.text1);
????? __load_stop_text1 = LOADADDR (.text1) + SIZEOF (.text1);
????? . = 0x1000 + MAX (SIZEOF (.text0), SIZEOF (.text1));

?
?
??

============

連接器在缺省狀態(tài)下被配置為允許分配所有可用的內(nèi)存塊。你可以使用‘MEMORY’命令重新配置這個(gè)設(shè)置。

‘MEMORY’命令描述目標(biāo)平臺(tái)上內(nèi)存塊的位置與長度。你可以用它來描述哪些內(nèi)存區(qū)域可以被連接器使用,
哪些內(nèi)存區(qū)域是要避免使用的。然后你就可以把節(jié)分配到特定的內(nèi)存區(qū)域中。連接器會(huì)基于內(nèi)存區(qū)域設(shè)置節(jié)
的地址,對于太滿的區(qū)域,會(huì)提示警告信息。連接器不會(huì)為了適應(yīng)可用的區(qū)域而攪亂節(jié)。

一個(gè)連接腳本最多可以包含一次'MEMORY'命令。但是,你可以在命令中隨心所欲定義任意多的內(nèi)存塊,語法
如下:

??? MEMORY
????? {
??????? NAME [(ATTR)] : ORIGIN = ORIGIN, LENGTH = LEN
??????? ...
????? }

NAME是用在連接腳本中引用內(nèi)存區(qū)域的名字。出了連接腳本,區(qū)域名就沒有任何實(shí)際意義。區(qū)域名存儲(chǔ)在一個(gè)
單獨(dú)的名字空間中,它不會(huì)和符號(hào)名,文件名,節(jié)名產(chǎn)生沖突,每一塊內(nèi)存區(qū)域必須有一個(gè)唯一的名字。

ATTR字符串是一個(gè)可選的屬性列表,它指出是否為一個(gè)沒有在連接腳本中進(jìn)行顯式映射地輸入段使用一個(gè)特定
的內(nèi)存區(qū)域。如果你沒有為某些輸入段指定一個(gè)輸出段,連接器會(huì)創(chuàng)建一個(gè)跟輸入段同名的輸出段。如果你定
義了區(qū)域?qū)傩?#xff0c;連接器會(huì)使用它們來為它創(chuàng)建的輸出段選擇內(nèi)存區(qū)域。

ATTR字符串必須包含下面字符中的一個(gè),且必須只包含一個(gè):
`R'
只讀節(jié)。
`W'
??? 可讀寫節(jié)。
`X'
可執(zhí)行節(jié)。
`A'
可分配節(jié)。
`I'
已初始化節(jié)。
`L'
??? 同‘I’
`!'
對前一個(gè)屬性值取反。

如果一個(gè)未映射節(jié)匹配了上面除'!'之外的一個(gè)屬性,它就會(huì)被放入該內(nèi)存區(qū)域。'!'屬性對該測試取反,所以
只有當(dāng)它不匹配上面列出的行何屬性時(shí),一個(gè)未映射節(jié)才會(huì)被放入到內(nèi)存區(qū)域。

ORIGIN是一個(gè)關(guān)于內(nèi)存區(qū)域地始地址的表達(dá)式。在內(nèi)存分配執(zhí)行之前,這個(gè)表達(dá)式必須被求值產(chǎn)生一個(gè)常數(shù),
這意味著你不可以使用任何節(jié)相關(guān)的符號(hào)。關(guān)鍵字'ORIGIN'可以被縮寫為'org'或'o'(但是,不可以寫為,比
如‘ORG’)

LEN是一個(gè)關(guān)于內(nèi)存區(qū)域長充(以字節(jié)為單位)的表達(dá)式。就像ORIGIN表達(dá)式,這個(gè)表達(dá)式在分配執(zhí)行前也
必須被求得為一個(gè)常數(shù)值。關(guān)鍵字'LENGTH'可以被簡寫為‘len'或'l'。

在下面的例子中,我們指定兩個(gè)可用于分配的內(nèi)存區(qū)域:一個(gè)從0開始,有256kb長度,另一個(gè)從0x4000000
開始,有4mb長度。連接器會(huì)把那些沒有進(jìn)行顯式映射且是只讀或可執(zhí)行的節(jié)放到'rom'內(nèi)存區(qū)域。并會(huì)把另
外的沒有被顯式映射地節(jié)放入到'ram'內(nèi)存區(qū)域。

??? MEMORY
????? {
??????? rom (rx)? : ORIGIN = 0, LENGTH = 256K
??????? ram (!rx) : org = 0x40000000, l = 4M
????? }

一旦你定義了一個(gè)內(nèi)存區(qū)域,你也可以指示連接器把指定的輸出段放入到這個(gè)內(nèi)存區(qū)域中,這可以通過使用
'>REGION'輸出段屬性。比如,如果你有一個(gè)名為'mem'的內(nèi)存區(qū)域,你可以在輸出段定義中使用'>mem'。如
果沒有為輸出段指定地址,連接器就會(huì)把地址設(shè)置為內(nèi)存區(qū)域中的下一個(gè)可用的地址。如果總共的映射到一
個(gè)內(nèi)存區(qū)域的輸出段對于區(qū)域來說太大了,連接器會(huì)提示一條錯(cuò)誤信息。

PHDRS命令
=============

ELF目標(biāo)文件格式使用“程序頭”,它也就是人們熟知的“節(jié)”。程序頭描述了程序應(yīng)當(dāng)如何被載入到內(nèi)存中。
你可以通過使用帶有'-p'選項(xiàng)的‘objdump’命令來打印出這個(gè)程序頭。

當(dāng)你在一個(gè)純ELF系統(tǒng)上運(yùn)行ELF程序時(shí),系統(tǒng)的載入程序通過讀取文件頭來計(jì)算得到如何來載入這個(gè)文件。這
只在程序頭被正確設(shè)置的情況下才會(huì)正常工作。本手冊并不打算介紹系統(tǒng)載入程序如何解釋文件頭的相關(guān)細(xì)節(jié)
問題;關(guān)于更多信息,請參閱ELF ABI。

連接順在缺省狀態(tài)下會(huì)自己創(chuàng)建一個(gè)可用的程序頭。但是,在某些情況下,你可能需要更為精確地指定程序頭。
你可以使用命令‘PHDRS’達(dá)到這個(gè)目的。當(dāng)連接器在連接腳本中看到‘PHDRS’命令時(shí),它只會(huì)創(chuàng)建被指定了
的程序頭。

連接器只在產(chǎn)生ELF輸出文件時(shí)關(guān)心‘PHDRS’命令。在其它情況下,連接器只是簡單地忽略‘PHDRS’。

下面是‘PHDRS’命令的語法。單詞‘PHDRS’,‘FILEHDR’,‘AT’和‘FLAGS’都是關(guān)鍵字。

??? PHDRS
??? {
????? NAME TYPE [ FILEHDR ] [ PHDRS ] [ AT ( ADDRESS ) ]
??????????? [ FLAGS ( FLAGS ) ] ;
??? }

NAME只在連接腳本的‘SECTIONS’命令中引用時(shí)用到。它不會(huì)被放到輸出文件中。程序頭的名字會(huì)被存儲(chǔ)到單獨(dú)
的名字空間中。每一個(gè)程序頭都必須有一個(gè)唯一的名字。

某些特定類型的程序頭描述系統(tǒng)載入程序要從文件中載入到內(nèi)存的節(jié)。在連接腳本中,你通過把可載入的輸出節(jié)放
到段中來指定這些段的內(nèi)容。你可以使用‘:PHDR’輸出節(jié)屬性把一個(gè)節(jié)放到一個(gè)特定的段中。

把某些節(jié)放到多個(gè)段中也是正常的。這僅僅暗示了一個(gè)內(nèi)存段中含有另一個(gè)段。你可以重復(fù)使用‘:PHDR’,在每
一個(gè)應(yīng)當(dāng)含有這個(gè)節(jié)的段中使用它一次。

如果你使用‘:PHDR’把一個(gè)節(jié)放到多個(gè)段中,那連接器把隨后的所有沒有指定‘:PHDR’的可分配節(jié)都放到同一個(gè)
段中。這是為了方便,因?yàn)橥ǔR淮B續(xù)的節(jié)會(huì)被放到一個(gè)單獨(dú)的段中。你可以使用‘:NONE’來覆蓋缺省的段,
告訴連接器不要把節(jié)放到任何一個(gè)段中。

你可能在程序頭類型后面使用‘FILEHDR’和‘PHDRS’關(guān)鍵字來進(jìn)一步描述段的內(nèi)容。‘FILEHDR’關(guān)鍵字表示段應(yīng)
當(dāng)包含ELF文件頭。‘PHDRS’關(guān)鍵字表示段應(yīng)當(dāng)包含ELF程序頭本身。

TYPE可以是如下的一個(gè)。數(shù)字表示關(guān)鍵字的值。

`PT_NULL' (0)
表示一個(gè)不用的程序頭。

`PT_LOAD' (1)
表示這個(gè)程序頭描述了一個(gè)被從文件中載入的段。

`PT_DYNAMIC' (2)
??? 表示一個(gè)可以從中找到動(dòng)態(tài)鏈接信息的段。

`PT_INTERP' (3)
? 表示一個(gè)可以從中找到關(guān)于程序名解釋的段。

`PT_NOTE' (4)
表示一個(gè)存有備注信息的段。

`PT_SHLIB' (5)
? 一個(gè)保留的程序頭類型,被定義了,但沒有被ELF ABI指定。

`PT_PHDR' (6)
表示一個(gè)可以從中找到程序頭的段。

EXPRESSION
一個(gè)給出程序頭的數(shù)值類型的表達(dá)式。這可以在使用上面未定義的類型時(shí)使用。

你可以通過使用‘AT’表達(dá)式指定一個(gè)段應(yīng)當(dāng)被載入到內(nèi)存中的一個(gè)特定的地址。這跟
在輸出節(jié)屬性中使用‘AT’命令是完全一樣的。程序頭中的‘AT’命令會(huì)覆蓋輸出節(jié)屬
性中的。

連接器通常會(huì)基于組成段的節(jié)來設(shè)置段屬性。你可以通過使用‘FLAGS’關(guān)鍵字來顯式指
定段標(biāo)志。FLAGS的值必須是一個(gè)整型值。它被用來設(shè)置程序頭的‘p_flags'域。

這里是一個(gè)關(guān)于‘PHDRS’的例子。它展示一個(gè)在純ELF系統(tǒng)上的一個(gè)標(biāo)準(zhǔn)的程序頭設(shè)置。

??? PHDRS
??? {
????? headers PT_PHDR PHDRS ;
????? interp PT_INTERP ;
????? text PT_LOAD FILEHDR PHDRS ;
????? data PT_LOAD ;
????? dynamic PT_DYNAMIC ;
??? }
????
??? SECTIONS
??? {
????? . = SIZEOF_HEADERS;
????? .interp : { *(.interp) } :text :interp
????? .text : { *(.text) } :text
????? .rodata : { *(.rodata) } /* defaults to :text */
????? ...
????? . = . + 0x1000; /* move to a new page in memory */
????? .data : { *(.data) } :data
????? .dynamic : { *(.dynamic) } :data :dynamic
????? ...
??? }

VERSION命令
===============

在使用ELF時(shí),連接器支持符號(hào)版本。符號(hào)版本只在使用共享庫時(shí)有用。動(dòng)態(tài)連接器在運(yùn)行一個(gè)
可能跟一個(gè)更早版本的共享庫鏈接程序時(shí),可以使用符號(hào)版本來選擇一個(gè)函數(shù)的特定版本。

你可以直接在主連接腳本中包含一個(gè)版本腳本,或者你可以以一個(gè)隱式連接腳本的形式提供這個(gè)
版本腳本。你也可以使用‘--version-script'連接器選項(xiàng)。

‘VERSION’命令的語法很簡單:

??? VERSION { version-script-commands }

版本腳本命令的格式跟Sun在Solaris 2.5中的連接器的格式是完全一樣的。版本腳本定義一個(gè)版本
節(jié)點(diǎn)樹。你可以在版本腳本中指定節(jié)點(diǎn)名和依賴關(guān)系。你可以指定哪些符號(hào)被綁定到哪些版本節(jié)點(diǎn)
上,你還可以把一組指定的符號(hào)限定到本地范圍,這樣在共享庫的外面它們就不是全局可見的了。

最簡單的演示版本腳本語言的方法是出示幾個(gè)小例子:

??? VERS_1.1 {
??? global:
??? foo1;
??? local:
??? old*;
??? original*;
??? new*;
??? };
????
??? VERS_1.2 {
??? foo2;
??? } VERS_1.1;
????
??? VERS_2.0 {
??? bar1; bar2;
??? } VERS_1.2;

這個(gè)示例版本腳本定義了三個(gè)版本節(jié)點(diǎn)。第一個(gè)版本節(jié)點(diǎn)定義為‘VERS_1.1’它沒有其它的依賴。
腳本把符號(hào)‘foo1’綁定給‘VERS_1.1’。它把一些數(shù)量的符號(hào)限定到本地范圍,這樣它們在共
享庫的外面就不可見了;這是通過通配符來完成的,所以任何名字以‘old’,‘original’或
‘new’開頭的符號(hào)都會(huì)被匹配。可用的通配符跟在shell中匹配文件名時(shí)一樣。

下面,版本腳本定義一個(gè)節(jié)點(diǎn)‘VER_1.2’。這個(gè)節(jié)點(diǎn)依賴‘VER_1.1’。腳本把符號(hào)‘foo2’綁
定給節(jié)點(diǎn)‘VERS_1.2’。

最后,版本腳本定義節(jié)點(diǎn)‘VERS_2.0’。這個(gè)節(jié)點(diǎn)依賴‘VERS_1.2’。腳本把符號(hào)‘bar1’和
‘bar2 ’綁定給版本節(jié)點(diǎn)‘VERS_2.0’。

當(dāng)連接器發(fā)現(xiàn)一個(gè)定義在庫中的符號(hào)沒有被指定綁定到一個(gè)版本節(jié)點(diǎn),它會(huì)把它綁定到一個(gè)未指
定基礎(chǔ)版本的庫。你可以通過使用‘global: *;’把所有未指定的符號(hào)綁定到一個(gè)給定的版本節(jié)
點(diǎn)上。

版本節(jié)點(diǎn)的名字沒有任何特殊的含義只是為了方便人們閱讀。版本‘2.0’可以出現(xiàn)在‘1.1’和
‘1.2’之間。但是,在書寫版本腳本時(shí),這會(huì)是一個(gè)引起混亂的辦法。

如果在版本腳本中,這是一個(gè)唯一的版本節(jié)點(diǎn),節(jié)點(diǎn)名可以被省略。這樣的版本腳本不給符號(hào)賦
任何版本,只是選擇哪些符號(hào)會(huì)被全局可見而哪些不會(huì)。

??? { global: foo; bar; local: *; };

當(dāng)你把一個(gè)程序跟一個(gè)帶有版本符號(hào)的共享庫連接時(shí),程序自身知道每個(gè)符號(hào)的哪個(gè)版本是它需
要的,而且它還知道它連接的每一個(gè)節(jié)享庫中哪些版本的節(jié)點(diǎn)是它需要的。這樣,在運(yùn)行時(shí),動(dòng)
態(tài)載入程序可以做一個(gè)快速的確認(rèn),以保證你連接的庫確實(shí)提供了所有的程序需要用來解析所有
動(dòng)態(tài)符號(hào)的版本節(jié)點(diǎn)。用這種方法,就有可能讓每一個(gè)動(dòng)態(tài)連接器知道所有的外部符號(hào)不需要通
過搜索每一個(gè)符號(hào)引用就能解析。

符號(hào)版本在SunOS上做次版本確認(rèn)是一種很成熟的方法。一個(gè)被提出來的基本的問題是對于外部
函數(shù)的標(biāo)準(zhǔn)引用會(huì)在需要時(shí)被綁定到正確的版本,但不是在程序啟動(dòng)的時(shí)候全部被綁定。如果一
個(gè)共享庫過期了,一個(gè)需要的界面可能就不存在了;當(dāng)程序需要使用這個(gè)界面的時(shí)候,它可能會(huì)
突然地意外失敗。有了符號(hào)版本后,當(dāng)用戶啟動(dòng)他們的程序時(shí),如果要使用的共享庫太老了的話,
用戶會(huì)得到一條警告信息。

GNU對Sun的版本確認(rèn)辦法有一些擴(kuò)展。首先就是能在符號(hào)定義的源文件中把一個(gè)符號(hào)綁定到一個(gè)
版本節(jié)點(diǎn)而不是在一個(gè)版本腳本中。這主要是為了減輕庫維護(hù)的工作量。你可以通過類似下面的
代碼實(shí)現(xiàn)這一點(diǎn):

??? __asm__(".symver original_foo,foo@VERS_1.1");

在C源文件中。這句會(huì)給函數(shù)'original_foo'取一個(gè)別名'foo',并綁定到版本節(jié)點(diǎn)`VERS_1.1'。
操作符'local:'可以被用來阻止符號(hào)'original_foo'被導(dǎo)出。操作符'.symver'使這句優(yōu)先于版
本腳本。

第二個(gè)GNU的擴(kuò)展是在一個(gè)給定的共享庫中允許同一個(gè)函數(shù)的多個(gè)版本。通過這種辦法,你可以
不增加共享庫的主版本號(hào)而對界面做完全不相容的修改。

要實(shí)現(xiàn)這個(gè),你必須在一個(gè)源文件中多次使用'.symver'操作符。這里是一個(gè)例子:

??? __asm__(".symver original_foo,foo@");
??? __asm__(".symver old_foo,foo@VERS_1.1");
??? __asm__(".symver old_foo1,foo@VERS_1.2");
??? __asm__(".symver new_foo,foo@@VERS_2.0");

在這個(gè)例子中,'foo@'表示把符號(hào)'foo'綁定到一個(gè)沒有指基版本的符號(hào)上。含有這個(gè)例子的源
文件必須定義4個(gè)C函數(shù):`original_foo', `old_foo', `old_foo1', 和`new_foo'.

當(dāng)你有一個(gè)給定符號(hào)的多個(gè)定義后,有必要有一個(gè)方法可以指定一個(gè)缺省的版本,對于這個(gè)符號(hào)
的外部引用就可以找到這個(gè)版本。用這種方法,你可以只聲明一個(gè)符號(hào)的一個(gè)版本作為缺省版本,
否則,你會(huì)擁有同一個(gè)符號(hào)的多個(gè)定義。

如果你想要綁定一個(gè)引用到共享庫中的符號(hào)的一個(gè)指定的版本,你可以很方便地使用別名(比如,
old_foo),或者你可以使用'.symver'操作符來指定綁定到一個(gè)外部函數(shù)的特定版本。

你也可以在版本腳本中指定語言。

??? VERSION extern "lang" { version-script-commands }

被支持的'lang'有‘C’,‘C++’和‘Java’。

連接腳本中的表達(dá)式
=============================

連接腳本語言中的表達(dá)式的語法跟C的表達(dá)式是完全是致的。所有的表達(dá)式都以整型值被求值。所有
的表達(dá)式也被以相同的寬度求值。在32位系統(tǒng)是它是32位,否則是64位。
    
你可以在表達(dá)式中使用和設(shè)置符號(hào)值。

連接器為了使用表達(dá)式,定義了幾個(gè)具有特殊途的內(nèi)建函數(shù)。

常數(shù)
---------

所有的常數(shù)都是整型值。

就像在C中,連接器把以'0'開頭的整型數(shù)視為八進(jìn)制數(shù),把以'0x'或'0X'開頭的視為十六進(jìn)制。連接器
把其它的整型數(shù)視為十進(jìn)制。

另外,你可以使用'K'和'M'后綴作為常數(shù)的度量單位,分別為'1024'和'1024*1024'。比如,下面的三個(gè)
常數(shù)表示同一個(gè)值。

??? _fourk_1 = 4K;
??? _fourk_2 = 4096;
??? _fourk_3 = 0x1000;

符號(hào)名
------------

除了引用,符號(hào)名都是以一個(gè)字母,下劃線或者句號(hào)開始,可以包含字母,數(shù)字,下劃線,句點(diǎn)和連接號(hào)。
不是被引用的符號(hào)名必須不和任何關(guān)鍵字沖突。你可以指定一個(gè)含有不固定它符數(shù)或具有跟關(guān)鍵字相同名
字但符號(hào)名必須在雙引號(hào)內(nèi):

??? "SECTION" = 9;
??? "with a space" = "also with a space" + 10;

因?yàn)榉?hào)可以含有很多非文字字符,所以以空格分隔符號(hào)是很安全的。比如,'A-B'是一個(gè)符號(hào),而'A - B'
是一個(gè)執(zhí)行減法運(yùn)算的表達(dá)式。

定位計(jì)數(shù)器
--------------------

一個(gè)特殊的連接器變量"dot"'.'總是含有當(dāng)前的輸出定位計(jì)數(shù)器。因?yàn)?#39;.'總引用輸出段中的一個(gè)位置,它
只可以出現(xiàn)在'SECTIONS'命令中的表達(dá)式中。'.'符號(hào)可以出現(xiàn)在表達(dá)式中一個(gè)普能符號(hào)允許出現(xiàn)的任何位
置。

把一個(gè)值賦給'.'會(huì)讓定位計(jì)數(shù)器產(chǎn)生移動(dòng)。這會(huì)在輸出段中產(chǎn)生空洞。定位計(jì)數(shù)器從不向前移動(dòng)。

??? SECTIONS
??? {
????? output :
??????? {
????????? file1(.text)
????????? . = . + 1000;
????????? file2(.text)
????????? . += 1000;
????????? file3(.text)
??????? } = 0x12345678;
??? }

在前面的例子中,來自'file1'的'.text'節(jié)被定位在輸出節(jié)'output'的起始位置。它后面跟有1000byte的
空隙。然后是來自'file2'的'.text'節(jié),同樣是后面跟有1000byte的空隙,最后是來自'file3'的'.text'
節(jié)。符號(hào)'=0x12345678'指定在空隙中填入什么樣的數(shù)據(jù)。

注意:'.'實(shí)際上引用的是當(dāng)前包含目標(biāo)的從開始處的字節(jié)偏移。通常,它就是'SECTIONS'語句,其起始地
址是0,因?yàn)?#39;.'可以被用作絕對地址。但是如果'.'被用在一個(gè)節(jié)描述中,它引用的是從這個(gè)節(jié)起始處開始
的偏移,而不是一個(gè)絕對地址。這樣,在下面這樣一個(gè)腳本中:

??? SECTIONS
??? {
??????? . = 0x100
??????? .text: {
????????? *(.text)
????????? . = 0x200
??????? }
??????? . = 0x500
??????? .data: {
????????? *(.data)
????????? . += 0x600
??????? }
??? }

'.text'節(jié)被賦于起始地址0x100,盡管在'.text'輸入節(jié)中沒有足夠的數(shù)據(jù)來填充這個(gè)區(qū)域,但其長
度還是0x200bytes。(如果數(shù)據(jù)太多,那會(huì)產(chǎn)生一條錯(cuò)誤信息,因?yàn)檫@會(huì)試圖把'.'向前移)。'.data'
節(jié)會(huì)從0x500處開始,并且它在結(jié)尾處還會(huì)有0x600的額外空間。

運(yùn)算符
---------

連接器可以識(shí)別標(biāo)準(zhǔn)的C的算術(shù)運(yùn)算符集, 以及它們的優(yōu)先集.

??? 優(yōu)先集??????? 結(jié)合性????????? 運(yùn)算符????????????????? 備注
??? (highest)
??? 1????????????? left??????????? !? -? ~????????????????? (1)
??? 2????????????? left??????????? *? /? %
??? 3????????????? left??????????? +? -
??? 4????????????? left??????????? >>? <<
??? 5????????????? left??????????? ==? !=? >? <? <=? >=
??? 6????????????? left??????????? &
??? 7????????????? left??????????? |
??? 8????????????? left??????????? &&
??? 9????????????? left??????????? ||
??? 10????????????? right????????? ? :
??? 11????????????? right????????? &=? +=? -=? *=? /=????? (2)
??? (lowest)
? 注: (1) 前綴運(yùn)算符 (2) *Note Assignments::.

求值
----------

連接器是懶惰求表達(dá)式的值。它只在確實(shí)需要的時(shí)候去求一個(gè)表達(dá)式的值。

連接器需要一些信息,比如第一個(gè)節(jié)的起始地址的值,還有內(nèi)存區(qū)域的起點(diǎn)與長度,在做任何連接的
時(shí)候這都需要。在連接器讀取連接腳本的時(shí)候,這些值在可能的時(shí)候被計(jì)算出來。

但是,其它的值(比如符號(hào)的值)直到內(nèi)存被分配之后才會(huì)知道或需要。這樣的值直到其它信息(比
如輸出節(jié)的長度)可以被用來進(jìn)行符號(hào)賦值的時(shí)候才被計(jì)算出來。

直到內(nèi)存分配之后,節(jié)的長度才會(huì)被知道,所以依賴于節(jié)長度的賦值只能到內(nèi)存分配之后才會(huì)被執(zhí)行。

有些表達(dá)式,比如那些依賴于定位計(jì)數(shù)器'.'的表達(dá)式,必須在節(jié)分配的過程中被計(jì)算出來。

如果一個(gè)表達(dá)式的結(jié)果現(xiàn)在被需要,但是目前得不到這個(gè)值,這樣會(huì)導(dǎo)致一個(gè)錯(cuò)誤。比如,象下面這
樣一個(gè)腳本:

??? SECTIONS
????? {
??????? .text 9+this_isnt_constant :
????????? { *(.text) }
????? }

會(huì)產(chǎn)生一個(gè)錯(cuò)誤信息'non constant expression for initial address'.

表達(dá)式的節(jié)
----------------------------

當(dāng)一個(gè)連接器計(jì)算一個(gè)表達(dá)式時(shí),得到的結(jié)果可能是一個(gè)絕對值,也可能跟某個(gè)節(jié)相關(guān)。一個(gè)節(jié)相關(guān)的
表達(dá)式是從一個(gè)節(jié)的基地址開始的固定的偏稱值。

表達(dá)式在連接腳本中的位置決定了它是絕對的或節(jié)相關(guān)的。一個(gè)出現(xiàn)在輸出節(jié)定義中的表達(dá)式是跟輸出
節(jié)的基地址相關(guān)的。一個(gè)出現(xiàn)在其它地方的表達(dá)式則是絕對的。

如果你通過'-r'選項(xiàng)指定需要可重位輸出,那一個(gè)被賦為節(jié)相關(guān)的表達(dá)式的符號(hào)就會(huì)是可重定位的。意
思是下一步的連接操作會(huì)改變這個(gè)符號(hào)的值。符號(hào)的節(jié)就是節(jié)相關(guān)的表達(dá)式所在的節(jié)。

一個(gè)被賦為絕對表達(dá)式的符號(hào)在后面進(jìn)一步的連接操作中會(huì)始終保持它的值不變。符號(hào)會(huì)是絕對的,并
不會(huì)有任何的特定的相關(guān)節(jié)。

如果一個(gè)表達(dá)式有可能會(huì)是節(jié)相關(guān)的,你可以使用內(nèi)建函數(shù)'ABSOLUTE'強(qiáng)制一個(gè)表達(dá)式為絕對的。比如,
要?jiǎng)?chuàng)建一個(gè)被賦為輸出節(jié)'.data'的末尾地址的絕對符號(hào):

??? SECTIONS
????? {
??????? .data : { *(.data) _edata = ABSOLUTE(.); }
????? }

如果沒有使用'ABSOLUTE','_edata'會(huì)跟節(jié)'.data'相關(guān)。

內(nèi)建函數(shù)
-----------------

為了使用連接腳本表達(dá)式,連接腳本語言含有一些內(nèi)建函數(shù)。

`ABSOLUTE(EXP)'
返回表達(dá)式EXP的絕對值(不可重定位,而不是非負(fù))。主要在把一個(gè)絕對值賦給一個(gè)節(jié)定義內(nèi)的
符號(hào)時(shí)有用。

`ADDR(SECTION)'
返回節(jié)SECTION的絕對地址(VMA)。你的腳本之前必須已經(jīng)定義了這個(gè)節(jié)的地址。在接下來的例子
中,'symbol_1'和'symbol_2'被賦以相同的值。

??????? SECTIONS { ...
????????? .output1 :
??????????? {
??????????? start_of_output_1 = ABSOLUTE(.);
??????????? ...
??????????? }
????????? .output :
??????????? {
??????????? symbol_1 = ADDR(.output1);
??????????? symbol_2 = start_of_output_1;
??????????? }
??????? ... }

`ALIGN(EXP)'
返回定位計(jì)數(shù)器'.'對齊到下一個(gè)EXP指定的邊界后的值。‘ALIGN’不改變定位計(jì)數(shù)器的值,它只是
在定位計(jì)數(shù)器上面作了一個(gè)算術(shù)運(yùn)算。這里有一個(gè)例子,它在前面的節(jié)之后,把輸出節(jié)'.data'對齊
到下一個(gè)'0x2000'字節(jié)的邊界,并在輸入節(jié)之后把節(jié)內(nèi)的一個(gè)變量對齊到下一個(gè)'0x8000'字節(jié)的邊界。

??????? SECTIONS { ...
????????? .data ALIGN(0x2000): {
??????????? *(.data)
??????????? variable = ALIGN(0x8000);
????????? }
??????? ... }

這個(gè)例子中前一個(gè)'ALIGN'指定一個(gè)節(jié)的位置,因?yàn)樗亲鳛楣?jié)定義的可選項(xiàng)ADDRESS屬性出現(xiàn)的。第
二個(gè)‘ALIGN’被用來定義一個(gè)符號(hào)的值。

內(nèi)建函數(shù)'NEXT'跟‘ALIGN’非常相似。

`BLOCK(EXP)'
這是'ALIGN'的同義詞,是為了與其它的連接器保持兼容。這在設(shè)置輸出節(jié)的地址時(shí)非常有用。

`DATA_SEGMENT_ALIGN(MAXPAGESIZE, COMMONPAGESIZE)'
??? 這跟下面的兩個(gè)表達(dá)同義:
??????? (ALIGN(MAXPAGESIZE) + (. & (MAXPAGESIZE - 1)))
??? 或者:
??????? (ALIGN(MAXPAGESIZE) + (. & (MAXPAGESIZE - COMMONPAGESIZE)))

隱式連接腳本
=======================

如果你指定了一個(gè)連接器輸出文件,而連接器不能識(shí)別它是一個(gè)目標(biāo)文件還是檔案文件,它會(huì)試圖把它讀作
一個(gè)連接腳本。如果這個(gè)文件不能作為一個(gè)連接腳本被分析,連接器就會(huì)報(bào)告一個(gè)錯(cuò)誤。

一個(gè)隱式的連接器腳本不會(huì)替代缺省的連接器腳本。

一般,一個(gè)隱式的連接器腳本只包含符號(hào)賦值,或者'INPUT','GROUP'或'VERSION'命令。

BFD
***

連接器通過BFD庫來對目標(biāo)文件和檔案文件進(jìn)行操作。這些庫允許連接器忽略目標(biāo)文件的格式而使用相關(guān)的
例程來操作目標(biāo)文件。只要簡單地創(chuàng)建一個(gè)新的BFD后臺(tái)并把它加到庫中,一個(gè)不同的目標(biāo)文件格式就會(huì)被
支持。但是為了節(jié)約運(yùn)行時(shí)內(nèi)存,連接器和相關(guān)的工具一般被配置為只支持可用的目標(biāo)文件格式的一個(gè)子集,
你可以使用'objdump -i'來列出你配置的所有支持的格式。

就像大多數(shù)的案例,BFD是一個(gè)在多種相互有沖突的需求之間的一個(gè)折中,影響B(tài)FD設(shè)計(jì)的一個(gè)最主要的因
素是效率。因?yàn)锽FD簡化了程序和后臺(tái),更多的時(shí)間和精力被放在了優(yōu)化算法以追求更快的速度。

BFD解決方案的一個(gè)副產(chǎn)品是你必須記住有信息丟失的潛在可能。在使用BFD機(jī)制時(shí),有兩處地方有用信息可
能丟失:在轉(zhuǎn)化時(shí)和在輸出時(shí)。

它如何工作: BFD概要。
===============================

當(dāng)一個(gè)目標(biāo)文件被打開時(shí),BFD子程序自動(dòng)確定輸入目標(biāo)文件的格式。然后它們在內(nèi)存中用指向子程序的指針
構(gòu)建一個(gè)描述符,這個(gè)描述符被用作存取目標(biāo)文件的數(shù)據(jù)結(jié)構(gòu)元素。

因?yàn)樾枰獊碜阅繕?biāo)文件的不同信息,BFD從文件的不同節(jié)中讀取它們,并處理。比如,連接器的一個(gè)非常普遍
的操作是處理符號(hào)表。每一個(gè)BFD后臺(tái)提供一個(gè)在目標(biāo)文件的符號(hào)表達(dá)形式跟內(nèi)部規(guī)范格式之間的轉(zhuǎn)化的函數(shù),
當(dāng)一個(gè)連接器需要一個(gè)目標(biāo)文件的符號(hào)表時(shí),它通過一個(gè)內(nèi)存指針調(diào)用一個(gè)來自相應(yīng)的BFD后臺(tái)的子程序,這
個(gè)子程序讀取表并把它轉(zhuǎn)化為規(guī)范表。然后,連接器寫輸出文件的符號(hào)表,另一個(gè)BFD后臺(tái)子程序被調(diào)用,以
創(chuàng)建新的符號(hào)表并把它轉(zhuǎn)化為選定的輸出格式。

信息丟失。
----------------

在輸出的過程中,信息可能會(huì)被丟失。BFD支持的輸出格式并不提供一致的特性,并且在某一種格式中可以被
描述的信息可能在另一種格式中沒有地方可放。一個(gè)例子是在'b.out'中的對齊信息,在一個(gè)'a.out'格式的
文件中,沒有地方可以存儲(chǔ)對齊信息,所以當(dāng)一個(gè)文件是從'b.out'連接而成的,并產(chǎn)生的是一個(gè)'a.out'的
文件,對齊信息就不會(huì)被傳入到輸出文件中(連接器還是在內(nèi)部使用對齊信息,所以連接器的執(zhí)行還是正確的)

另一個(gè)例子是COFF節(jié)名字。COFF文件中可以含有不限數(shù)量的節(jié),每一個(gè)都有一個(gè)文字的節(jié)名。如果連接的目標(biāo)是
一種不支持過多節(jié)的格式(比如,'a.out')或者是一種不含有節(jié)名的格式(比如,Oasys格式),連接器不
能像通常那樣簡單地處理它。你可以通過把所需的輸入輸出節(jié)通過連接腳本語言進(jìn)行詳細(xì)映射來解決這下問題。

在規(guī)范化的過程中信息也會(huì)丟失。BFD內(nèi)部的對應(yīng)于外部格式的規(guī)范形式并不是完全詳盡的;有些在輸入格式
中的結(jié)構(gòu)在內(nèi)部并沒有對應(yīng)的表示方法。這意味著BFD后臺(tái)在從外部到內(nèi)部或從內(nèi)部到外部的轉(zhuǎn)化過程中不能
維護(hù)所有可能的數(shù)據(jù)。

這個(gè)限制只在一個(gè)程序讀取一種格式并寫成另一種格式的時(shí)候會(huì)是一個(gè)問題。每一個(gè)BFD后臺(tái)有責(zé)任維護(hù)盡可能
多的數(shù)據(jù),內(nèi)部的BFD規(guī)范格式具有對BFD內(nèi)核不透明的結(jié)構(gòu)體,只導(dǎo)出給后臺(tái)。當(dāng)一個(gè)文件以一種格式讀取后,
規(guī)范格式就會(huì)為之產(chǎn)生。同時(shí),后臺(tái)把所有可能丟失的信息進(jìn)行存儲(chǔ)。如果這些數(shù)據(jù)隨后會(huì)寫以相同的格式寫
回,后臺(tái)程序就可以使用BFD內(nèi)核提供的跟選前準(zhǔn)備的相同的規(guī)范格式。因?yàn)樵诤笈_(tái)之間有大量相同的東西,在
把big endianCOFF拷貝成littile endian COFF時(shí),或者'a.out'到'b.out'時(shí),不會(huì)有信息丟失。當(dāng)一些混合格
式被連接到一起時(shí),只有那些格式跟目標(biāo)格式不同的文件會(huì)丟失信息。?
?


對于.lds 文件,它定義了整個(gè)程序編譯之后的連接過程,決定了一個(gè)可執(zhí)行程序的各個(gè)段的存儲(chǔ)位置。

GNU官方網(wǎng)站上對.lds 文件形式的完整描述:

SECTIONS {

...

secname? start BLOCK(align) (NOLOAD) : AT (? ldadr )

? {? contents } >region :phdr =fill

...

}

secname和contents是必須的,其他的都是可選的。

下面挑幾個(gè)常用的看看:

1.secname:段名

2.contents:決定哪些內(nèi)容放在本段,可以是整個(gè)目標(biāo)文件,也可以是目標(biāo)文件中的某段(代碼段、數(shù)據(jù)段等)

3. start:本段連接(運(yùn)行)的地址,如果沒有使用 AT(ldadr),本段存儲(chǔ)的地址也是start。

????????????? GNU網(wǎng)站上說start 可以用任意一種描述地址的符號(hào)來描述。

4. AT(ldadr):定義本段存儲(chǔ)(加載)的地址。

看一個(gè)簡單的例子:

/* nand.lds */

SECTIONS {

firtst 0x00000000 : { head.o init.o }

second 0x30000000 :?AT(4096)?{ main.o }

}

?從上可知:

head.o放在 0x00000000地址開始處,init.o放在 head.o 后面,他們的運(yùn)行地址也是0x00000000,即連接和存儲(chǔ)地址相同(沒有AT 指定);

main.o放在4096(0x1000,是AT 指定的,存儲(chǔ)地址)開始處,但是它的運(yùn)行地址在0x30000000,運(yùn)行之前需要從0x1000(加載處)復(fù)制到

???????? 0x30000000(運(yùn)行處)

此過程也就需要讀取flash,把程序拷貝到相應(yīng)位置才能運(yùn)行。這就是存儲(chǔ)地址和運(yùn) 行的不同,稱為加載時(shí)域和運(yùn)行時(shí)域可以再.lds連接腳本文件中分別制定。 編寫好的.lds文件,在用arm-linux-ld連接命令時(shí)帶-Tfilename來調(diào)用執(zhí)行,如 arm-linux-ld? -Tnand.lds x.o y.o -o xy.o。也可用-Ttext參數(shù)直接指定連接地址如: arm-linux-ld -Ttext 0x30000000 x.o y.o -o xy.o 既然程序有了 兩種地址,就涉及到一些跳轉(zhuǎn)指令的區(qū)別。 下面1、2這一部分可以看韋東山的書,看看具體如何跳轉(zhuǎn)的 1)b? step: b跳轉(zhuǎn)指令是相對跳轉(zhuǎn),依賴當(dāng)前PC的值,偏移量是通過該指令本身的bit[23:0]算出來的,這使得使用b指令的不依賴于要跳到的代碼的位置,只看指令本身。 2)ldr pc,=step;該指令是一個(gè)偽指令編譯后會(huì)生成以下代碼: ldr pc,0x30008000 <0x30008000> step 是從內(nèi)存中的某個(gè)位置(step)讀出數(shù)據(jù)并賦給PC,同樣依賴于當(dāng)前PC的值,但是偏移量是step的連接地址(運(yùn)行時(shí)的地址),所以可以用它實(shí)現(xiàn)從Flash到RAM的程序跳轉(zhuǎn)。 (3)此外,有必要回味一下adr偽指令,U-boot中那段relocate代碼就是通過adr實(shí)現(xiàn)當(dāng)前程序是在RAM中還是在Flash中: relocate:??????????????????????? /*把u-boot重新定位到RAM*/ adr? r0,_start????? r0是代碼的當(dāng)前位置 adr指令:ADR偽指令---?小范圍的地址讀取?

???? ADR偽指令將基于PC相對偏移的地址值或基于寄存器相對偏移的地址值讀取到寄存器中。在匯編編譯器編譯源程序時(shí),ADR偽指令被編譯器替換成一條合適的 指令。通常,編譯器用一條ADD指令或SUB指令來實(shí)現(xiàn)該ADR偽指令的功能,若不能用一條指令實(shí)現(xiàn),則產(chǎn)生錯(cuò)誤,編譯失敗。

?

ADR偽指令格式 :ADR{cond}???register, expr

地址表達(dá)式expr的取值范圍:

??? 當(dāng)?shù)刂分凳亲止?jié)對齊時(shí),其取指范圍為:?+255??~?255B;

??? 當(dāng)?shù)刂分凳亲謱R時(shí),其取指范圍為:???-1020?~?1020B;

是根據(jù)pc地址與相對偏移地址計(jì)算出來的,例如:現(xiàn)在是在片內(nèi)ram中執(zhí)行時(shí),這時(shí)的r0的值為0x00000000,如果被從flash中l(wèi)oad到外置的sdram(假設(shè)便宜地址為0x3000 0000)中,則此時(shí)r0的值為0x3000 0000

/*adr偽指令,匯編器會(huì)自動(dòng)通過當(dāng)前PC的值得算出這條指令中“_start" 的值,執(zhí)行到start時(shí)PC的值放到r0中: 當(dāng)此段在flash中執(zhí)行時(shí) r0=_start=0;當(dāng)此段在RAM中執(zhí)行時(shí)_start=_TEXT_BASE(在board/smdk2410/config.mk)中指定的值 為0x33F80000,即U-Boot在把代碼拷貝到RAM中去執(zhí)行的代碼段的開始*/ ldr? r1,_TEXT_BASE? /*測試判斷從Flash啟動(dòng),還是從RAM啟動(dòng)此句執(zhí)行的結(jié)果r1始終是0x33FF80000,因?yàn)榇酥凳擎溄又付ǖ?/ cmp r0,r1???? /*比較r0和r1,調(diào)試的時(shí)候不要執(zhí)行重定位*/ OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm");指定輸出可執(zhí)行文件elf格式,32位ARM指令,小端模式
/*OUTPUT_FORMAT("elf32-arm", "elf32-arm", "elf32-arm")*/
OUTPUT_ARCH(arm) 指定輸入平臺(tái)為ARM
ENTRY(_start) 指定輸出可執(zhí)行文件的起始代碼為_start
SECTIONS
{
?. = 0x00000000;?? 定位當(dāng)前地址為0地址 . = ALIGN(4);??????? 代碼以四字節(jié)對齊
?.text????? :?????????????? 指定代碼段
?{
?? cpu/arm920t/start.o (.text)? 代碼的第一個(gè)代碼段
?? *(.text)???????? 其他代碼段
?} . = ALIGN(4);
?.rodata : { *(.rodata) }? 指定只讀數(shù)據(jù)段 . = ALIGN(4);
?.data : { *(.data) } 指定讀寫數(shù)據(jù)段 . = ALIGN(4);
?.got : { *(.got) } 指定got段,got段式是uboot自定義的一個(gè)段,非標(biāo)準(zhǔn)段
?__u_boot_cmd_start = .;其賦值為當(dāng)前位置,即起始位置
?.u_boot_cmd : { *(.u_boot_cmd) } u_boot_cmd段,Uboot把所有的uboot命令放在該段。
?__u_boot_cmd_end = .;把其賦值為當(dāng)前位置,即結(jié)束位置 . = ALIGN(4);
?__bss_start = .;? 把__bss_start賦值為當(dāng)前位置,即bss段的開始位置
?.bss : { *(.bss) }? ;指定bss段
?_end = .;;把_end賦值為當(dāng)前位置,即bss段的結(jié)束位置
}

總結(jié)

以上是生活随笔為你收集整理的u-boot.lds文件详解的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。