GNU ld链接脚本快速参考

gnu ld链接脚本快速参考:
简单的链接脚本示例:
SECTIONS
{
    . = 0x10000;
    .text : { *(.text) }
    . = 0x8000000;
    .data : { *(.data) }
    .bss : { *(.bss) }
}
设置执行入口点:
ENTRY(symbol)
设置执行入口点的方法有:
1.-e 命令行参数
2.ENTRY(symbol)
3.start符号的值
3.代码段起始的字节
4.0地址
关于文件处理的命令:
INCLUDE filename
注意:深度最多10层,文件先从当前目录查找,再到-L指定目录查找
INPUT(file,file,...)
INPUT(file file ...)
INPUT(-lfile) ld 将链接 libfile.a
GROUP(file,file, ...)
GROUP(file file...)
以上两个和INPUT类似 除了文件都是归档文件。
OUTPUT(filename) 同命令行参数‘-o filename’,如果同时存在,则命令行参数优先。
SEARCH_DIR(path) 同‘-L path’
STARTUP(filename) 同INPUT,除了filename将第一次链接。

输出文件格式命令
OUTPUT_FORMAT(bfdname) 同命令行参数‘--oformat bfdname’
OUTPUT_FORMAT(default,big,little) 通过命令行参数‘-EB’指定big格式,
‘-EL’指定little格式,命令行不指定默认是default格式。
TARGET(bfdname)命令指定读输入文件时的格式,它影响随后的INPUT和GROUP命令。
如果使用TARGET而未用OUTPUT_FORMAT则最后输出文件格式和最后一个TARGET指定格式一致。
REGION_ALIAS(alias, region) 为内存域分配别名

举例:
Section     Variant A         Variant B         Variant C
.text         RAM             ROM             ROM
.rodata     RAM             ROM             ROM2
.data         RAM             RAM/ROM         RAM/ROM2
.bss         RAM             RAM             RAM

Variant A:    linkcmds.memory:        
    MEMORY
    {
        RAM : ORIGIN = 0, LENGTH = 4M
    }
    REGION_ALIAS("REGION_TEXT", RAM);
    REGION_ALIAS("REGION_RODATA", RAM);
    REGION_ALIAS("REGION_DATA", RAM);
    REGION_ALIAS("REGION_BSS", RAM);
Variant B:    linkcmds.memory:
    MEMORY
    {
        ROM : ORIGIN = 0, LENGTH = 3M
        RAM : ORIGIN = 0x10000000, LENGTH = 1M
    }
    REGION_ALIAS("REGION_TEXT", ROM);
    REGION_ALIAS("REGION_RODATA", ROM);
    REGION_ALIAS("REGION_DATA", RAM);
    REGION_ALIAS("REGION_BSS", RAM);    
Variant C:    linkcmds.memory:
    MEMORY
    {
        ROM : ORIGIN = 0, LENGTH = 2M
        ROM2 : ORIGIN = 0x10000000, LENGTH = 1M
        RAM : ORIGIN = 0x20000000, LENGTH = 1M
    }
    REGION_ALIAS("REGION_TEXT", ROM);
    REGION_ALIAS("REGION_RODATA", ROM2);
    REGION_ALIAS("REGION_DATA", RAM);
    REGION_ALIAS("REGION_BSS", RAM);
mylds.lds链接文件内容如下:
INCLUDE linkcmds.memory  /* 系统特定链接文件 */
/* 通用链接脚本 */
    SECTIONS
    {
        .text : {
            *(.text)
        } > REGION_TEXT
        
        .rodata : {
            *(.rodata)
            rodata_end = .;
        } > REGION_RODATA
        
        .data : AT (rodata_end) {
            data_start = .;
            *(.data)
        } > REGION_DATA
        
        data_size = SIZEOF(.data);
        data_load_start = LOADADDR(.data);
        
        .bss : {
            *(.bss)
        } > REGION_BSS
    }
