自定义节

__attribute__((section(".testsec"))) int global = 42;
#pragma section ".testsec"
int global1 = 41;
#pragma section
SECTIONS 
{
. = 0x80001000;
.testsection : { *(.testsec) }
}

上面编译global到.testsection

以下编译平台为hightech,编译工具为基于gcc的tricore-gcc和tricore-ld。

链接文件基本概念

简单例子

创建一个新的链接文件ldtest1.ld,输入如下内容:

SECTIONS
{
. = 0x10000000;
.text : { *(.text) }
. = 0x80000000;
.data : { *(.data) }
.bss : { *(.bss) }
}

SECTION命令用来描述输出文件的内存布局。
上面的特殊符号".“是一个定位计数器。”."默认值是0,这里第一句给它赋值为0x10000000,
第二行定义了一个输出节.text,.text表示运行代码的二进制数据会放在这里面,*(.text)表示所有的输入文件的text节,*是一个通配符,表示所有文件。
在.text定义的时候定位计数器的值是0x10000000,链接器会把.text的起始地址设为0x10000000。可以在.map中查看.text的地址。
第三行把定位计数器的值设为0x80000000
第四行第五行定义了两个新的输出节.data,.bss,链接器会把.data放到0x80000000,.data处理完成后,定位计数器的值会就是0x80000000加上.data的长度,然后链接器会把.bss放在这个地址。
链接器必要时会通过增加定位计数器的值保证每一个输出节具有它所需要的对齐。
链接器必要时会在两个输出节之间插入空字节以保证字节对齐。比如tricore默认是64位对齐,如果定位计数器不满足64位对齐,在开始下一节时会补齐。

命令

入口点

在运行程序时第一个执行的指令叫入口点。
可以使用ENTRY命令来设置入口点,参数是一个符号名,通常是main或者start,如ENTRY(main),ENTRY(start),表示从符号main和start开始执行,即我们通常的main函数。
链接器尝试不同的方式设置入口点,如果成功了就停止:

ENTRY(main) /* 入口点为main函数 */
SECTIONS
{
. = 0x10000000;
.text : { *(.text) }
. = 0x80000000;
.data : { *(.data) }
.bss : { *(.bss) }
}

main.c文件:

/* .data */
int dataA = 1;
int dataB = 2;
/* .bss */
int bssA;
int main()
{
dataA++;
dataB++;
bssA = 2;
bssA++;
return bssA;
}

编译链接之后查看.map文件

.text           memory region -> *default*
0x0000000010000000 0x6e
*(.text)
.text 0x0000000010000000 0x6e CMakeFiles/LCTest.elf.dir/src/main.c.obj
0x0000000010000000 main
0x0000000080000000 . = 0x80000000

.data memory region -> *default*
0x0000000080000000 0x8
*(.data)
.data 0x0000000080000000 0x8 CMakeFiles/LCTest.elf.dir/src/main.c.obj
0x0000000080000000 dataA
0x0000000080000004 dataB

.bss memory region -> *default*
0x0000000080000008 0x8
*(.bss)
.bss 0x0000000080000008 0x8 CMakeFiles/LCTest.elf.dir/src/main.c.obj
0x0000000080000008 bssA
0x00000000d0000000 . = 0xd0000000

可以看出,.text,.data,.bss节的地址和上面的分析一样。

文件处理

文件处理用来指明要链接的文件,或者链接输出的文件名。通常在命令行中指定,脚本文件中暂时略过

符号

可以在链接脚本中为一个符号赋值,这会把这个符号定义为一个全局符号。
可以通过对一个符号执行下面的运算:

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

第一行定义一个符号同时赋值为expression。
余下的行执行符号的计算操作,符号必须是已经定义的。
特殊符号"."只能在SECTION命令中使用它。
表达式必须以分号结尾。
例子:

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

floating_point为0,
_etext的值是.text节尾部的地址,即当前"."的地址,
_bdata是.text后面一个向上对齐到4字节边界的地址。

HIDDEN

在elf文件中,如果不想定义的符号被导出到目标文件,可以使用HIDDEN符号,如
HIDDEN(_extext)即不导出_extext符号。

PROVIDE

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

如果在程序中定义了一个_etext符号,链接器会给出一个错误,因为在脚本文件中又定义了这个符号,如果程序中定义了一个etext符号,链接器会默认使用程序中的定义。如果程序中引用了etext但不定义它,链接器会使用脚本中的定义。

PROVIDE-HIDDEN

和PROVIDE一样,在elf文件中不会导出这个符号。

在源码中引用符号

在脚本文件中定义了一个符号etext=0x10000000,可以在源码中引用它,extern int etext。但是这里etext指向的值是一个地址,即etext是一个void类型的指针,要使用它,需要注意进行转换,同时应该留有足够大小的空间如

etext=0x10000000;
etext_size=2K;

即可使用etext引用2K大小的数据。

SECTIONS

SECTIONS命令的基本格式如下:

SECTIONS
{
sections-command
sections-command

}

每一个sections-command可以是下面的一种

输出节描述

完整的输出节描述如下:

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不是命令SECTIONS,这里的section是节的名字,常见的节名有.text,.data,.bss等。

输出地址

如果提供了address,输出节的地址会精确的设置为这个地址
如果没有提供address,会使用region的地址
如果没有address,也没有region,会使用定位计数器的值(会对齐)
指定一个节的地址会改变定位计数器的值。
address可以是任意的表达式,比如

.text . :{*{.text}} /* 使用定位计数器为address */
.text ALIGN(8) : {*{.text}} /* 使用定位计数器8字节对齐之后为address */

输出节

输入节

__attribute__((section(".testsec"))) int global = 42;
#pragma section ".testsec"
int global1 = 41;
#pragma section
SECTIONS 
{
. = 0x80001000;
.testsection : { *(.testsec) }
}

上面编译global,global1到.testsection,其中testsection是输出节,.testsec是输入节