ld script 学习笔记

来源:互联网 发布:数据脱敏是什么意思 编辑:程序博客网 时间:2024/05/24 07:26

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,就是SECTIONScommand,
SECTIONScommand用来描述输出文件的内存布局,例如:

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 regionMEMORY命令创建

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 directlyspecial 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.ootherfile.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_MERGESHF_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不分配内存 */DSECTCOPYINFOOVERLAY

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的第一个空闲地址

如果ATAT>都没有指定,那么该输出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
phdrprogram headersegment是同义词,对于可执行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一样
不建议这么做

原创粉丝点击