ILD

ld (3): 链接脚本
作者:Herbert Yuan 邮箱:yuanjp89@163.com
发布时间:2017-7-23 站点:Inside Linux Development

Introduction

每一个链接都是链接脚本(linker script)控制的,脚本是按照链接器命令语言(linker command language)书写的。

链接脚本的主要目的是描述输入文件的section如何映射到输出文件,以及如何控制输出文件的内存布局。除此之外,大多数链接脚本不做什么其它的事。然而如果需要,链接脚本也可以指导链接器执行其它许多操作。

链接器总是使用链接脚本,如果没有提供,它使用缺省脚本,使用--verbose命令行选项,可以显示缺省的链接脚本。一些命令如-r或者-N,会 影响缺省链接脚本。

可以使用-T选项指定自己的链接脚本,这么做时,你的脚本完全替换缺省链接脚本。


基本的链接脚本概念:

section:可参考本博客ELF分类中的文章。

input section:输入文件中的section。

output section:输出文件中的section。

loadable:当输出文件运行时,该section的内容加载内存中。

allocatable:该section没有内容,但是会分配内存,通常是.bss section。

每个可加载和可分配section有两个地址,第一个是VMA(virtual memory address),这是输出文件运行时,section的地址。第二个是LMA(load memory address),这是section要加载到的地址。大多数情况下,这两个地址相同。但是在一些嵌入式系统中,整个目标文件烧写到nor flash(指令可直接执行),只读段仍然在ROM中,但是变量加载到RAM中,此时VMA和LMA不同。


链接脚本格式:

链接脚本是文本文件,链接脚本由一系列命令组成。每一个命令或者是一个关键字,关键字可以带参数;或者是给符号赋值。可以用分号分开各个命令。注释的格式为 /* */。文件名和格式名可直接输入,如果有特殊字符,使用双引号。


简单的链接脚本例子:

最简单的链接脚本只有一个命令:"SECTIONS",这个命令用来描述输出文件的内存布局。

1
2
3
4
5
6
7
8
SECTIONS
{
    . = 0x10000;
    .text : { *(.text) }
    . = 0x8000000;
    .data : { *(.data) }
    .bss : { *(.bss) }
}

.符号是location counter,用于指定section的地址,SECTIONS命令开始时,其值为0,可以显式设置,如果没有设置,则按照section大小自动增长。.text定义一个ouput section。后面跟一个冒号,现在可以省略。大括号用来指定input sections,*是通配符,匹配任何文件名。表达式'*(.text)'表示输入文件的所有.text section。链接器保证output section满足对齐要求。


ENTRY命令

程序执行的第一条指令叫做entry point。ENTRY命令用于设置入口点。链接器按照下面的顺序寻找入口点。

  1. -e 命令行选项。

  2. ENTRY(symbol)链接脚本命令。

  3. 目标指定的符号,通常是start。

  4. .text的第一个字节。

  5. 0


文件相关的命令

INCLUDE,用来包含其它链接脚本。INCLUDE filename

INPUT,用来包含要链接的目标文件,INPUT(file, file, ...) 或者 INPUT(file file ...)。file可以为-lfile格式。

OUTPUT,用来指定输出文件,OUTPUT(file)

SEARCH_DIR,用来指定库搜索路径,同命令行-L。SEARCH_DIR(path)

STARTUP,STARTUP(file),指定第一个链接的文件。


内存区别名

REGION_ALIAS命令,用来给内存区指定一个别名,如 REGION_ALIAS("REGION_BSS", RAM)。通常用来将section放入一个指定的位置。


符号赋值

可以使用任何c语言赋值:

1
2
3
4
5
6
7
8
9
symbol = expression ; 
symbol += expression ; 
symbol -= expression ; 
symbol *= expression ; 
symbol /= expression ; 
symbol <<= expression ; 
symbol >>= expression ; 
symbol &= expression ; 
symbol |= expression ;

第一种定义符号的值。其它的情况,符号必须已有定义。特殊的符号.表示location counter,只能使用在SECTIONS命令中。表达式后面的分号是要求的。表达式的的定义参考后续小节。


HIDDEN命令

可以隐藏符号,使其在模块之外不可见,使用HIDDEN(symbol=expression)


PROVIDE/PROVIDE_HIDDEN命令

