GNU链接脚本详解(转载)

🔗 原文链接: https://zhuanlan.zhihu.com/p/516338...

⏰ 剪存时间:2022-10-17 11:05:37 (UTC+8)

✂️ 本文档由 飞书剪存 一键生成

目录

收起

一、链接脚本

二、基本概念

1.简单的链接脚本示例

三、脚本命令

1.指定入口地址命令

四、文件处理命令

1.INCLUDE

2.INPUT

3.GROUP

4.AS_NEEDED

5.OUTPUT

6.SEARCH_DIR

7.STARTUP

五、输出文件格式命令

1.OUTPUT_FORMAT

2.TARGET

六、REGION_ALIAS命令

七、其他命令

1.ASSERT(exp, message)

2.EXTERN(symbol symbol …)

3.FORCE_COMMON_ALLOCATION

4.INHIBIT_COMMON_ALLOCATION

5.FORCE_GROUP_ALLOCATION

6.INSERT [ AFTER | BEFORE ] output_section

7.NOCROSSREFS(section section …)

8.NOCROSSREFS_TO(tosection fromsection …)

9.OUTPUT_ARCH(bfdarch)

10.LD_FEATURE(string)

八、符号赋值

1.像C一样简单的赋值

2.HIDDEN

3.PROVIDE

4.PROVIDE_HIDDEN

5.源码引用

九、SECTIONS命令

1.输出节的描述

1.格式要求:

2.section

3.[address]

4.[(type)]

5.[AT(lma)]

6.[ALIGN(section_align) | ALIGN_WITH_INPUT]

7.[SUBALIGN(subsection_align)]

8.[constraint]

9.[>region]

10.[AT>lma_region]

11.[:phdr :phdr …]

12.[=fillexp]

13.output-section-command格式:

14.输入节描述

一、链接脚本

每一个链接都是由链接脚本控制的,链接脚本是用链接命令语言编写的脚本。大部分链
接脚本都是描述输入文件中的节在输出文件中怎么分布、内存怎么布局的,但有时候链
接脚本也可以实现其他功能。链接都会用到一个链接脚本,如果你没有指定自己的脚本
,就会使用默认的链接脚本。你可以用"--verbose"命令行选项显示默认的连接脚本。
指定命令行参数,比如'-r''-N'都会影响默认的链接脚本。你可以用'-T'来指定自己的
链接脚本,也可以隐式地把自己的连接脚本当成链接输入文件,就像普通的链接文件一
样,参见链接文件说明。

(1)功能:指导链接器把可重定向目标文件( .o)链接成可执行目标文件或动态库( .out, .elf, .so)
(2)查看默认的链接脚本:gcc -Wl,--verbose
(3)使用自定义链接脚本:-T myself.ld

二、基本概念

链接脚本是一个文本文件。编写方式跟脚本文件编写是一样的。其中每一个命令都是一
个关键字,后面可以带参数,或者对齐方式。同行命令之间用';'隔开,空格忽略不计。

文件或格式名称等字符串可以直接输入,如果文件名里面带有特殊符号,比如一个逗号,
那么文件名就必须写在双引号里面,否则逗号就会被当成文件名的分隔符。双引号不能
作为文件名。

链接脚本跟C语言一样,可以用'/ '和' /'来注释代码,注释在语法上等同于空格。

(1)命令分隔符:;
(2)空格处理:忽略
(3)注释方法:/**/

1.简单的链接脚本示例

很多链接文件都是相当简单的,最简单的链接脚本就只有一个‘SECTIONS’命令,用来描
述目标文件的内存布局。

‘SECTIONS’命令的功能十分强大,这里我们演示一个最简单的用法。