通用系统初始化拷贝数据段.data到RAM:
#include <string.h>
extern char data_start [];
extern char data_size [];
extern char data_load_start [];
void copy_data(void)
{
    if (data_start != data_load_start)
    {
        memcpy(data_start, data_load_start, (size_t) data_size);
    }
}
另外一些链接脚本命令:
ASSERT(exp,message) 如果exp为0,则产生链接错误代码并打印message
EXTERN(symbol symbol...) 同-u命令行参数
FORCE_COMMON_ALLOCATION  同命令行参数 -d, 让ld为符号分配空间
INHIBIT_COMMON_ALLOCATION 禁止分配
INSERT [ AFTER | BEFORE ]output_section
在output_section之后(之前)插入所有之前的链接脚本语句,并且不覆盖默认链接脚本
例如:
SECTIONS
{
    OVERLAY : {
        .ov1 { ov1*(.text) }
        .ov2 { ov2*(.text) }
    }
}
INSERT AFTER .text;

NOCROSSREFS(section section...) 指定的section发生重叠则ld报错
OUTPUT_ARCH(bfdarch) 指定特殊的输出体系结构
你可以用 objdump -f 查看 object文件的体系结构

给标识符赋值:
symbol = expression ;
symbol += expression ;
symbol -= expression ;
symbol *= expression ;
symbol /= expression ;
symbol <<= expression ;
symbol >>= expression ;
symbol &= expression ;
symbol |= expression ;
注意分号不能少
例如:
floating_point = 0; /* defined as zero */
SECTIONS
{
    .text : {
        *(.text)
        _etext = .;  /* defined as the address following the last ‘.text’ input section */
    }
    _bdata = (. + 3) & ~ 3; /* defined as the address following the ‘.text’ output section aligned upward to a 4
byte boundary */
    .data : { *(.data) }
}

PROVIDE(symbol=expression) 定义一个标识符只有当该标识符在脚本包含的任何object未定义时,
如果在脚本包含的object文件中已定义,则脚本中引用已定义的。
例如:
SECTIONS
{
    .text : {
        *(.text)
        _etext = .;
        PROVIDE(etext = .);
    }
}

PROVIDE_HIDDEN 与PROVIDE相同,对于ELF,标识符将被隐藏并且不会输出到目标文件

在链接脚本中标识符不同于高级语言中变量的声明。
编译器通常会把变量名函数名等标识符转换成不同的名字存储于标识符表中。
因此在高级语言代码中使用的变量的名字和脚本中定义的名字会产生不一致。
例如C语言中:
    extern int foo;
在链接脚本中可能定义为:
    _foo = 1000;
在高级语言中例如C语言
当一个标识符被定义时,发生了两件事:
1. 编译器为保存标识符的值,在程序存储区预留足够的空间
2. 编译器在标识符表中创建一个条目,用来存储标识符的地址
当程序引用一个标识符时,编译器产生汇编代码,此代码首先
在标识符表中获取存储它的内存块地址,然后从内存中读取它的值。

链接脚本中的标识符的定义:
在标识符表中创建一个条目,但不分配任何存储空间。
也就是说标识符是一个地址,它没有值。

当你在C中使用链接脚本中定义的标识符时,你只能使用它的地址,绝对不能尝试使用它的值。
例如:你想把.ROM段中的内容拷贝到.FLASH段,链接脚本如下:
    start_of_ROM = .ROM;
    end_of_ROM = .ROM + sizeof (.ROM) - 1;
    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);
注意:使用取址操作符"&"

SECTIONS中的命令:
格式如下:
SECTIONS
{
    sections-command
    sections-command
    ...
}
每个sections-command可以是一下任意:
1. an ENTRY command
2. a symbol assignment
3. an output section description
4. an overlay description

输出段描述:
section [address] [(type)] :
[AT(lma)]
[ALIGN(section_align)]
[SUBALIGN(subsection_align)]
[constraint]
{
    output-section-command
    output-section-command
    ...
} [>region] [AT>lma_region] [:phdr :phdr ...] [=fillexp]

output-section-command可以是:
1. a symbol assignment
2. an input section description
3. data values to include directly
4. a special output section keyword

输出段名字:形式如 .section
名字和输出文件格式相关
例如ELF格式('.text'、'.data'、'.bss'等)

输出段地址:是一个输出段的虚拟地址的表达式(VMA)
.text . : { *(.text) } /*  设置地址为当前计数值 */
.text : { *(.text) }   /*  设置地址为,当前计数值依据".text"输入段,按照严格地址对齐后的值 */
.text ALIGN(0x10) : { *(.text) } /* 地址为按照16字节对齐后的值,地址低四位为0 */

