ld script 学习笔记
Linker Script
linker script 是用来控制链接过程的,它的主要目的有两个:
1. 描述如何将输入文件的section映射到输出文件
2. 控制输出文件的内存布局
链接器总是使用linker script,如果你没有指定,它使用默认的linker script,可以使用如下命令来显示默认的链接脚本:
$ ld --verbose
使用-T选项来指定自己的链接脚本
1. Basic Script Concepts
输入文件一般为object file,输出文件一般为executable file,在这里我们都称为object file
一个object file里包含一些section,可以使用如下命令查看:
$ readelf -S file.o
一个object file里还包含一些symbol,可以使用如下命令查看:
$ readelf -s file.o
2. Script Format
linker script 是文本文件
linker script 由一系列command组成,命令之间以分号;分隔,这些command用来命令(指示)ld干活
command 可以是:
1. 一个keyword后跟一些参数(可以理解为函数)
2. 对一个符号的赋值语句(必须以分号;
结尾)
3. 其他描述语句
可以将command
,语句
,理解为一个意思,区别只是命令
是语句块
或复合语句
,想象一下C语言中的函数就是语句块
脚本中使用的字符串可以直接输入,除非字符串中包含一些特殊字符(比如:逗号(,),空格等),那么要用双引号限定
脚本中的注释同C89,例如:/* this is a comment */
3. Simple Example
最简单的linker script 只有一条command,就是SECTIONS
command,
SECTIONS
command用来描述输出文件的内存布局,例如:
SECTIONS
{
. = 0x10000;
.text : { *(.text) }
. = 0x8000000;
.data : { *(.data) }
.bss : { *(.bss) }
}
这条命令由符号赋值语句
和输出section描述语句
组成
特殊符号.
称为location counter
,可以理解为当前的虚拟地址(VMA)
第一条赋值语句. = 0x10000
指示ld将当前地址设置为0x10000
第二条描述语句指示ld将所有输入文件的.text
段输出到输出文件的.text
段,*
匹配所有输入文件
第三条赋值语句. = 0x8000000
指示ld将当前地址设置为0x8000000
第四条描述语句指示ld将所有输入文件的.data
段输出到输出文件的.data
段
第五条描述语句指示ld将所有输入文件的.bss
段输出到输出文件的.bss
段
4. Simple Commands
4.1 Entry Point
程序执行的第一条指令称为entry point
指示ld设置程序entry point的命令为:
ENTRY(symbol)
同命令行选项-e
通过命令行选项的方式优先级高
4.2 File Commands
OUTPUT(filename)
同命令行选项-o
SEARCH_DIR(path)
同命令行选项-L
其它的文件处理命令不怎么常用,就不介绍了
4.3 Format Commands
OUTPUT_FORMAT(bfdname)
同命令行选项–oformat
常用的格式有
elf32-i386
binary
4.4 REGION_ALIAS
REGION_ALIAS(alias, region)
该命令可以为一个memory region
设置别名
memory region
由MEMORY
命令创建
MEMORY
{
RAM : ORIGIN = 0, LENGTH = 4M
}
REGION_ALIAS("REGION_TEXT", RAM);
MEMORY
命令创建一个名为RAM的内存区,起始内存地址为0,内存区大小为4M
REGION_ALIAS
命令为内存区RAM设置一个别名REGION_TEXT
4.5 Miscellaneous Commands
OUTPUT_ARCH(bfdarch)
该命令用来指定输出架构,例如:
OUTPUT_ARCH(i386)
指定输出架构为i386
5. Assignments
给一个symbol赋值,将会定义这个symbol,并将其放到输出文件的symbol table中,且是global scope
注意:给符号赋的值表示的是地址,给符号赋值只是把一个符号和一个地址关联起来
5.1 Simple Assignments
可以使用C语言中的赋值操作符给符号赋值,语法如下:
symbol = expression;
symbol += expression;
symbol -= expression;
symbol *= expression;
symbol /= expression;
symbol <<= expression;
symbol >>= expression;
symbol &= expression;
symbol |= expression;
赋值语句末尾的分号;
是必须的
赋值语句(除了对.
的赋值)可以出现在①SECTIONS
命令外,也可以出现在②SECTIONS
命令里,还可以出现在③output section description
中,例如:
floating_point = 0; /* ① */
SECTIONS
{
.text :
{
*(.text)
_etext = .; /* ③ */
}
_bdata = (. + 3) & ~3; /* ② */
.data : { *(.data) }
}
对.
的赋值语句只能出现在SECTIONS
命令中
5.2 HIDDEN
通过赋值语句定义的符号是全局的,如果想定义一个只在本链接脚本中可见的符号,可以通过HIDDEN(symbol = expression)
命令来定义,例如:
HIDDEN(start_of_data = 0x123456)
该命令类似C语言中在函数外定义一个static变量
5.3 PROVIDE
PROVIDE(symbol = expression)
该命令当下面两个条件同时成立时,定义symbol:
1. 有其他object file引用该symbol
2. symbol没有被任一object file定义
5.4 PROVIDE_HIDDEN
PROVIDE_HIDDEN(symbol = expression)
该命令同PROVIDE
,区别是PROVIDE
定义的符号是全局,PROVIDE_HIDDEN
定义的符号只在本脚本中可见
5.5 Source Code Reference
因为链接脚本中定义的符号只是代表一个内存地址,所以在源代码中引用链接脚本中定义的符号要求只使用符号的地址
举例:
在链接脚本中有如下定义:
start_of_ROM = .ROM;
end_of_ROM = .ROM + sizeof (.ROM);
start_of_FLASH = .FLASH;
在C语言代码中可以这样引用:
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);
6. SECTIONS
SECTIONS
命令告诉ld如何将输入section映射到输出section,以及如何将输出section放到内存
SECTIONS
命令的格式如下:
SECTIONS
{
sections-command
sections-command
...
}
sections-command
可以是如下语句:
ENTRY命令
符号赋值语句
输出section描述语句
overlay描述语句
6.1 输出section描述语句
输出section描述语句告诉ld在内存中如何布局你的输出section
输出section描述语句的完整格式如下:
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] [,]
output-section-command
可以是如下语句:
符号赋值语句
输入section描述语句
data values to include directly
special output section keyword
6.2 Output Section Name
输出section的名字section
必须是输出格式支持的section name,比如a.out格式只支持.text
, .data
, .bss
三个
对于elf格式,可以是任意名字,但是如果名字中包含特殊字符,比如逗号,
,则要用双引号限定
还有一个特殊的section name: /DISCARD/
,在后面介绍
6.3 Output Section Address
输出section地址是指输出section的虚拟地址VMA
,这个address
是可选的。如果指定address
,那么address
即为该输出section的地址。如果未指定address
,那么该输出section的地址根据以下情况而定:
1. 如果为该输出section设置了memory region
,那么该输出section会被放到这个memory region
里,该输出section的地址为这个memory region
里的第一个空闲地址
2. 如果脚本中使用了MEMORY
命令创建了一些memory region
,那么该输出section会被放到第一个与该输出section 属性兼容(attributes compatible)的memroy region
里,该输出section的地址为这个memory region
里的第一个空闲地址
3. 如果上面两种情况都不满足,那么该输出section的地址为location counter
的当前值加上为了满足对齐要求的一个调整值
例如:
.text . : { *(.text) }
.text : { *(.text) }
上面那条语句设置.text
的地址为location counter
的当前值
下面那条语句设置.text
的地址为location counter
的当前值加上一个对齐调整
如果你想让一个输出section 16字节对齐,你可以这样写:
.text ALIGN(16) : { *(.text) }
这是因为ALIGN
命令返回(loation counter
的当前值加上为满足对齐的一个调整值)作为结果
6.4 Input Section Description
输入section描述告诉ld如何将输入section映射到输出section
6.4.1 Input Section Basics
输入section描述格式如下:
filename [(section1 section2 section3 ...)]
文件名和section名都可以是通配符,例如:
*(.text)
*
是一个通配符,匹配任意文件
如果要排除某些文件可以用EXCLUDE_FILE
命令,例如:
EXCLUDE_FILE (*crtend.o *otherfile.o) *(.ctors)
*(EXCLUDE_FILE (*crtend.o *otherfile.o) .ctors)
在这个例子里上面两条命令等价,意思是:匹配所有文件(除了crtend.o
和otherfile.o
)的 .ctors
section
把EXCLUDE_FILE
放在括号外,则对其后的所有section都起作用
把EXCLUDE_FILE
放在括号里,则只对其后的第一个section起作用
对于elf格式object file,还可以根据section header里的sh_flags属性来匹配输入section
elf 的 section header 结构如下:
typedef struct
{
Elf32_Word sh_name; /* Section name (string tbl index) */
Elf32_Word sh_type; /* Section type */
Elf32_Word sh_flags; /* Section flags */
Elf32_Addr sh_addr; /* Section virtual addr at execution */
Elf32_Off sh_offset; /* Section file offset */
Elf32_Word sh_size; /* Section size in bytes */
Elf32_Word sh_link; /* Link to another section */
Elf32_Word sh_info; /* Additional section information */
Elf32_Word sh_addralign; /* Section alignment */
Elf32_Word sh_entsize; /* Entry size if section holds table */
} Elf32_Shdr;
举例:
SECTIONS {
.text : { INPUT_SECTION_FLAGS (SHF_MERGE & SHF_STRINGS) *(.text) }
.text2 : { INPUT_SECTION_FLAGS (!SHF_WRITE) *(.text) }
}
上面的第一条输入描述语句表示匹配所有设置了SHF_MERGE
和SHF_STRINGS
的section
上面的第二条输入描述语句表示匹配所有未设置SHF_WRITE
的section
还可以匹配.a
文件中的.o
文件,格式如下:
archive:file
例如:
libc.a:printf.o(.text)
libc.a:(.text)
:file.o(.text)
第一条语句表示匹配libc.a里的printf.o文件
第二条语句表示匹配libc.a里的所有.o文件
第三条语句表示匹配非.a
里的某个文件file.o
其中archive和file都可以包含shell的文件替换通配符,比如(*
, ?
, []
)
archive:file
还可以出现在EXCLUDE_FILE
命令中
如果只指定输入文件名而没有指定输入section,例如:
data.o
则匹配输入文件中的所有section
6.4.2 Input Section Wildcards
在一个输入section描述语句中,输入文件名和输入section都可以包含通配符
通配符和unix shell中的文件替换通配符类似
*
匹配0个或0个以上的任意字符
?
匹配任意的单个字符
[chars]
匹配中括号中的字符,可以使用-
,例如:
[a-z] /* 匹配一个小写字母 */
通配符只匹配在命令行或INPUT
命令指定的输入文件
可以对输入的文件或section排序,例如:
.text : { SORT_BY_NAME(*)(.text) }
.text : { *(SORT_BY_NAME(.text*)) }
.data : { *(SORT_BY_ALIGNMENT(.data*)) }
如果同时指定命令行选项--sort-sections name
或--sort-sections alignment
,则脚本命令优先级高
可以用SORT_NONE
命令禁止对输入的section排序,例如:
*(SORT_NONE(.init))
6.4.3 Input Section for Common Symbols
许多object file format(包括elf)都没有common section,而只是把common符号放在符号表中,ld链接器认为common符号在COMMON
section中,所以我们可以像指定其他输入section一样来指定COMMON
section,例如:
.bss { *(.bss) *(COMMON) }
6.4.4 Input Section Keep
KEEP
命令防止ld消除某些section,例如:
KEEP(*(.init))
KEEP(SORT_BY_NAME(*)(.ctors))
6.4.5 Input Section Example
SECTIONS {
outputa 0x10000 :
{
all.o
foo.o (.input1)
}
outputb :
{
foo.o (.input2)
foo1.o (.input1)
}
outputc :
{
*(.input1)
*(.input2)
}
}
这个例子很简单,不解释了
6.5 Output Section Data
输出section数据直接存储命令
用来在某个输出section里直接存储数据,有以下几个命令:
BYTE(expr) /* 1 byte */
SHORT(expr) /* 2 bytes */
LONG(expr) /* 4 bytes */
QUAD(expr) /* 8 bytes */
举例:
SECTIONS
{
.text :
{
*(.text)
LONG(1)
}
.data :
{
*(.data)
}
}
6.6 Output Section Keywords
CONSTRUCTORS
命令
这个命令用在一些老式的不支持C++的格式,比如a.out
, ECOFF
, XCOFF
等,对于ELF
格式,ld,忽略这个命令
对于CONSTRUCTORS
就不做过多介绍了
6.7 Output Section Discarding
特殊的输出section名:/DISCARD/
被映射到输出section名为/DISCARD/
的所有输入section将会被丢弃,不会出现在输出object file中
6.8 Output Section Attributes
输出section描述语句的完整格式如下:
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
, address
, output-section-command
,下面介绍剩下的section attributes
6.8.1 Output section type
有下面几种类型:
/* 程序运行时,这种类型的section不会被加载到内存中 */
NOLOAD
/* 下面四种类型作用相同,都是是为了向后兼任,很少使用,他们的作用是:当程序运行时,这种类型的section不分配内存 */
DSECT
COPY
INFO
OVERLAY
ld通常会根据相应的输入section来设置输出section的attributes
可以通过显式指定输出section的type来override
默认行为,例如:
SECTIONS {
ROM 0 (NOLOAD) : { ... }
...
}
6.8.2 Output section LMA
每个输出section有一个虚拟地址(VMA)和一个加载地址(LMA)
VMA通过address
指定
LMA通过AT
或者AT>
指定
AT(addr)
:指定输出section的绝对加载地址addr
AT>memroy_region
:输出section被加载到指定memory_region
的第一个空闲地址
如果AT
和AT>
都没有指定,那么该输出section的加载地址根据以下情况而定:
1. 如果该输出section指定了VMA,那么LMA等于VMA
2. 如果该输出section不分配内存,那么LMA等于VMA
3. 如果有一个与该输出section兼容的memroy region
且这个memroy region
至少包含一个section
,那么LMA = VMA +/- abs(pre_LMA - pre_VMA)
4. 如果没有定义memory_region
,那么使用默认的,覆盖整个地址空间的memroy region
,使用该memory region
应用第3步
5. 如果第3步和第4步中的内存区都不包含一个section,那么LMA等于VMA
6.8.3 Forced Output Alignment
You can increase an output section’s alignment by using ALIGN
. As an alternative you can enforce that the difference between the VMA and LMA remains intact throughout this output section with the ALIGN_WITH_INPUT
attribute.
6.8.4 Forced Input Alignment
You can force input section alignment within an output section by using SUBALIGN
. The value specified overrides any alignment given by input sections, whether larger or smaller.
6.8.5 Output Section Constraint
ONLY_IF_RO
:所有的输入section都是只读时才创建输出section
ONLY_IF_RW
:所有的输入section都是可读写时才创建输出section
6.8.9 Output Section Region
>memory_region
这个语法表示把输出section分配给相应的memroy region
例如:
MEMORY { rom : ORIGIN = 0x1000, LENGTH = 0x1000 }
SECTIONS { ROM : { *(.text) } >rom }
6.8.10 Output Section Phdr
:phdr
语法把该输出section输出到对应的segment
phdr
即program header
和segment
是同义词,对于可执行object file,一个segment包含多个section,这些section具有相同的权限,即可执行、可读、可写等权限
6.8.11 Output Section Fill
=fillexp
该语法填充内存区里因对其要求而产生的一些gap
例如:
SECTIONS { .text : { *(.text) } =0x90909090 }
6.9 Overlay Description
不介绍了
7. MEMORY
MEMORY
命令用来定义memroy region
,例如:
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 */
ORIGIN
, org
, o
指定内存区起始地址
LENGTH
, len
, l
指定内存区大小
获取某个内存区起始地址命令:ORIGIN(memory_region)
获取某个内存区大小命令:LENGTH(memory_region)
8. PHDRS
PHDRS
命令,这个命令是ELF
格式专用的,格式如下:
PHDRS
{
name type [ FILEHDR ] [ PHDRS ] [ AT ( address ) ] [ FLAGS ( flags ) ] ;
}
默认情况下,ld创建合适的程序头
type
用来设置p_type
字段,flags
用来设置p_flags
字段
Elf32_Phdr
结构如下:
typedef struct
{
Elf32_Word p_type; /* Segment type */
Elf32_Off p_offset; /* Segment file offset */
Elf32_Addr p_vaddr; /* Segment virtual address */
Elf32_Addr p_paddr; /* Segment physical address */
Elf32_Word p_filesz; /* Segment size in file */
Elf32_Word p_memsz; /* Segment size in memory */
Elf32_Word p_flags; /* Segment flags */
Elf32_Word p_align; /* Segment alignment */
} Elf32_Phdr;
ELF详细的定义参考/usr/include/elf.h
使用示例:
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
...
}
9. VERSION
不介绍了
10. Expressions
linker script的表达式同C语言,linker script只支持整型表达式
11. Implicit Linker Scripts
可以在命令行上指定链接脚本,就像其他输入object file一样
不建议这么做