您的位置:首页 > 运维架构 > Linux

linux的那些事儿之链接脚本(link script)

2017-02-05 16:43 232 查看

基础概念

object file——ld链接器的输入文件

executable——ld链接器的输出文件

section——每个节都有名字和大小,大部分节以一块数据区相联系,一个节包含可加载,可分配等属性,如果及不可加载,也不可分配,可能包含的是调试信息

VMA——虚拟内存地址

LMA——线性内存地址,一般与VMA值相同

symbol table——符号表,包含程序中所有的符号定义,如果未定义某符号,在这里就不可能找到,当然也不可能链接成功

简单链接脚本实例

SECTIONS
{
. = 0x10000;
.text : { *(.text) }
. = 0x8000000;
.data : { *(.data) }
.bss : { *(.bss) }
}


  SECTIONS是一个指令,链接器在链接目标文件时通过执行链接脚本中的指令来完成链接过程。

  如上代码所示,’.’是一个特殊的符号,用于记录当前位置,第一行指定了程序的.text节从地址0x10000开始,第二行用于表示.text节内容,‘:’是语法需要,其后表示.text节内容的来源,其中’*’是通配符,表示链接器的所有输入文件,*(.text)则表示链接器所有输入文件中的.text节。

  .data,与.bss与.text类似,链接脚本实际上定义了可执行文件在内存加载后实际的布局结构,即在实际程序运行时,.text就是加载在0x10000的内存地址,.data加载在0x8000000的内存地址,.bss加载在紧随.data后开始的内存地址处。

链接脚本中的指令

ENTRY(symbol)

描述:这个指令用于指定一个可执行文件的入口地址,链接器在执行链接操作时顺序执行如下动作去设置程序入口地址,直到程序入口地址被成功设置。

ld命令的”-e”命令行选项

ENTRY(symbol)这个链接脚本命令

通过搜索start符号来确定入口地址

.text节的开始地址

0x0地址

INCLUDE filename

描述:用于在一个链接脚本中包含其它链接脚本,最高可包含深度达10层,搜索路径为当前所在目录,也可在链接时用-L选项指定INCLUDE命令的搜索路径,类似C语言中引用头文件

INPUT(file, file, …)

INPUT(file file …)

描述:指定输入文件名,如INPUT(entry.o), INPUT(-lfile),前一个好理解,后一个表示将名为libfile.a的文件作为输入。对于输入文件的搜索路径可通过-L选项制定,若不指定,将默认以当前ld链接器执行的路径作为搜索路径。

GROUP(file, file, …)

GROUP(file file …)

描述:这个指令与INPUT指令类似,唯一的区别是GROUP只以档案文件(*.a)作为输入。

OUTPUT(filename)

描述:制定链接后输出文件的名字

SEARCH_DIR(path)

描述:类似-L选项,制定搜索路径

STARTUP(filename)

OUTPUT_FORMAT(bfdname)

OUTPUT_FORMAT(default, big, little)

描述:指定BFD格式

TARGET(bfdname)

描述:类似OUTPUT_FORMAT

REGION_ALIAS(alias, region)

描述:设置一块制定内存区域的别名

HIDDEN(symbol = expression)

描述:HIDDEN指令可以将定义的符号隐藏,使其不导出,这样其它模块就见不到这些定义的符号

PROVIDE(symbol = expression)

描述:防止程序中定义同名的符号时链接出错

PROVIDE_HIDDEN(symbol = expression)

描述:与PROVIDE相似,区别在于此处定义的符号不会导出

ASSERT(exp, message)

EXTERN(symbol symbol …)

FORCE_COMMON_ALLOCATION

INHIBIT_COMMON_ALLOCATION

INSERT [ AFTER | BEFORE ] output_section

NOCROSSREFS(section section …)

NOCROSSREFS_TO(tosection fromsection …)

OUTPUT_ARCH(bfdarch)

LD_FEATURE(string)

  以上介绍了在链接脚本中的基本指令,其中没加注释描述的是一些不太常用的指令,如果需要可以自行谷歌,在后续可能会补充完善…

链接脚本中的赋值

/*
* test.lds
*/
floating_point = 0;
SECTIONS
{
. = 0;
.text :
{
*(.text)
_etext = .;
}
_bdata = (. + 3) & ~ 3;
.data :
{
*(.data)
}
__init_array_start = .;
__init_array_end = .;
}


  以上是在链接脚本中定义符号和赋值操作的一个示例,其中’.’是特殊的符号,只能在SECTIONS指令中使用,其它符号可以自定义并赋值,赋值可以使用C语言中的赋值语句(=, +=, -=, /=, *=, &=, |=, <<=, >>=),赋值语句后需用分号分隔,其中符号的定义可以在SECTIONS指令中定义,也可以在其外的链接脚本中定义。

在链接脚本中定义的符号如何在高级语言代码中引用?

  像上面的那段代码,定义了floating_point这个符号,这儿拿C语言举个例子,如下列C程序

/*
*test.c
*/
#include <stdio.h>
extern void* floating_point;
int main()
{
printf("floating_point position is : %p\n", floating_point);
return 0;
}


  这儿尝试引用floating_point这个符号,至于能不能成功,还请读者自己去尝试一二,请记住编译链接时一定要使用上面的链接脚本,下面是编译链接方法。