一些情况下,链接器可以定义一个目标文件没定义的符号,目标文件引用该符号。可以用PROVIDE(symbol=expression)。如果目标文件有定义,则会报错,此方法可以将一些变量定义放在链接阶段,在某些情况下非常有用。PROVIDE会把变量放在PROVIDE所在的段的位置。PROVIDE_HIDDEN和PROVIDE相似,但是隐藏起来,不导出。


高级语言引用

访问链接脚本定义的变量可能和直觉不同,首先上层语言的变量名和链接阶段的变量名可能不同,为了简单,这里考虑相同的情况。当上层语言定义一个变量时,两件事情发生:在程序内存中保留一些空间存储符号的值;在符号表创建一个条目存储符号的地址。当程序引用一个符号时,编译器产生访问符号表的代码,找到符号的内存地址,然后从内存地址读取数据。而

int *a = & foo;

在符号表寻找符号foo,得到他的地址,拷贝到变量a的内存块。然而链接脚本符号声明,只是在符号表创建一个条目,不分配任何的内存。因此他们是只是地址。例如:

foo = 1000;

在符号表创建一个叫foo的条目,地址为1000。但实际上地址1000没有任何东西,这意味着不能访问链接器脚本定义的符号的值。因此,当在源代码中使用链接器定义的符号时,应当总是使用符号的地址,而不要去尝试用它的值。例如,下面的例子将ROM section的内容拷贝到FLASH section。链接脚本包含:

1
2
3
    start_of_ROM = .ROM;
    end_of_ROM = .ROM + sizeof (.ROM) - 1;
    start_of_FLASH = .FLASH;

c源代码执行拷贝操作:

1
2
    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命令告诉链接器,如何映射输入section到输出section,如何将输出section放到内存。SECTIONS命令的格式:

1
2
3
4
5
6
SECTIONS
{
    sections-command
    sections-command
    ...
}

每个sections-command可能是下列之一:

ENTRY命令和符号赋值允许出现在SECTIONS命令中,是为了在这些命令中方便的使用location counter。这也使链接脚本更容易理解,因为你将这些命令放在输出文件布局中有意义的位置。

如果在链接脚本中,没有使用SECTIONS命令。链接器将每一个输入section放入一个唯一命名的输出section。按照其在第一个输入文件的顺序。当然也会合并输入section。第一个section的地址将为0。


输出section描述

完整的格式:

1
2
3
4
5
6
7
8
9
10
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]


大多数输出section不使用大多数section属性。section周围的空白是必须的。冒号和大括号也是必须的。换行和其它空白是可选的。每个output-section-command是下列之一:


Output section name

上述Ouput section description格式中的section,就是section的名字。


Output section addresses

上述Ouput section description格式中的addresses。是输出section的VMA的表达式。这个地址是可选的,如果提供,那么将严格设置为指定的地址。如果没有指定地址,地址被调整为满足输出section的对齐要求,按照下面的方法:

    - 如果为section设置了输出内存区,那么它的地址是region的下一个空闲地址。

    - 如果用MEMORY命令创建一系列内存区,那么第一个属性匹配的内存区被选择存放section。

    - 如果没有内存区,则选择location counter。

注意,下述是不同的。

1
2
.text . : { *(.text) }
.text   : { *(.text) }

前者完全是.的值,后者以.为参考,但是满足对齐要求。地址可以为任何表达式。


Input section description

大多数output-section-command是一个input section description。input section description是最基本的链接脚本操作。你使用output sections告诉链接器如何在内存中布局你的程序。使用input section descriptions告诉链接器如何映射输入文件到内存布局。输入section描述符有一个文件名,跟着一个section名的列表,列表用括号括起来,如

1
a.o(.text .rodata)

最常用的输入section描述符是使用一个特殊的名字来包含所有的输入section。例如,包含所有的输入的.text section。

1
*(.text)

通配符*表示所有的输入文件。如果要排除一些文件可以使用EXCLUDE_FILE,如下:

1
*(EXCLUDE_FILE (*ctrend.o *otherfile.o) .ctors)

有两种方法,包含多于一种section。

1
2
*(.text .rdata)
*(.text) *(.rdata)

不同之处是顺序,前者按文件,后者按section类型。为了根据section的flag包含,可以使用INPUT_SECTION_FLAGS。如:

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

也可以指定静态库中的文件。如archive:file,匹配库中的文件。archive是静态库的名字,file是库中文件的名字。archive,匹配整个库。:file,匹配所有非静态库中的文件。


通配符

* 匹配任何数量的字符。

? 匹配任何单个字符。

[chars] 匹配指定的字符。如[a-z]

\ 转义接下来的字符。

