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
()