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

轉載自:http://hubingforever.blog.163.com/blog/static/171040579201192472552886/函數

1、 概論佈局

每個連接過程都由連接腳本(linker script, 通常以lds做爲文件的後綴名)控制. 連接腳本主要用於規定如何把輸入文件內的section放入輸出文件內, 並控制輸出文件內各部分在程序地址空間內的佈局. 但你也能夠用鏈接命令作一些其餘事情.
鏈接器有個默認的內置鏈接腳本, 可用ld –verbose查看. 鏈接選項-r和-N能夠影響默認的鏈接腳本(如何影響?).
-T選項用以指定本身的連接腳本, 它將代替默認的鏈接腳本。你也可使用以增長自定義的連接命令.
如下沒有特殊說明,鏈接器指的是靜態鏈接器.
2、基本概念
連接器把一個或多個輸入文件合成一個輸出文件.
輸入文件: 目標文件或連接腳本文件.
輸出文件: 目標文件或可執行文件.
目標文件(包括可執行文件)具備固定的格式, 在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筆記)
3、 腳本格式
連接腳本由一系列命令組成, 每一個命令由一個關鍵字(通常在其後緊跟相關參數)或一條對符號的賦值語句組成. 命令由分號‘ ;’分隔開.
文件名或格式名內若是包含分號’ ;'或其餘分隔符, 則要用引號‘ ’將名字全稱引用起來. 沒法處理含引號的文件名.
/* */之間的是註釋。
4、 簡單例子
在介紹連接描述文件的命令以前, 先看看下述的簡單例子:
如下腳本將輸出文件的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的大小. 注意: 此處沒有考慮對齊約束.
5、 簡單腳本命令
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的聯機幫助, 裏面也包括了對這些命令的介紹.
6、 對符號的賦值
在目標文件內定義的符號能夠在連接腳本內被賦值. (注意和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 -o a-without-lds.exe a.c
運行結果:
&a = 0×601020
編譯命令:
$ gcc -Wall -o a-with-lds.exe a.c a.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以後的第一個字節的地址。
相關文章
相關標籤/搜索