Linux下的lds鏈接腳本簡介(一)

轉載自:http://linux.chinaunix.net/techdoc/beginner/2009/08/12/1129972.shtml
一、 概論
每一個鏈接過程都由鏈接腳本(linker script, 一般以lds作爲文件的後綴名)控制. 鏈接腳本主要用於規定如何把輸入文件內的section放入輸出文件內, 並控制輸出文件內各部分在程序地址空間內的佈局. 但你也可以用連接命令做一些其他事情.
連接器有個默認的內置連接腳本, 可用ld –verbose查看. 連接選項-r和-N可以影響默認的連接腳本(如何影響?).
-T選項用以指定自己的鏈接腳本, 它將代替默認的連接腳本。你也可以使用以增加自定義的鏈接命令.
以下沒有特殊說明,連接器指的是靜態連接器.
二、基本概念
鏈接器把一個或多個輸入文件合成一個輸出文件.
輸入文件: 目標文件或鏈接腳本文件.
輸出文件: 目標文件或可執行文件.
目標文件(包括可執行文件)具有固定的格式, 在UNIX或GNU/Linux平臺下, 一般爲ELF格式
有時把輸入文件內的section稱爲輸入section(input section), 把輸出文件內的section稱爲輸出section(output sectin).
目標文件的每個section至少包含兩個信息: 名字大小. 大部分section還包含與它相關聯的一塊數據, 稱爲section contents(section內容). 一個section可被標記爲“loadable(可加載的)”或“allocatable(可分配的)”.
loadable section: 在輸出文件運行時, 相應的section內容將被載入進程地址空間中.
allocatable section: 內容爲空的section可被標記爲“可分配的”. 在輸出文件運行時, 在進程地址空間中空出大小同section指定大小的部分. 某些情況下, 這塊內存必須被置零.
如果一個section不是“可加載的”或“可分配的”, 那麼該section通常包含了調試信息. 可用objdump -h命令查看相關信息.
每個“可加載的”或“可分配的”輸出section通常包含兩個地址: VMA(virtual memory address虛擬內存地址或程序地址空間地址)和LMA(load memory address加載內存地址或進程地址空間地址). 通常VMA和LMA是相同的.
在目標文件中, loadable或allocatable的輸出section有兩種地址: VMA(virtual Memory Address)和LMA(Load Memory Address). VMA是執行輸出文件時section所在的地址, 而LMA是加載輸出文件時section所在的地址. 一般而言, 某section的VMA == LMA. 但在嵌入式系統中, 經常存在加載地址和執行地址不同的情況: 比如將輸出文件加載到開發板的flash中(由LMA指定), 而在運行時將位於flash中的輸出文件複製到SDRAM中(由VMA指定).
可這樣來理解VMA和LMA, 假設:
(1) .data section對應的VMA地址是0×08050000, 該section內包含了3個32位全局變量, i、j和k, 分別爲1,2,3.
(2) .text section內包含由”printf( “j=%d “, j );”程序片段產生的代碼.
連接時指定.data section的VMA爲0×08050000, 產生的printf指令是將地址爲0×08050004處的4字節內容作爲一個整數打印出來。
如果.data section的LMA爲0×08050000,顯然結果是j=2
如果.data section的LMA爲0×08050004,顯然結果是j=1
還可這樣理解LMA:
.text section內容的開始處包含如下兩條指令(intel i386指令是10字節,每行對應5字節):
jmp 0×08048285
movl $0×1,%eax
如果.text section的LMA爲0×08048280, 那麼在進程地址空間內0×08048280處爲“jmp 0×08048285”指令, 0×08048285處爲movl $0×1,%eax指令. 假設某指令跳轉到地址0×08048280, 顯然它的執行將導致%eax寄存器被賦值爲1.
如果.text section的LMA爲0×08048285, 那麼在進程地址空間內0×08048285處爲“jmp 0×08048285”指令, 0×0804828a處爲movl $0×1,%eax指令. 假設某指令跳轉到地址0×08048285, 顯然它的執行又跳轉到進程地址空間內0×08048285處, 造成死循環.
符號(symbol): 每個目標文件都有符號表(SYMBOL TABLE), 包含已定義的符號(對應全局變量和static變量和定義的函數的名字)和未定義符號(未定義的函數的名字和引用但沒定義的符號)信息.
符號值: 每個符號對應一個地址, 即符號值(這與c程序內變量的值不一樣, 某種情況下可以把它看成變量的地址). 可用nm命令查看它們. (nm的使用方法可參考本blog的GNU binutils筆記)
三、 腳本格式
鏈接腳本由一系列命令組成, 每個命令由一個關鍵字(一般在其後緊跟相關參數)或一條對符號的賦值語句組成. 命令由分號‘;’分隔開.
文件名或格式名內如果包含分號’;'或其他分隔符, 則要用引號‘’將名字全稱引用起來. 無法處理含引號的文件名.
/* */之間的是註釋。
四、 簡單例子
在介紹鏈接描述文件的命令之前, 先看看下述的簡單例子:
以下腳本將輸出文件的text section定位在0×10000, data section定位在0×8000000:
SECTIONS
{
. = 0×10000;
.text : { *(.text) }
. = 0×8000000;
.data : { *(.data) }
.bss : { *(.bss) }
}
解釋一下上述的例子:
. = 0×10000 : 把定位器符號置爲0×10000 (若不指定, 則該符號的初始值爲0).
.text : { *(.text) } : 將所有(*符號代表任意輸入文件)輸入文件的.text section合併成一個.text section, 該section的地址由定位器符號的值指定, 即0×10000.
. = 0×8000000 :把定位器符號置爲0×8000000
.data : { *(.data) } : 將所有輸入文件的.data section合併成一個.data section, 該section的地址被置爲0×8000000.
.bss : { *(.bss) } : 將所有輸入文件的.bss section合併成一個.bss section,該section的地址被置爲0×8000000+.data section的大小.
連接器每讀完一個section描述後, 將定位器符號的值*增加*該section的大小. 注意: 此處沒有考慮對齊約束.
五、 簡單腳本命令
ENTRY(SYMBOL):將符號SYMBOL的值設置成入口地址。
入口地址(entry point)是指進程執行的第一條用戶空間的指令在進程地址空間的地址
ld有多種方法設置進程入口地址, 按一下順序: (編號越前, 優先級越高)
1, ld命令行的-e選項
2, 連接腳本的ENTRY(SYMBOL)命令
3, 如果定義了start符號, 使用start符號值
4, 如果存在.text section, 使用.text section的第一字節的位置值
5, 使用值0
INCLUDE filename : 包含其他名爲filename的鏈接腳本
相當於c程序內的的#include指令, 用以包含另一個鏈接腳本.
腳本搜索路徑由-L選項指定. INCLUDE指令可以嵌套使用, 最大深度爲10. 即: 文件1內INCLUDE文件2, 文件2內INCLUDE文件3… , 文件10內INCLUDE文件11. 那麼文件11內不能再出現 INCLUDE指令了.
INPUT(files): 將括號內的文件做爲鏈接過程的輸入文件
ld首先在當前目錄下尋找該文件, 如果沒找到, 則在由-L指定的搜索路徑下搜索. file可以爲 -lfile形式,就象命令行的-l選項一樣. 如果該命令出現在暗含的腳本內, 則該命令內的file在鏈接過程中的順序由該暗含的腳本在命令行內的順序決定.
GROUP(files): 指定需要重複搜索符號定義的多個輸入文件
file必須是庫文件, 且file文件作爲一組被ld重複掃描,直到不在有新的未定義的引用出現。
OUTPUT(FILENAME): 定義輸出文件的名字
同ld的-o選項, 不過-o選項的優先級更高. 所以它可以用來定義默認的輸出文件名. 如a.out
SEARCH_DIR(PATH):定義搜索路徑,
同ld的-L選項, 不過由-L指定的路徑要比它定義的優先被搜索。
STARTUP(filename) : 指定filename爲第一個輸入文件
在鏈接過程中, 每個輸入文件是有順序的. 此命令設置文件filename爲第一個輸入文件。
OUTPUT_FORMAT(BFDNAME) : 設置輸出文件使用的BFD格式
同ld選項-o format BFDNAME, 不過ld選項優先級更高.
OUTPUT_FORMAT(DEFAULT,BIG,LITTLE): 定義三種輸出文件的格式(大小端)
若有命令行選項-EB, 則使用第2個BFD格式; 若有命令行選項-EL,則使用第3個BFD格式.否則默認選第一個BFD格式.
TARGET(BFDNAME):設置輸入文件的BFD格式
同ld選項-b BFDNAME. 若使用了TARGET命令, 但未使用OUTPUT_FORMAT命令, 則最用一個TARGET命令設置的BFD格式將被作爲輸出文件的BFD格式.
ASSERT(EXP, MESSAGE):如果EXP不爲真,終止連接過程
EXTERN(SYMBOL SYMBOL …):在輸出文件中增加未定義的符號,如同連接器選項-u
FORCE_COMMON_ALLOCATION:爲common symbol(通用符號)分配空間,即使用了-r連接選項也爲其分配
NOCROSSREFS(SECTION SECTION …):檢查列出的輸出section,如果發現他們之間有相互引用,則報錯。對於某些系統,特別是內存較緊張的嵌入式系統,某些section是不能同時存在內存中的,所以他們之間不能相互引用。
OUTPUT_ARCH(BFDARCH):設置輸出文件的machine architecture(體系結構),BFDARCH爲被BFD庫使用的名字之一。可以用命令objdump -f查看。
可通過 man -S 1 ld查看ld的聯機幫助, 裏面也包括了對這些命令的介紹.
六、 對符號的賦值
在目標文件內定義的符號可以在鏈接腳本內被賦值. (注意和C語言中賦值的不同!) 此時該符號被定義爲全局的. 每個符號都對應了一個地址,此處的賦值是更改這個符號對應的地址.
舉例. 通過下面的程序查看變量a的地址:
a.c文件
/* a.c */
#include <stdio.h>
int a = 100;
int main()
{
printf( "&a=%p\n", &a );
return 0;
}
a.lds文件
/* a.lds */
a = 3;
編譯命令:
$ gcc -Wall -oa-without-lds.exe a.c
運行結果:
&a = 0×601020
編譯命令:
$ gcc -Wall -oa-with-lds.exe a.ca.lds
運行結果:
&a = 0×3
注意: 對符號的賦值只對全局變量起作用!
對於一些簡單的賦值語句,我們可以使用任何c語言語法的賦值操作:
SYMBOL = EXPRESSION ;
SYMBOL += EXPRESSION ;
SYMBOL -= EXPRESSION ;
SYMBOL *= EXPRESSION ;
SYMBOL /= EXPRESSION ;
SYMBOL >= EXPRESSION ;
SYMBOL &= EXPRESSION ;
SYMBOL |= EXPRESSION ;
除了第一類表達式外, 使用其他表達式需要SYMBOL已經被在某目標文件的源碼中被定義。
. 是一個特殊的符號,它是定位器,一個位置指針,指向程序地址空間內的某位置(或某section內的偏移,如果它在SECTIONS命令內的某section描述內),該符號只能在SECTIONS命令內使用。
注意:賦值語句包含4個語法元素:符號名、操作符、表達式、分號;一個也不能少。
被賦值後,符號所屬的section被設值爲表達式EXPRESSION所屬的SECTION(參看11. 腳本內的表達式)
賦值語句可以出現在連接腳本的三處地方:SECTIONS命令內,SECTIONS命令內的section描述內和全局位置。
示例1
floating_point = 0; /* 全局位置 */
SECTIONS
{
.text :
{
*(.text)
_etext = .; /* section描述內 */
}
_bdata = (. + 3) & ~ 4; /* SECTIONS命令內 */
.data : { *(.data) }
}
PROVIDE關鍵字
該關鍵字用於定義這類符號:在目標文件內被引用,但沒有在任何目標文件內被定義的符號。
示例2
SECTIONS
{
.text :
{
*(.text)
_etext = .;
PROVIDE(etext = .);
}
}
這裏,當目標文件內引用了etext符號,卻沒有定義它時,etext符號對應的地址被定義爲.text section之後的第一個字節的地址。
發佈了5 篇原創文章 · 獲贊 12 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章