連接腳本
**************
連接腳本的一個主要目的是描述輸入文件中的節如何被映射到輸出文件中,並控制輸出文件的內存排布. 幾乎所有的連接腳本只做這兩件事情. 但是,在需要的時候,連接器腳本還可以指示連接器執行很多其他的操作.這通過下面描述的命令實現.
連接器總是使用連接器腳本的.如果你自己不提供, 連接器會使用一個缺省的腳本,這個腳本是被編譯進連接器可執行文件的. 你可以使用'--verbose'命令行選項來顯示缺省的連接器腳本的內容. 某些命令行選項,比如
'-r'或'-N', 會影響缺省的連接腳本.
你可以過使用'-T'命令行選項來提供你自己的連接腳本. 當你這么做的時候, 你的連接腳本會替換缺省的連接腳本.
你也可以通過把連接腳本作為一個連接器的輸入文件來隱式地使用它,就象它們是一個被連接的文件一樣.
基本的連接腳本的概念
============================
我們需要定義一些基本的概念與詞匯以描述連接腳本語言.
連接器把多個輸入文件合並成單個輸出文件. 輸出文件和輸入文件都以一種叫做'目標文件格式'的數據格式形式存在. 每一個文件被叫做'目標文件'. 輸出文件經常被叫做'可執行文件',但是由於需要,我們也把它叫做目標文件. 每一個目標文件中,在其它東西之間,有一個節列表.我們有時把輸入文件的節叫做輸入節; 相似的,輸出文件中的一個節經常被叫做輸出節.
一個目標文件中的每一個節都有一個名字和一個大小尺寸. 大多數節還有一個相關的數據塊, 稱為節內容. 某一個節可能被標式詎'loadable',含義是在輸出文件被執行時,這個節應當被載入到內存中去. 一個沒有內容的節可能是'allocatable', 含義是內存中必須為這個節開辟一塊空間,但是沒有實際的內容載入到這里(在某些情況下,這塊內存必須被標式詎零). 一個既不是loadable也不是allocatable的節一般含有一些調試信息.
每一個loadable或allocatable的輸出節有兩個地址. 第一個是'VMA'或稱為虛擬內存地址. 這是當輸出文件運行時節所擁有的地址. 第二個是"LMA', 或稱為載入內存地址. 這個節即將要載入的內存地址. 這大多數情況下這兩個地址是相同的. 它們兩個有可能不同的一個例子是當一個數據節在ROM中時, 當程序啟動時,被拷貝到RAM中(這個技術經常被用在基於ROM的系統中進行全局變量的初始化). 在這種情況下, ROM地址就是LMA, 而RAM地址就是VMA.
你可以通過使用帶有'-h'選項的'objdump'來察看目標文件中的節.
每一個目標文件還有一個關於符號的列表, 被稱為'符號表'. 一個符號可能是定義過了的,也可能是未定義的.
每一個符號有一個名字, 而且每一個定義的符號有一個地址. 如果你把一個C/C++程序編譯為一個目標文件,對於每一個定義的函數和全局或靜態變量,你為得到一個定義的符號. 每一個在輸入文件中只是一個引用而未定義的函數或全局變量會變成一個未定義的符號.
你可以使用'nm'程序來看一個目標文件中的符號, 或者使用'objdump'程序帶有'-t'選項.
連接腳本的格式
====================
連接腳本是文本文件.
你寫了一系列的命令作為一個連接腳本. 每一個命令是一個帶有參數的關鍵字,或者是一個對符號的賦值. 你可以用分號分隔命令. 空格一般被忽略.
文件名或格式名之類的字符串一般可以被直接鍵入. 如果文件名含有特殊字符,比如一般作為分隔文件名用的逗號, 你可以把文件名放到雙引號中. 文件名中間無法使用雙引號.
你可以象在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'節之間創建一個小的缺口.
就這樣,這是一個簡單但完整的連接腳本.
每個連接都被一個'連接腳本'所控制. 這個腳本是用連接命令語言書寫的.