匹配文件名时,通配符不匹配/。但是单一的*是个例外。

当出现多次匹配时,选择第一次匹配。


排序

相关关键字有SORT_BY_NAME,SORT_BY_ALIGMENT,SORT_BY_INIT_PRIORITY。排序可以嵌套1层。

SORT_NONE可以取消命令行的--sort-sections选项。


Input Section for Common Symbols

通用符号通常没有一个特别的输入section。链接器对待common symbols好像他们都在一个名叫COMMON的section中。

大多数情况下,通用符号放在.bss section后,如下:

1
.bss { *(.bss) *(COMMON) }


input section and garbage collection

garbage collection就是删除不用的section,不输出到输出文件中,使用--gc-sections选项设置。但是可以使用KEEP来保留。如下:

1
KEEP(*(.init))


Output section data

可以使用BYTE, SHORT, LONG,QUAD或者SQUAD来在输出section中包含数据。每个关键字跟着一个括号括起来的表达来表示存储的值。存储的位置是当前location counter。如:

BYTE(1)

在64位机器上,QUAD和SQUAD是相同的,都是存储64位数据。在32位机器上,表达式的值是计算为32位,QUAD按0扩展64位。SQUAD按位扩展64位。如果输出文件有大小端,值将按其大小端存储。

注意:只能放在section描述中,而不是能section之间,如下将产生一个链接错误。

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

下面这个是正面的:

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


同时可以使用FILL命令来填充当前section。同样跟着一个括号表达式表示填充的值。section中所有未定义的区域被填充。如:

FILL(0x90909090)

FILL命令等同于输出section的=fillexp属性。但是其只影响FILL之后的区域。


Output section keywords

CREATE_OBJECT_SYMBOLS

CONSTRUCTORS

在ELF格式中,没用到。


Output section discarding

链接器不会创建没有内容的输出section。引用不在任何输入文件中存在的sections时,这很方便。如:

1
.foo : { *(.foo) }

如果所有的输入文件中,都没有.foo这个section,那么输出文件也将没有。在丢弃的output section,链接器忽略地址赋值。特殊的输出section名'/DISCARD/'用来丢弃输入section。所有在这个输出section中的输入section都不会包含在输出文件中。


Output section type

每个输出section可以有一个类型,类型是用圆括号括起来的关键字。定义了下列类型:

NOLOAD,section被标记为不可加载类型,程序运行时不会载入到内存。

DSECT/COPY/INFO/OVERLAY,几乎不使用,向后兼容。被标记为不分配。程序运行时不分配内存。


Output section LMA

每个section有一个虚拟地址VMA和一个加载地址LMA。加载地址是通过AT或者AT>关键字指定的。AT关键字跟一个表达式做参数表示家在地址。AT>关键字跟一个memory region名做参数,section的加载地址设置为区的下一个空闲地址,并满足对齐要求。

如果没有指定AT或者AT>,按下面的方式决定加载地址。

  1. 如果section有一个指定的VMA地址,使用这个地址作为LMA地址。

  2. 如果section不是allocatable,设置为VMA。

  3. 如果兼容本section的memory region找到,其region包含至少一个section。LMA被设置,VMA的差值等于LMA的差值。

  4. 如果没有memory region,这使用default region。该region覆盖整个地址空间,按照3的方式设置。

  5. 最后,使用VMA。


LMA通常用来创建ROM镜像。


Forced output alignment

ALIGN/ALIGN_WITH_INPUT


Forced Input alignment

SUBALIGN


Output section constraint

输出section只有在输入section全部为只读或者读写时才创建。ONLY_IF_RO/ONLY_IF_RW.


Output section region

使用>region,把一个section赋给之前定义的内存区。如:

1
2
MEMORY { rom : ORIGIN = 0x1000, LENGTH = 0x1000 }
SECTIONS { ROM : { *(.text) } >rom }


Output section phdr

可以把一个输出section赋给之前定义的程序段,使用:phdr。如果一个section被赋给一个或多个段,那么后续的section都将赋给那个段,除非显示的设置。

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


Output section fill

使用=fileexp的形式,指定填充内容。内存区中未定义的内容(如对齐导致的空白)都将被填充该内容,


Overlay description

overlay描述符用来实现不同的output section运行在相同的地址。OVERLAY命令用在SECTIONS命令中。语法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
OVERLAY [start] : [NOCROSSREFS] [AT ( ldaddr )]
{
    secname1
    {
        output-section-command
        output-section-command
        ...
    } [:phdr...] [=fill]
    secname2
    {
        output-section-command
        output-section-command
        ...
    } [:phdr...] [=fill]
    ...
} [>region] [:phdr...] [=fill]