输入段描述:
例如:
*(.text)
*(EXCLUDE_FILE(*crtend.o *otherfile.o) .ctors) 去掉文件*crtend.o和*otherfile.o
*(.text .rdata) /* 第一个.o(.text .rdata) 第二个.o(.text .rdata) ... */
*(.text) *(.rdata) /* 第一个.o(.text) 第二个.o(.text) ... 第一个.o(.rdata) 第二个.o(.rdata) ...*/

指定特定文件的某个段:
data.o(.data)
'archive:file' 匹配在归档中的文件
'archive:' 匹配整个归档
':file' 匹配不在归档中的文件
archive和file能包含shell统配符
注意:冒号两边不能加空格

输入段通配符:
在输入段描述中,文件名或段名都可以是通配符。
'*'       匹配所有字符
'?'      匹配任何单个字符
'[chars]' 匹配在某个范围的一个字符
'\'       转义之后的一个字符
SORT_BY_NAME 可以对输入文件按文件名排序(升序)
例如:SORT_BY_NAME(.text*)
SORT_BY_ALIGNMENT与SORT_BY_NAME是很相似的。
不同之处是SORT_BY_ALIGNMENT按照对齐方式进行升序排列。
SORT是SORT_BY_NAME的别名。
1. SORT_BY_NAME(SORT_BY_ALIGNMENT(wildcard section pattern)). 先按名字排,再按对齐方式排
2. SORT_BY_ALIGNMENT(SORT_BY_NAME(wildcard section pattern)). 先按对齐方式排,在按名字排
3. SORT_BY_NAME(SORT_BY_NAME(wildcard section pattern)) 和SORT_BY_NAME(wildcard section pattern) 一样
4. SORT_BY_ALIGNMENT(SORT_BY_ALIGNMENT(wildcard section pattern)) 和 SORT_BY_ALIGNMENT(wildcard section pattern) 一样
5. 其他所有嵌套的段排序命令是非法的.

如果在命令行和链接脚本里都指定排序方式:
1. SORT_BY_NAME(wildcard section pattern ) with ‘--sort-sections alignment’ 同 SORT_BY_NAME(SORT_BY_ALIGNMENT(wildcard section pattern)).
2. SORT_BY_ALIGNMENT(wildcard section pattern) with ‘--sort-section name’ 同 SORT_BY_ALIGNMENT(SORT_BY_NAME(wildcard section pattern)).
如果链接脚本中段排序命令是嵌套的,则命令行段排序参数无效。

如果你对段排序后段的顺序有困惑,可以使用"-M" ld选项参数来产生一个 map 文件。
此文件中有准确的说明:输入段怎么样映射到输出段

输入段共同的标识符
段名通常是"COMMON"
通常把COMMON放到.bss段中例如:
.bss { *(.bss) *(COMMON) }
有些格式的object文件有多个common symbol
例如mips ELF有standard common symbols(COMMON)和 small common symbols(.scommon)

输出段数据:
BYTE, SHORT, LONG, QUAD, SQUAD后面跟一个(),括号内是值
BYTE(1个字节), SHORT(2个字节), LONG(4), and QUAD(8).
例如:
BYTE(1)      /* 存一个字节,值为1 */
LONG(addr)   /* 存4个字节,值为addr */
当使用64位host或target, QUAD和SQUAD是相同的,他们都存一个8位或64位的值。
当时32位时,QUAD使用32位无符号扩展为64位,SQUAD使用32位符号扩展为64位。

注意:这些命令只能在段描述内部使用。
FILL命令为当前段填充某个值,包括由于对齐而产生的裂缝。
例如:
FILL(0x90909090) /* 填充为指定存储域为'0x90' */
和'=fillexp'输出段属性 类似,但FILL只影响段的一部分。
如果两个都使用了,那么FILL命令是优先的。

CREATE_OBJECT_SYMBOLS
让链接器为每个输入文件创建一个标识符,标识符的名字和文件名相同。

输出段属性:
section [address] [(type)] :
[AT(lma)]
[ALIGN(section_align)]
[SUBALIGN(subsection_align)]
[constraint]
{
output-section-command
output-section-command
...
} [>region] [AT>lma_region] [:phdr :phdr ...] [=fillexp]

