8、 內存區域命令
在默認情形下,鏈接器能夠爲section在程序地址空間內分配任意位置的存儲區域。
並經過輸出
section描述的
>
REGION屬性
顯示地將該輸出section限定於在程序地址空間內的某塊存儲區域,當存儲區域大小不能知足要求時,鏈接器會報告該錯誤
。
你也能夠用MEMORY命令讓
在SECTIONS命令內
*未*引用
的
selection
分配在程序地址空間內的某個存儲區域內。
注意:如下存儲區域指的是在程序地址空間內的。
MEMORY命令的文法以下,
MEMORY
{
NAME1 [(ATTR)] : ORIGIN = ORIGIN1, LENGTH = LEN1
NAME2 [(ATTR)] : ORIGIN = ORIGIN2, LENGTH = LEN2
…
}
NAME :存儲區域的名字,這個名字能夠與符號名、文件名、section名重複,由於它處於一個獨立的名字空間。
ATTR :定義該存儲區域的屬性,在講述SECTIONS命令時提到,當某輸入section沒有在SECTIONS命令內引用時,鏈接器會把該輸入 section直接拷貝成輸出section,而後將該輸出section放入內存區域內。若是設置了內存區域設置了ATTR屬性,那麼該區域只接受知足該屬性的section(怎麼判斷該section是否知足?輸出section描述內好象沒有記錄該section的讀寫執行屬性)。
ATTR屬性內能夠出現如下7個字符,
R
只讀section
W
讀/寫section
X
可執行section
A
‘可分配的’section
I
初始化了的section
L
同I
! 不知足該字符以後的任何一個屬性的section
ORIGIN :關鍵字,區域的開始地址,可簡寫成org或o
LENGTH :關鍵字,區域的大小,可簡寫成len或l
示例
MEMORY
{
rom (rx) : ORIGIN = 0, LENGTH = 256K
ram (!rx) : org = 0×40000000, l = 4M
}
此例中
,把在SECTIONS命令內*未*引用的且具備讀屬性或寫屬性的輸入section放入rom區域內,把其餘未引用的輸入section放入 ram。若是某輸出section要被放入某內存區域內,而該輸出section又沒有指明ADDRESS屬性,那麼鏈接器將該輸出section放在該區域內下一個能使用位置。
9、 PHDRS命令
該命令僅在產生ELF目標文件時有效。
ELF目標文件格式用program headers程序頭(程序頭內包含一個或多個segment程序段描述)來描述程序如何被載入內存
。能夠用objdump -p命令查看。
當在本地ELF系統運行ELF目標文件格式的程序時,系統加載器經過讀取程序頭信息以知道如何將程序加載到內存。要了解系統加載器如何解析程序頭,請參考ELF ABI文檔。
在鏈接腳本內不指定
PHDRS
命令時,鏈接器可以很好的建立程序頭,可是有時須要更精確的描述程序頭,那麼
PAHDRS
命令就派上用場了。
注意:一旦在鏈接腳本內使用了
PHDRS命令,那麼鏈接器**僅會**建立PHDRS命令指定的信息,因此使用時須謹慎。
PHDRS命令文法以下,
PHDRS
{
NAME
TYPE
[ FILEHDR ]
[ PHDRS ]
[ AT ( ADDRESS ) ]
[ FLAGS ( FLAGS ) ] ;
}
其中FILEHDR、PHDRS、AT、FLAGS爲關鍵字。
NAME
:爲程序段名,此名字能夠與符號名、section名、文件名重複,由於它在一個獨立的名字空間內。此名字只能在SECTIONS命令內使用。
一個程序段能夠由多個‘可加載’的section組成。經過輸出section描述的屬性
:PHDRS
能夠將輸出section加入一個程序段,
: PHDRS
中的
PHDRS
爲程序段名。在一個輸出section描述內能夠屢次使用
:PHDRS
命令,也便可以將一個section加入多個程序段。
若是在一個輸出section描述內指定了
:PHDRS
屬性,那麼其後的輸出section描述將默認使用該屬性,除非它也定義了:PHDRS屬性。顯然當多個輸出section屬於同一程序段時可簡化書寫。
TYPE能夠是如下八種形式,
PT_NULL 0
表示未被使用的程序段
PT_LOAD 1
表示該程序段在程序運行時應該被加載
PT_DYNAMIC
表示該程序段包含動態鏈接信息
PT_INTERP 3
表示該程序段內包含程序加載器的名字,在linux下常見的程序加載器是ld-linux.so.2
PT_NOTE 4
表示該程序段內包含程序的說明信息
PT_SHLIB 5
一個保留的程序頭類型,沒有在ELF ABI文檔內定義
PT_PHDR 6
表示該程序段包含程序頭信息。
EXPRESSION 表達式值
以上每一個類型都對應一個數字,該表達式定義一個用戶自定的程序頭。
在TYPE屬性後存在FILEHDR關鍵字,表示該段包含ELF文件頭信息;存在PHDRS關鍵字,表示該段包含ELF程序頭信息。
AT(ADDRESS)
屬性定義該程序段的加載位置(LMA),該屬性將**覆蓋**該程序段內的section的AT()屬性。
默認狀況下,鏈接器會根據該程序段包含的section的屬性(什麼屬性?好象在輸出section描述內沒有看到)設置
FLAGS標誌,該標誌用於設置程序段描述的p_flags域。
下面看一個典型的PHDRS設置
示例
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 */
…
. = . + 0×1000;
/* move to a new page in memory */
.data
: { *(.data) }
:data
.dynamic
: { *(.dynamic) }
:data
:dynamic
…
}
10、版本號命令
當使用ELF目標文件格式時,鏈接器支持帶版本號的符號。版本號也只限於ELF文件格式。
讀者能夠發現僅僅在共享庫中,符號的版本號屬性纔有意義。動態加載器使用符號的版本號爲應用程序選擇共享庫內的一個函數的特定實現版本。
能夠在鏈接腳本內直接使用版本號命令,也能夠將版本號命令實現於一個特定版本號描述文件(用鏈接選項–version-script指定該文件)。
該命令的文法以下,
VERSION
{ version-script-commands }
如下討論用gcc
10.1. 帶版本號的符號的定義(共享庫內)
文件b.c內容以下,
int
getVersion
()
{
return 1
;
}
寫鏈接器的版本控制腳本,本例中爲
b.lds,內容以下
VER1.0
{
getVersion;
};
VER2.0{
};
$gcc -c
b.c
$gcc -shared -
Wl
,
--version-script
=
b.lds
-o
libb.so
b.o
能夠在
{}內填入要綁定的符號,本例中
getVersion符號就與VER1.0綁定了。
那麼若是有一個應用程序鏈接到該庫的
getVersion符號,那麼它鏈接的就是VER1.0版本的
getVersion符號
若是咱們對b.c文件進行了升級,更改以下:
int
getVersion
()
{
return
101;
}
這裏我對getVersion()進行了更改,其返回值的意義也進行改變,也就是它和前不兼容:
爲了程序的安全,咱們把b.lds更改成,
VER1.0
{
};
VER2.0
{
getVersion;
};
而後生成新的libb.so文件。
這時若是咱們運行app.exe(它已經鏈接到VER1.0版本的
getVersion
()),就會發現該應用程序不能運行了。
提示信息以下:
./app.exe: relocation error: ./app.exe: symbol getVersion, version VER1.0 not defined in file libb.so with link time reference
由於庫內沒有VER1.0版本的
getVersion
()
,只有VER2.0版本的
getVersion
()
。
10.二、參看鏈接的符號的版本
對上面生成的app.exe執行如下命令:
nm
app.exe
| grep getVersion
結果
U new_true@@VER1.0
用nm命令發現app鏈接到VER1.0版本的getVersion
10.三、 GNU的擴充
在GNU中,容許在程序文件內綁定 *符號* 到 *帶版本號的別名符號*
文件b.c內容以下,
int
old_getVersion
()
{
return 1;
}
int
new_getVersion
()
{
return 101;
}
__asm__
(".symver
old_getVersion
,
getVersion
@
VER1.0
");
__asm__
(".symver
new_getVersion
,
getVersion
@@
VER2.0
");
其中,對於
VER1.0版本號的
getVersion別名符號是
old_getVersion;
對於VER2.0版本號的getVersion別名符號是new
_getVersion
,
在鏈接時,默認的版本號爲
VER2.0
供鏈接器用的版本控制腳本b.lds內容以下,
VER1.0
{
};
VER2.0
{
};
版本控制文件內必須包含版本
VER1.0
和版本
VER2.0
的定義,由於在b.c文件內有對他們的引用
再次執行如下命令編譯鏈接b.c和app.c
gcc -c src/b.c
gcc -shared -Wl,--version-script=./lds/b.lds -o libb.so b.o
gcc -o app.exe ./src/app.c libb.so
運行:
./app.exe
結果:
Version=0x65
說明app.exe的確是鏈接的
VER2.0的
getVersion,即
new_getVersion
()
咱們再對app.c進行修改,以使它鏈接的
VER1.0的
getVersion,即
old
_getVersion
()
app.c文件:
#include <stdio.h>
__asm__
(".symver
getVersion
,
getVersion@VER1.0
");
extern int
getVersion()
;
int
main()
{
printf("Version=%p\n",
getVersion()
);
return
0;
}
再次編譯鏈接b.c和app.c
結果:
Version=0x1
說明這次app.exe的確是鏈接的
VER1.0的
getVersion,即
old
_getVersion
()