除了OVERLAY,其它都是可选的。section定义和普通的SECTIONS命令里的一样。除了没有地址和没有内存区外。所有的sections有相同的起始地址,但是加载地址是连贯的。如果NOCROSSREFS关键字使用,则检查sections之间是否有相互的符号引用。如果有,链接器将报错。链接器为每个section自动定义两个符号。__load_start_secname和__load_stop_secname。表示加载地址的开始和结束。下面是一个例子:

1
2
3
4
5
OVERLAY 0x1000 : AT (0x4000)
{
    .text0 { o1/*.o(.text) }
    .text1 { o2/*.o(.text) }
}

.text0和.text1的起始地址为0x100,.text0加载到0x4000。.text0紧跟着.text0。下述符号将被定义:__load_start_text0 __load_stop_text0 __load_start_text1 __load_stop_text1。可以使用c代码,拷贝.text1到重叠区:

1
2
3
extern char __load_start_text1, __load_stop_text1;
memcpy ((char *) 0x1000, &__load_start_text1,
    &__load_stop_text1 - &__load_start_text1);

OVERLAY命令只是简化链接脚本,也可以用基本命令实现。如下:

1
2
3
4
5
6
7
.text0 0x1000 : AT (0x4000) { o1/*.o(.text) }
PROVIDE (__load_start_text0 = LOADADDR (.text0));
PROVIDE (__load_stop_text0 = LOADADDR (.text0) + SIZEOF (.text0));
.text1 0x1000 : AT (0x4000 + SIZEOF (.text0)) { o2/*.o(.text) }
PROVIDE (__load_start_text1 = LOADADDR (.text1));
PROVIDE (__load_stop_text1 = LOADADDR (.text1) + SIZEOF (.text1));
. = 0x1000 + MAX (SIZEOF (.text0), SIZEOF (.text1));


MEMORY命令

链接器的缺省配置允许分配所有可用的内存,可以使用MEMORY命令改变。MEMORY命令描述了目标中内存块的位置和大小。可以用来告诉链接器哪个内存区可以用,哪个不可以。然后把section放入指定的内存区。链接器将根据内存区设置section的地址。并且当内存区满时,给出警告。一个链接脚本最多包含一个MEMORY命令,但是在命令里面可以包含许多内存块。

1
2
3
4
5
MEMORY
{
    name [(attr)] : ORIGIN = origin, LENGTH = len
    ...
}

名字用来指向region。命令在链接脚本外无意义,名字也和符号冲突。名字也可以有别名(见REGION_ALIAS)。attr字符串是可选的。用来实现满足特定属性的输入section放入该内存区。该字符串只能包含下列字符:

'R',只读section。

'W',读写section。

'X',可执行section。

A,Allocatable section

I, Initialized section

L, 和I相同。

!,非。

如果有任何未映射(unmapped)的section匹配上述属性,则放入该内存区。ORIGIN是数字表达式,表示内存区的开始地址。表达式必须是常量,不能有任何符号。可简写为org或o。LENGTH是内存区长度的表达式。也必须是常量。可简写为len或l。


下面为例子:

1
2
3
4
5
MEMORY
{
    rom (rx) : ORIGIN = 0, LENGTH = 256K
    ram (!rx) : org = 0x40000000, l = 4M
}


一旦定义了内存区,可以指导链接器将输出section放入内存区,通过使用>region属性。如果没有指定地址,则放入内存区下一个可用的地址。可以访问内存区的地址和长度,通过表打死ORIGIN(memory)和LENGTH(memory),如:

1
_fstack = ORIGIN(ram) + LENGTH(ram) - 4;


PHDRS 命令

链接器缺省或创建合适的程序头,然而,你可能需要创建特定的程序头。这时可以使用PHDRS来实现。命令格式如下:

1
2
3
4
5
PHDRS
{
    name type [ FILEHDR ] [ PHDRS ] [ AT ( address ) ]
    [ FLAGS ( flags ) ] ;
}

PHDRS,FILEHDR, AT, FLAGS是关键字。

name,段的名字,只在SECTIONS命令中引用,不会放到输出文件中,也不会和符号、文件名、section名冲突。

type,段的类型,为下面关键字的值。

PT_NULL (0),未使用的程序头。

PT_LOAD (1),可加载段。

PT_DYNAMIC (2),动态链接信息段。

PT_INTERP (3),该段包含程序解释器名字。

PT_NOTE,包含note信息。

PT_SHLIB,ELF标准未定义。

PT_PHDR,包含程序头本身。

使用 :phdr 输出section属性,将一个输出section放入段中。将一个输出section放如多个段是常见的,通过重复使用 :phdr实现。当使用 :phdr将一个section放入段中后,后续的section都将放入相同的段,除非指定特定的段,这只是为了方便。可以使用:NONE属性告诉临界期不要将section放入任何段中。FILEHDR关键字表示段应该包含ELF头。PHDRS关键字表示应该包含ELF程序头本身。第一个PT_LOAD段,必须有一个上述关键字。使用AT命令,指定段的地址。链接器通常跟着成员section设置段flag。当然,也可以使用flags来显示的设置。


下面是一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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 */
    ...
    . = . + 0x1000; /* move to a new page in memory */
    .data : { *(.data) } :data
    .dynamic : { *(.dynamic) } :data :dynamic
    ...
}


VERSION命令

ELF支持符号版本(symbol versions),符号版本只在使用共享库时有用。动态链接器使用符号版本来选择特定版本的函数。你可以在主链接脚本中包含版本脚本,也可以也可应用版本脚本为一个隐含的链接脚本,也可以使用--version-script链接选项。VERSION命令的语法如下:

1
VERSION { version-script-commands }


表达式

和c表达式一致,所有的表达式评估为整数,且有相同的size。在32位target上为32位,64位target则为64位。可以在表达式中使用符号的值。


常量:

0开头的是8进制。0x或0X开头的是16进制。

h或H结尾的是16进制。o或O结尾的是8进制。b或B结尾的是二进制。d或D结尾的是10进制。

可以使用K或M结尾,表示1024和1024*1024.


符号常量

格式CONSTANT(NAME)。NAME只有两种:

MAXPAGESIZE/COMMONPAGESIZE。表示最大和缺省页大小。

如 .text ALIGN (CONSTANT(MAXPAGESIZE)) : { *(.text) }


符号名:

除非使用引号。符号名以字母、下划线和点开始,跟着字母、数字、下划线、点和连字符。

无引号的符号名不能和任何关键字冲突。

"SECTION" = 9


Orphan sections

孤儿section是指那些输入文件中的,在链接脚本中没有显示的放入输出文件中的section。链接器仍然会将这些section放入输出文件。链接器会猜想如何放置,链接器使用简单的启发式的方法。链接器将其放在相同属性的非孤儿section方面。如果没有足够的空间,将放在最后面。

ELF目标属性包括section类型和section flag。

如果孤儿section的名字符合C标识符的规范,则链接器提供两个符号:__start_SECNAME和__stop_SECNAME。表示孤儿section的开始地址和结束地址。以.开头的section名不符合c标识符规范。


The location counter

输出链接器变量. 总是包含当前输出位置。既然 . 总是指向一个输出section的位置。因此他可能只出现在SECTIONS命令内的表达式中。在表达式中,. 符号可以出现在普通符号允许的地方。

给 . 赋值将改变location counter。这可能导致output section的空洞。

. 为相对当前containing object的起始位置的字节偏移。在SECTIONS命令内,其起始地址为0,因此也未绝对地址。然而如果 . 用在section description内,. 为相对于section起始地址的偏移,不是一个绝对地址。如

1
2
3
4
5
6
7
8
9
10
11
12
13
SECTIONS
{
    . = 0x100
    .text: {
        *(.text)
        . = 0x200
    }
    . = 0x500
    .data: {
        *(.data)
        . += 0x600
    }
}

如上.text将从0x100开始。而且大小为 0x200。如果0x200小于 *(.text)的大小,将报错(因为这视图向后移动.)。.data从0x500开始。包含*(.data),后面在跟0x600的填充。


在output section语句之外将.赋值给符号可能有期望之外的结果。因为链接器可能将孤儿orphan放在某个位置,这可能无形影响.的值。如:

1
2
3
4
5
6
7
8
9
10
SECTIONS
{
    start_of_text = . ;
    .text: { *(.text) }
    end_of_text = . ;
 
    start_of_data = . ;
    .data: { *(.data) }
    end_of_data = . ;
}

链接器可能将.rodata放入下述位置。

1
2
3
4
5
6
7
8
9
10
11
SECTIONS
{
    start_of_text = . ;
    .text: { *(.text) }
    end_of_text = . ;
 
    start_of_data = . ;
    .rodata: { *(.rodata) }
    .data: { *(.data) }
    end_of_data = . ;
}

链接器将赋值和其它语句想象为属于之前的输出section。但是赋给.的语句想象为下一个输出section。所以为了避免上述问题。可以在分割处添加 .=.

1
2
3
4
5
6
7
8
9
10
11
12
SECTIONS
{
    start_of_text = . ;
    .text: { *(.text) }
    end_of_text = . ;
 
    .=.
    start_of_data = . ;
    .rodata: { *(.rodata) }
    .data: { *(.data) }
    end_of_data = . ;
}


操作符

链接器识别c算术运算符集合,绑定和优先级相同。


计算

链接器计算表达式非常懒,除非有绝对必要时,才会计算。

链接器需要一些信息,如第一个section的起始地址,内存区的地址和长度等,这些值会尽快计算。

然而其他值会在存储分配后计算。如果表达式的值需要,但是当前无法计算,将导致一个错误。


表达式的section

一个地址或者符号可以是section相对的或绝对的,一个section相对的符号是可重定向的。如果使用-r生成可重定向输出。后续的链接可能改变section相对符号的值。儿绝对符号在链接器阶段不变。

表达式中的某些术语是地址。如section相对符号和返回地址的内建函数。例如:ADDR,LOADADDR,ORIGIN和SEGMENT_START。其它术语主要数字后者返回非地址的内建函数,如LENGTH。

除非设置LD_FEATURE("SANE_EXPR"),数字和绝对符号被不同的对待,这依赖于他们的位置。为了和老版本的ld兼容。在输出section定义之外的表达式,所有数字当做绝对地址。如果给出了LD_FEATURE("SANE_EXPR"),绝对符号和数字被简单的对待为数字。例子:

1
2
3
4
5
6
7
8
9
10
11
12
SECTIONS
{
    . = 0x100;
    __executable_start = 0x100;
    .data :
    {
        . = 0x10;
        __data_start = 0x10;
        *(.data)
    }
    ...
}

第一个.和__executable_start是绝对地址。而第二.和__data_start是相对于.data的地址。

对于涉及数字、相对地址和绝对地址的表达式,ld按下面的规则评估。

  1. 绝对地址和数字的单操作符,两个绝对地址或两个绝对数的双操作符,或者一个绝对地址、一个数字。应用操作符为value(es).

  2. 相对地址的单操作符,两个相同section内的两个相对地址,或者一个相对地址、一个数字的双操作。应用操作符为地址的偏移部分。

  3. 其它双操作符,即两个不在同一个section的相对地址;相对地址和绝对地址的。在引用到操作符之前,先将非绝对量转化为绝对量


子表达式的结果如下:

可以使用内建的ABSOLUTE函数,将一个表达式强制为了绝对。例如,创建一个绝对符号,地址为输出section .data的最后。

1
2
3
4
SECTIONS
{
    .data : { *(data) _edata = ABSOLUTE(.); }
}

使用LOADADDR也将一个表达式强制为绝对。


内置函数 builtin functions

ABSOLUTE(exp)

    返回表达式的绝对值(non-relocatable)。主要在section定义中,将一个绝对地址赋给符号。


ADDR(section)

    返回名叫section的section的VMA地址。


ALIGN(align) ALIGN(exp, align)

    前者返回.按align对齐的地址。后者返回表达式按align对齐的地址。


ALIGNOF(section)

    返回setion的对齐要求。


DATA_SEGMENT_ALIGN(maxpagesize, commonpagesize)

DATA_SEGMENT_END(exp)

DATA_SEGMENT_RELRO_END(offset, exp)


DEFINED(symbol)

    符号是否在全局符号表中定义,请定义在该语句之前。


LENGTH(memory)

    返回内存区的长度


LOADADDR(section)

    返回section的LMA地址


LOG2CEIL(exp)

MAX(exp1, exp2)

MIN(exp1,exp2)


NEXT(exp)

    和ALIGN基本相同,除非使用MEMORY命令定义不连续的内存。


ORIGIN(memory)

    返回memory的origin。


SEGMENT_START(segment, default)

    返回段的基地址,如果没有-T选项设置,则返回default。


SIZEOF(section)

    返回section的大小。


SIZEOF_HEADERS

    返回文件头的大小。

Copyright © linuxdev.cc 2017-2024. Some Rights Reserved.