[(type)]:
1. NOLOAD 指定段不可加载的,当代码运行时不会加载到内存。
2. DSECT COPY INFO OVERLAY这四个效果一样,为了提供向后兼容性,
它们很少用到,标志段是不可分配的,在代码运行时不会为段分配内存。

[AT(lma)]和[AT>lma_region]指定段的加载地址(LMA)
VMA是虚拟地址是代码实际运行时的地址。
LMA是加载地址是代码应该位于的地址。
通常情况下这两个地址是一样的。
指定段的加载地址是可选的。

如果没有以上两个属性,链接器会根据以下规则确定加载地址(LMA):
1. 如果该段有一个特定的VMA地址,那么这个地址也被用作LMA地址。
2. 如果该段是不可分配,则其LMA被设置为它的VMA。
3. 如果一个内存区域与目前段相兼容,并且该区域至少包含一个段,那么LMA被设置
,所以VMA和LMA之差是和所在区域最后一个段的VMA和LMA之差是一样的。
4. 如果未有内存区域被定义,那么默认覆盖整个地址空间的区域被用于上一步中。
5. 如果未发现合适的区域,或者前面没有段,那么LMA和VMA一样。

例如:
SECTIONS
{
    .text 0x1000 : {
        *(.text)
        _etext = . ;
    }
    .mdata 0x2000 : AT ( ADDR (.text) + SIZEOF (.text) ) {
        _data = . ;
        *(.data);
        _edata = . ;
    }
    .bss 0x3000 : {
        _bstart = . ;
        *(.bss) *(COMMON) ;
        _bend = . ;
    }
}
C语言:
extern char _etext, _data, _edata, _bstart, _bend;
char *src = &_etext;
char *dst = &_data;
/* ROM has data at end of text; copy it. */
while (dst < &_edata)
    *dst++ = *src++;
/* Zero bss. */
for (dst = &_bstart; dst< &_bend; dst++)
    *dst = 0;

[ALIGN(section_align)]:设置输出段的对齐方式

[SUBALIGN(subsection_align)]:设置输入段的对齐方式

[constraint]:指定输出段的约束
例如:
ONLY_IF_RO 只有输入段只读时才创建此输出段
ONLY_IF_RW 只有输入段读写时才创建此输出段

指定输出段的域
例如:
MEMORY {
    rom : ORIGIN = 0x1000, LENGTH = 0x1000
}
SECTIONS {
    ROM : { *(.text) } >rom
}

[:phdr :phdr ...]:把一个段分配到之前定义的段内。
你可以用 :NONE 告诉链接器不要把section放到其他segment内。
例如:
PHDRS {
    text PT_LOAD ;
}
SECTIONS {
    .text : {
        *(.text)
    } :text
}

[=fillexp]
例如:SECTIONS { .text : { *(.text) } =0x90909090 }

MEMORY命令:描述目标在内存区域块的位置和大小
MEMORY
{
    name [(attr)] : ORIGIN = origin, LENGTH = len
    ...
}
attr可以是:
‘R’ 只读段
‘W’ 读写段
‘X’ 可执行段
‘A’ 可分配段
‘I’ 初始化的段
‘L’ 同‘I’
‘!’ 对属性取反
ORIGIN:起始地址数字表达式,表达式只能是常量不能是任何标识符。
可以被缩写成org或者o,但不能是ORG。
LENGTH:大小
也是常量,可被缩写成len或者l。
可以在表达式中用ORIGIN(memory)和LENGTH(memory)获得一块内存的origin和length
例如:_fstack = ORIGIN(ram) + LENGTH(ram) - 4;

PHDRS 命令:
objdump -p获取代码加载到内存的信息。
PHDRS
{
    name type [ FILEHDR ] [ PHDRS ] [ AT ( address ) ] [ FLAGS ( flags ) ] ;
}
FILEHDR: the segment should include the ELF file header.
PHDRS: the segment should include the ELF program headers themselves.
type:
PT_NULL:表示一个未使用的程序头
PT_LOAD:表明此程序头描述了一个段从文件加载。
PT_DYNAMIC:表示动态链接信息都可以从一个段中找到。
PT_INTERP:表示该程序解释器的名字可从一个段中找到。
PT_NOTE:表示段包含附注信息。
PT_SHLIB:一个保留程序头类型,定义但没有由ELF ABI指定
PT_PHDR:定义程序头从该段中找。
例如:
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
    ...
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值