假设输入的 .o文件构成为: 代码,.text节
初始化过的数据,.data节
未初始化的数据,.bss节
我们写一个链接脚本来指导链接: SECTIONS { . = 0x10000; .text : { (.text) } . = 0x8000000; .data : { (.data) } .bss : { (.bss) } } 从上面的链接脚本可见:
(1)输出节的文件偏移地址
.表示位置计数器,需要赋初值,然后会随着输出节的存放而自动增加,始终为文件尾, 也可以给它赋值,相当于在文件中留出空白区域。
这个例子中,表示输出节从文件的0x10000位置开始存放。这里只是示例实际上输出文
件必须流出文件头的大小,即.=SIZEOF_HEADERS+NUM,否则链接会报错。

.data的前面.被重新赋值为0x8000000,指定.data从0x8000000开始存放。如果.text
的末尾和0x8000000之间有空的内存,则会被清零处理。

(2)输入节到输出节的映射关系
链接会把至少一个输入文件的节放到输出文件的指定节中。所以,输出文件的节和输入
文件的节是一对多的关系。

在本例中:
所有输入文件( .o)的.text节都被放入输出文件( .out/ .so)的.text节; 所有输入文件(.o)的.data节都被放入输出文件(.out/.so)的.data节;
所有输入文件( .o)的 .bss节都被放入输出文件( .out/*.so)的 .bss节;

三、脚本命令

1.指定入口地址命令

程序运行的第一条指令就是调用入口地址。可以用ENTRY链接命令来指定程序的入口地
址,入口地址的指定方式有:

a.通过命令行指定:gcc -e symbol b.链接脚本指定:ENTRY(symbol)
c.如果指定了特殊目标符号的话,就用这个符号;有很多目标文件就是以一个特殊符
号开始的,但是像PE-和BeOS-based系统会检索一系列的入口地址符号,直到找到。 d.第一个‘.text’节的起始地址
e.0地址。

四、文件处理命令

下面是几条基本的文件处理命令。

1.INCLUDE

将其他链接脚本包含到当前脚本,链接器会在当前目录和用-L参数指定过的目录下查找
被包含的文件。链接脚本可以嵌套包含,最大层数为10层。INCLUDE既可以放在链接脚
本的开始,也可以放在MEMORY或者SECTIONS命令里面,或者放在输出节的描述里面。

格式:INCLUDE filename
功能:包含其他脚本文件。
搜索路径:当前目录、-L添加的目录。
放置位置:链接脚本开始、MEMORY或SECTIONS中、输出节描述中。

2.INPUT

INPUT命令直接链接指定的文件名,就像从命令行输入一样。例如如果要包含subr.o,
但是又不想在每一条链接命令中都写上的话,就可以在链接脚本中使用‘INPUT (
subr.o)’。实际上,还可以把所有输入文件(*.o)都写在链接脚本里面,然后只要用'
-T'指定一下链接脚本就好了。为了防止设置了根目录,文件名要用'/'开始,这样连接
脚本就会从根目录开始检索文件,否则,链接器就会在当前目录下查找文件,如果找不
到文件,链接器就会在所有归档库里面检索。根目录也可以在文件名一开始的时候用‘
=’来强制指定,或者在文件名前面加上$SYSROOT。

如果使用‘INPUT (-lfile)’,链接器会自动翻译成libfile.a,就好像使用命令行参
数"-l"一样。如果使用INPUT命令在链接脚本中包含文件的话,文件会从链接脚本所在
的目录开始检索。这会影响到归档文件的检索。

格式:
INPUT(file, file, …)
INPUT(file file …)
功能:指定要链接的输入文件( .o, .a)。 搜索路径:$SYSROOT、当前目录。
subr.o包含:INPUT(subr.o)
libfile.a包含:INPUT(-lfile)

3.GROUP

格式: GROUP(file, file, …) GROUP(file file …) GROUP命令语法跟INPUT差不多,但是专门用来指定归档文件(*.a),这个会不断的检索
直到发现一个新的未定义的符号引用。参见命令行参数里面关于'-('的描述。

4.AS_NEEDED

这个命令只能出现在INPUT或GROUP命令里面,对于可执行文件AS_NEEDED(file)和直接
写file的效果一样,但是对于动态库,AS_NEEDED(file)只会链接真正被依赖的输入文
件。

链接命令里可以用‘--as-needed’参数设置所有输入文件为AS_NEEDED,这个设置会被
保存,需要用‘--no-as-needed’ 来取消。

格式: AS_NEEDED(file, file, …) AS_NEEDED(file file …) 功能:当输入为动态库时,指定被依赖才链接进来的输入文件。
可以出现的位置: INPUT或GROUP命令里面。

5.OUTPUT

OUTPUT用来设置输出文件的名称,等价的命令行参数为-o filename。默认输出文件名
称为a.out。

格式:OUTPUT(filename)
功能:设置输出文件名称。 等价命令行参数:-o filename

6.SEARCH_DIR

SEARCH_DIR命令用来添加链接器的搜索路径,等价的命令行参数是-L path。如果即用
了-L也用了SEARCH_DIR,那么链接器会优先使用-L设置的路径。

格式:SEARCH_DIR(path)
功能:添加链接器的搜索路径。
等价命令行参数:-L path

7.STARTUP

STARTUP命令用来指定第一个被链接的输入文件,等价于命令行中第一个输入文件,当
目标操作系统的要求程序入口地址必须位于第一个输入文件的时候使用。

格式:STARTUP(filename)
功能:指定第一个被链接的输入文件。
使用场景:目标操作系统的要求程序入口地址必须位于第一个输入文件的时候。

五、输出文件格式命令

1.OUTPUT_FORMAT

OUTPUT_FORMAT命令用来设置输出文件使用的BFD格式。等价的命令行参数为
--oformat bfdname。命令行参数优先。OUTPUT_FORMAT可以设置三个格式,当命令行
没有-EB和-EL的时候,使用第一个格式,当有-EB的时候使用第二个参数,当有-EL的时
候,使用第三个参数。

格式:
OUTPUT_FORMAT(bfdname)
OUTPUT_FORMAT(default, big, little)
功能:设置输出文件使用的BFD格式。 等价命令行:--oformat bfdname

2.TARGET

TARGET命令用来设置链接器读取输入文件的时候使用的BFD格式。等价命令行参数
-b bfdname。

格式:
TARGET(bfdname)

六、REGION_ALIAS命令

REGION_ALIAS用来在MEMORY命令里面给内存区域创建别名。通过给内存区域创建别名,
可以快速的更改输出节在内存中的映射。

格式:REGION_ALIAS(alias, region)
功能:在MEMORY命令里面给内存区域创建别名。
使用位置:MEMORY命令里面。
应用场景:快速更改输出节在内存中的映射。
例子:

假设我们有两块内存区域RAM1,RAM2。有一个输出节.data可以映射到RAM1也可以映射到
RAM2,我们可以这样做:

REGION_ALIAS("RAM", RAM1);/*REGION_ALIAS("RAM", RAM2);*/SECTIONS{    .data:    {        *(.data)    } >RAM}

这样我们要修改.data映射到的内存区域,就只要分别注释就可以了。

七、其他命令

1.ASSERT(exp, message)

断言表达式exp的值为非零,如果exp的值是零,链接器就会打印message内容,然后报
错退出。

注意:由于assert发生在链接的过程中,所以所有在输出节里面用PROVIDE自定义的符
号赋值,assert的时候都会失败,除了赋值为'.'。但是在输出节外面定义的可以断言
成功。

如:

.stack :{  PROVIDE (__stack = .);  PROVIDE (__stack_size = 0x100);  ASSERT ((__stack > (_end + __stack_size)), "Error: No room left for the stack");}

这样ASSERT是无效的,因为 stack_size不能再节里面赋值为0x100, stack赋值为'.'是有效的。 改为下面这样即可:

PROVIDE (__stack_size = 0x100);.stack :{  PROVIDE (__stack = .);  ASSERT ((__stack > (_end + __stack_size)), "Error: No room left for the stack");}

2.EXTERN(symbol symbol …)

把输出到目标文件的symbol强制变成未定义的symbol,这样做可以触发外部标准库的连
接,一个EXTERN里面可以写很多符号,EXTERN也可以写很多条。EXTERN命令的效果跟‘
-u’命令行参数一样。

3.FORCE_COMMON_ALLOCATION

强制链接器给common symbol分配空间,即使通过'-r'指定了重定向文件。等同于
'-d'命令行参数。

4.INHIBIT_COMMON_ALLOCATION

强制链接器给common symbol分配地址,即使是非重定向文件,等同于
‘--no-define-common’

5.FORCE_GROUP_ALLOCATION

强制链接器把节组(section group)里面的节跟普通输入节一样处理,等同于
‘--force-group-allocation’

6.INSERT [ AFTER | BEFORE ] output_section

INSERT可以使‘-T’不覆盖默认的脚本,而是把指定脚本的节插入在默认脚本的节之前
或之后。

SECTIONS{  OVERLAY :  {    .ov1 { ov1*(.text) }    .ov2 { ov2*(.text) }  }}INSERT AFTER .text;

7.NOCROSSREFS(section section …)

NOCROSSREFS用在输出节上,告诉链接器当引用不确定的节的时候,发生错误。

8.NOCROSSREFS_TO(tosection fromsection …)

NOCROSSREFS_TO命令告诉链接器,告诉链接器当一个引用有多个节对应的时候,发生错误。

9.OUTPUT_ARCH(bfdarch)

指定特殊的机器架构,参数是BFD库中的参数,通过objdump的'-f'参数可以看到使用的架构。

10.LD_FEATURE(string)

改变链接器的行为,如果string的值为"SANE_EXPR",那么所有的节里面的地址都会被
当成绝对地址。

比如:

SECTIONS  {    . = 0x100;    __executable_start = 0x100;    .data :    {      . = 0x10;      __data_start = 0x10;      *(.data)    }    …  }

.data节里面的‘.’和__data_start的地址本来是相对于.data节的,但是如果使用了
LD_FEATURE(string),那么它们的地址将变成绝对地址。相对于全局。

八、符号赋值

1.像C一样简单的赋值

symbol = expression ;symbol += expression ;  symbol -= expression ;  symbol *= expression ;  symbol /= expression ;  symbol <<= expression ;  symbol >>= expression ;  symbol &= expression ;  symbol |= expression ;  

2.HIDDEN

格式:HIDDEN(symbol = expression)
定义成HIDDEN的符号不会被输出到目标文件,如:

HIDDEN(floating_point = 0);  SECTIONS{  .text :    {      *(.text)      HIDDEN(_etext = .);    }  HIDDEN(_bdata = (. + 3) & ~ 3);  .data : { *(.data) }}

3.PROVIDE

格式:PROVIDE(symbol = expression)
定义一个输入文件里面未定义的符号,如:

SECTIONS{  .text :    {      *(.text)      _etext = .;      PROVIDE(etext = .);    }}

etext可以在输入文件中引用,如果输入文件中也定义了etext,那么优先使用输入文件
中的,但是如果输入文件中定义了_etext,连接的时候就会报多重定义的错误。

4.PROVIDE_HIDDEN

跟PROVIDE功能类似,但不会输出到目标文件。

5.源码引用

在高级语言,比如C语言中引用链接脚本中定义的符号的时候,是不能获取到符号的值
的,只能获取到符号的地址。

例如:

start_of_ROM   = .ROM;end_of_ROM     = .ROM + sizeof (.ROM);start_of_FLASH = .FLASH;

要在C语言中把.ROM里面的内容全部拷贝到.FLASH中,那么可以写成:

  extern char start_of_ROM, end_of_ROM, start_of_FLASH;  memcpy (& start_of_FLASH, & start_of_ROM, & end_of_ROM - & start_of_ROM);

或者:

  extern char start_of_ROM[], end_of_ROM[], start_of_FLASH[];  memcpy (start_of_FLASH, start_of_ROM, end_of_ROM - start_of_ROM);

九、SECTIONS命令

格式:

SECTIONS{  sections-command  sections-command  …}

每一个SECIONS的组成都有:
a.ENTRY命令
b.符号排列
c.输出节描述
d.覆盖描述

如果链接文件中没有定义SECTIONS,那么输入文件中的节就会原封不动的输出到目标文
件中。

1.输出节的描述

输出节的完整描述如下:

section [address] [(type)] :  [AT(lma)]  [ALIGN(section_align) | ALIGN_WITH_INPUT]  [SUBALIGN(subsection_align)]  [constraint]  {    output-section-command    output-section-command    …  } [>region] [AT>lma_region] [:phdr :phdr …] [=fillexp] [,]

大部分的属性实际使用的时候不会用到。

1.格式要求:

(1)section 后面必须有一个空格。
(2)':'‘{’‘}’是必需的。
(3)如果使用了[=fillexp],则末尾必须加','

2.section

section是输出节的名称。包含哪些输出节(如:.text,.data,.bss),是由输出文件
的格式决定的。

3.[address]

address是输出节的VMA(the virtual memory address)的表达式,可写可不写。

4.[(type)]

输出节的类型,如果没写,则根据输入节的类型来决定。
NOLOAD 禁止加载节,程序运行时不会加载。
READONLY 只读节
DSECT/COPY/INFO/OVERLAY
不同平台可能用的不一样,这些类型效果一样,就是程序运行时不会自动分配内存空间。

5.[AT(lma)]

每个节都有一个虚拟地址和加载地址,虚拟地址VMA就是节加载到内存中的地址,加载
地址LMA就是在目标文件中的地址。

LMA可以通过关键字AT或AT>来设置,但不是必须的要设置的。AT和AT>的区别是,AT的
参数是表达式,而AT>的参数是一块内存区域。

6.[ALIGN(section_align) | ALIGN_WITH_INPUT]

强制输出节地址对齐。

7.[SUBALIGN(subsection_align)]

强制输入节地址对齐。

8.[constraint]

输出节限制条件,通过关键字ONLY_IF_RO/ONLY_IF_RW可以指定只有当输入节是只读或
者可读可写的时候才创建。

9.[>region]

输出节的内存区域,>region可以指定输出节的内存区域。

10.[AT>lma_region]

设置输出节所在的LMA的存储区域。

11.[:phdr :phdr …]

设置输出节所属的段。
例如:

PHDRS { text PT_LOAD ; }SECTIONS { .text : { *(.text) } :text }

12.[=fillexp]

设置输出节间隙填充值,由于对齐要求,输出节和节之间可能由一些间隙,这些间隙默
认用0填充,也可以自己设置。

例如:

SECTIONS { .text : { *(.text) } =0x90909090 }

13.output-section-command格式:

a.符号分配
b.输入节描述
c.直接包含是输出节的数据
d.特殊的输出节关键字

14.输入节描述

输入节节描述告诉链接器,哪些输入节被放置到当前输出节,以及输出节里面输入节的
排序和对齐方式等。

a.基本的输入节描述
输入节描述由输入文件名称加上输入节名称的集合组成。

格式为:输入文件名称(输入节名称1 ... 输入节名称n)

数据文件名称可以是全称,也可以是通配符。 比如:
*(.text)意思是所有输入文件中的.text节。

  • EXCLUDE_FILE
    EXCLUDE_FILE 可以通过通配符来排除匹配的文件,如:
    EXCLUDE_FILE ( crtend.o otherfile.o) (.ctors) 意思是排除 ( crtend.o otherfile.o),而包含 (.ctors)。
    EXCLUDE_FILE 也可以放在节列表里面,如: (EXCLUDE_FILE ( crtend.o otherfile.o) .ctors)结果也一样。 EXCLUDE_FILE 也支持两种写法: (.text .rdata) (.text) (.rdata)
    不同的是, (.text .rdata)在目标文件中是乱序排列的,而 (.text) (.rdata)是顺序排列的。 下面的写法都是支持的: (EXCLUDE_FILE ( somefile.o) .text .rdata) (EXCLUDE_FILE ( somefile.o) .text EXCLUDE_FILE ( somefile.o) .rdata)
    EXCLUDE_FILE ( somefile.o) (.text .rdata)

  • INPUT_SECTION_FLAGS 使用INPUT_SECTION_FLAGS可以根据节头的标志来决定是否包含,如:

SECTIONS {  .text : { INPUT_SECTION_FLAGS (SHF_MERGE & SHF_STRINGS) *(.text) }  .text2 :  { INPUT_SECTION_FLAGS (!SHF_WRITE) *(.text) }}

.text输出节里面包含所有节头部带有SHF_MERGE和SHF_STRINGS标志的 (.text),而. text2里面包含所有节头SHF_WRITE被清除掉的 (.text)。

也可以使用':'来指定归档文件(*.a)里面的输入文件。‘:’前后无空格。 ‘archive:file’ 配置归档文件里面所有的输入文件。 ‘archive:’ 匹配所有的归档文件 ‘:file’ 匹配所有不在归档文件里面的输入文件。

b.输入节的通配符 ‘*’ 匹配任意个字符 ‘?’ 匹配单个字符 ‘[chars]’ matches a single instance of any of the chars; the ‘-’
character may be used to specify a range of characters,
as in ‘[a-z]’ to match any lower case letter
匹配单个实例字符,'-'可指定范围,比如[a-z]。
‘\’
换行符
如:

SECTIONS {  .text : { *(.text) }  .DATA : { [A-Z]*(.data) }  .data : { *(.data) }  .bss : { *(.bss) }}

c.输入节的Common符号
输入文件中注释一般放在.COMMON节里面,可以把.COMMON端放在输出文件的.bss节里面。如:
.bss { (.bss) (COMMON) }

d.输出节的数据 可以通过BYTE, SHORT, LONG, QUAD, or SQUAD这些关键字向输出节输出指定字节
的数据,输出数据的同时,位置计数器也会自动增加。

比如: BYTE(1) LONG(addr) 意思是向目标文件输出一个字节的1,然后再输出2个字节的addr的值,这些关键字只能
在输入节里面使用,如:
SECTIONS { .text : { (.text) } LONG(1) .data : { (.data) } }是错误的,而 SECTIONS { .text : { (.text) ; LONG(1) } .data : { (.data) } }就是正确的。 填充内存,FILL(value)意思是用value填充输出节FILL之后的部分。

e.输出节关键字 CREATE_OBJECT_SYMBOLS 为每个输入文件创建一个符号,符号名就是文件名,这个命令是a.out特有的,其他格
式目标文件不支持这个命令。

CONSTRUCTORS 当目标文件为a.out的格式的时候,链接器使用一个不常用的结构来支持C++的全局构造
函数和析构函数,如果链接不支持的特有的节的时候,比如ECOFF和XCOFF,链接器会自
动把它当成是C++全局构造函数和析构函数的名字。对于这些目标文件格式,
CONSTRUCTORS’命令告诉连接器把构造函数信息放到‘CONSTRUCTORS’命令出现的那个
输出端中,其他格式的目标文件,‘CONSTRUCTORS’命令是无效的。
如: CTOR_LIST = .; LONG(( CTOR_END - CTOR_LIST ) / 4 - 2) (.ctors) LONG(0) CTOR_END = .; DTOR_LIST = .; LONG(( DTOR_END - DTOR_LIST ) / 4 - 2) (.dtors) LONG(0) DTOR_END = .;

posted @ 2022-10-17 11:11  zephyr~  阅读(459)  评论(0编辑  收藏  举报