$ gcc -c test.c -T test.lds -o test
$ nm test


  按照以上方法得到的可执行文件不一定能执行,但这样确实链接成功了,并且可以通过nm工具可以看到floating_point在符号表中定义的所在位置。同时也会发现在链接脚本中对符号赋值其实是在给符号指定地址。当然要想要使编译出的结果可执行,那就需要用汇编实现printf后重新编译了。利用c库的话链接脚本需要做其它一些更复杂的配置。这将在之后讲到…

SECTIONS

  sections指令可以所是链接脚本中最重要的指令,它描述了输入的sections到输出的sections的映射关系,同时定义了输出的sections在内存中的布局。

  下面将从SECTIONS指令中涉及到的各个细节具体描述。

  

输出节的描述

  section [address] [(type)] :

[AT(lma)]

[ALIGN(secti
4000
on_align) | ALIGN_WITH_INPUT]

[SUBALIGN(subsection_align)]

[constraint]

{

output-section-command

output-section-command



} [>region] [AT>lma_region] [:phdr :phdr …] [=fillexp] [,]

section是节名,用于区分不同的节。

address指定当前节在内存中的加载地址,详情可参考https://sourceware.org/binutils/docs/ld/Output-Section-Address.html#Output-Section-Address

(type)制定了节的类型,可选的节类型如下:

  NOLOAD

  DSECT

  COPY

  INFO

  OVERLAY

lma

输入节

  对于输入节可用如下例子进行理解,其中是在链接脚本中可能出现的可能出现的对于输入文件的描述情况,可以尝试去解释下面这个链接脚本的含义。 

SECTIONS {
outputa 0x10000 :
{
all.o
foo.o (.input1)
}
outputb :
{
[A-Z]foo.o (.input2)
foo?.o (.input1)
}
outputc :
{
*(.input1)
*(.input2)
}
}


输出节的数据

  可在每个节的描述中使用BYTE, SHORT, LONG, QUAD, or SQUAD等关键字填充内存数据。例如下面示例,但这些关键字只能在节的描述内部使用,不能在节间的区域使用。

SECTIONS { .text : { *(.text) ; LONG(1) } .data : { *(.data) } }


C++中的构造与析构函数在程序构造中的实现

  构造函数和析构函数对于学过C++的人来说都不陌生,但是很少有人能说清它们是怎么一回事儿,这要从链接脚本中的连个关键字说起,CREATE_OBJECT_SYMBOLS与CONSTRUCTORS。

  CREATE_OBJECT_SYMBOLS是告诉链接器为每个输入文件创建一个符号,这个符号与输入文件是一一对应的。

  CONSTRUCTORS这个关键字是告诉链接器构造函数应该放在哪儿,但这适用于a.out格式,在COFF或者ELF格式中,C++编译器适用.ctors节和.dtors节分别放置构造函数和析构函数,其中CTOR_LISTCTOR_END,__DTOR_LISTDTOR_END等符号分别用于表示构造函数的开始地址和结束地址与析构函数的开始地址与结束地址。SORT_BY_NAME则保证构造和析构函数的正确顺序。构造函数的调用在C++中是通过子例程__main调用的,而__main是编译其自动插入main的启动代码位置的,析构函数的话一般是从atexit调用的,也有的直接在exit中就调用了析构函数。

  

__CTOR_LIST__ = .;
LONG((__CTOR_END__ - __CTOR_LIST__) / 4 - 2)
*(.ctors) /* *(SORT_BY_NAME(.ctors) */
LONG(0)
__CTOR_END__ = .;
__DTOR_LIST__ = .;
LONG((__DTOR_END__ - __DTOR_LIST__) / 4 - 2)
*(.dtors) /* *(SORT_BY_NAME(.dtors)) */
LONG(0)
__DTOR_END__ = .;


MEMORY

  memory指令的使用方法如下

MEMORY
{
name [(attr)] : ORIGIN = origin, LENGTH = len
...
}


attr的可选值如下:
`R'——Read-only section
`W'——Read/write section
`X'——Executable section
`A'——Allocatable section
`I'——Initialized section
`L'——Same as `I'
`!'——Invert the sense of any of the attributes that follow


6. PHDRS

  phdrs指令的使用方法如下,其中type的是必选的,而其它则是可选项,其中FILEHDR,PHDRS,AT,FLAGS都是关键字。

  

PHDRS
{
name type [ FILEHDR ] [ PHDRS ] [ AT ( address ) ]
[ FLAGS ( flags ) ] ;
}


  type的可选参数如下:

PT_NULL (0)——Indicates an unused program header.

PT_LOAD (1)——Indicates that this program header describes a segment to be loaded from the file.

PT_DYNAMIC (2)——Indicates a segment where dynamic linking information can be found.

PT_INTERP (3)——Indicates a segment where the name of the program interpreter may be found.

PT_NOTE (4)——Indicates a segment holding note information.

PT_SHLIB (5)——A reserved program header type, defined but not specified by the ELF ABI.

PT_PHDR (6)——Indicates a segment where the program headers may be found.

结语

  链接脚本的组成部分还是比较简单的,但可能大部分人在平时的工作与学习中并不需要深入理解这个东西,所以对这东西也是朦朦胧胧,写下这篇笔记也是为了自己在用到这方面东西是能够迅速理解,其中有许多不足与叙述不当,不够深入的地方,但当做一份在急需时帮助理解的文档也是足够了。

  

https://sourceware.org/binutils/docs/ld/
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  编译 linux 链接脚本