1.如何调用
scons架构:
program = env.Program(target='my_program', source=object_files, LINKFLAGS=['-T', linker_script])
make架构:
gcc -T my_linker_script.ld -o my_program main.c
2.链接脚本用处
链接脚本其实是个规则文件,他是程序员用来指挥链接器工作的。链接器会参考链接脚本,并且使用其中规定的规则来处理.o文件中那些段,将其链接成一个可执行程序。
3.链接参数
链接脚本针对的主要是内存空间和地址分配部分,对于链接过程其它的部分,是链接器自动处理的,毕竟符号解析和重定位通常不会涉及到自定义的操作,同时,可以通过传入命令行参数来控制链接过程,使用 ld --help 可以查看所有的链接器参数,下面我们就来介绍几个比较常用的链接参数:
- -T:-T 参数表示指定链接脚本,用户可以通过 ld -Tfile 来指定使用自己的链接脚本,而不使用系统默认的,在一些特殊的场景中适用。
- @file: 从文件中读取命令行参数,而不是手动指定,通常在脚本编程时使用这种做法。
- -e entry、–entry=entry:这两个命令是同等效果,显示地指定程序开始的位置,通常情况下,需要指定程序内部的符号,如果给定的参数不是一个符号,链接器会尝试将参数解析成数字,表示从指定的地址开始执行程序。
- -EB、-EL:指定大小端,这会覆盖掉系统默认的大小端设置。
- -L、–library-path=searchdir:指定搜索的目录
- -l :链接指定的库,库名通常是 libname.a 或者 libname.so,使用该参数时去掉库的前后缀,即 -lname
- -o output、–output=output:指定输出文件名
- -s、–strip-all:丢弃可执行文件中的符号,以减小尺寸。
- -static:不使用动态库,静态地链接
- -nostdlib:默认情况下链接标准库,该参数显示地指明不链接标准库。
- -shared:创建一个动态库
4.链接脚本常用关键词
- ALIGN:指定段的对齐方式。
. = ALIGN(4); - ASSERT:在链接脚本中添加断言,以确保链接脚本的一致性和正确性。
ASSERT(_heap_size > 0x4000, “Heap size is too small…”); - DEFINED:用于检查符号是否已经定义,通常用于条件编译。
__stack_size = DEFINED(__stack_size) ? __stack_size : 4K; - ENTRY:指定程序的入口点。
ENTRY( _start )
5.EXTERN:声明一个外部可见的符号。 - FILL:用于填充段以匹配特定长度。
- GROUP:定义符号组,以便一起加载和链接。
- INCLUDE:引入其他链接脚本文件。
- INCLUDE_PATH:指定链接脚本中的包含路径。
- KEEP:用于防止链接器删除未使用的段或符号。
KEEP (*(SORT_NONE(.init))) - LOAD:指定如何加载目标文件的段到内存中。
- MEMORY块:关键字用于定义不同的内存区域,包括其名称、起始地址(ORIGIN)、长度(LENGTH)等信息。这些内存区域通常表示硬件中的不同存储器,如ROM、RAM
属性部分是可选的,它主要有以下几个选项:
'R':只读段
'W':读写段
'X':可执行段
'A':需要分配内存的段
'I','L':初始化段
'!':和上述的属性合并使用,表示反转给出的属性
MEMORY
{
iram(rw) : ORIGIN = DTOP_IRAM_START, LENGTH = DTOP_IRAM_LENGTH
}
- PROVIDE:为链接器提供一个符号,以便在链接过程中使用。
- SECTIONS块:关键字用于定义各个段(sections)的配置,包括代码段、数据段等。在这个部分,你可以指定哪些段应该被分配到哪些内存区域中,并定义它们的排列顺序。
先天性段:
.text: 代码段。存放程序运行代码(机器码)。
.rodata: 只读数据段。通常包含只读的常量数据,如字符串和常量变量。
.data: 数据段。用于存储程序的全局变量和静态变量等可读写数据。
.bss: 未初始化数据段。用于存储未初始化的全局变量和静态变量。
.stack: 栈段。用于存储函数调用时的局部变量和返回地址。
.heap: 堆段。用于动态分配内存,如使用 malloc 函数时分配的内存。
.comment:注释段。包含程序的注释信息。
.debug: 调试信息段。包含调试符号和源代码映射信息。
.init: 初始化代码段。用于在程序启动时执行的初始化代码。该段自动产生名为init的函数,该函数早于main执行。
.fini: 结束代码段。用于在程序终止时执行的清理代码。该段自动产生名为fini的函数,该函数在main函数结束之后执行。
.init_array和 .fini_array:用于存储初始化和结束函数的段。
后天性的段:
.stack:栈段。用于存储函数调用时的局部变量和返回地址。
.heap:堆段。用于动态分配内存,如使用 malloc 函数时分配的内存。
自定义段:
// 在C语言中,使用 __attribute__((section("name"))) 来指定段名
// 将变量 my_data 放置到名为 "my_section" 的段中
int my_data __attribute__((section("my_section"))) = 42;
// 将函数 my_function 放置到名为 "my_code" 的段中
void my_function() __attribute__((section("my_code")))
{
// 函数实现
}
- SORT:定义段的排列顺序。
- SUBALIGN:指定段的子对齐方式。
5.示例
理解:其中主要是是两个块就是上面提到的MEMORY块和SECTIONS块。MEMORY块很好理解,主要的是SECTIONS块里面的各个段都是大概什么作用需要稍微记忆一下
#include "xxx.h"
OUTPUT_ARCH( "riscv" )
ENTRY( _start )
__stack_size = DEFINED(__stack_size) ? __stack_size : 2K;
MEMORY
{
iram : ORIGIN = 0x04000000, LENGTH = 0x1000
}
SECTIONS
{
.init :
{
KEEP (*(SORT_NONE(.init)))
} >iram
.text :
{
*(.text .text.*)
*(.gnu.linkonce.t.*)
} >iram
.fini :
{
KEEP (*(SORT_NONE(.fini)))
} >iram
PROVIDE (__etext = .);
PROVIDE (_etext = .);
PROVIDE (etext = .);
.rodata :
{
*(.rdata)
*(.rodata .rodata.*)
*(.gnu.linkonce.r.*)
. = ALIGN(4);
PROVIDE( _single_module_start = .);
KEEP(*(.singletestmodule.*))
PROVIDE( _single_module_end = . );
. = ALIGN(4);
PROVIDE( _test_module_start = .);
KEEP(*(.testmodule.*))
PROVIDE( _test_module_end = . );
. = ALIGN(4);
PROVIDE( _cli_cmd_start = .);
KEEP (*(.cli_cmd.*))
PROVIDE( _cli_cmd_end = .);
} >iram
. = ALIGN(4);
.dtors :
{
KEEP (*crtbegin.o(.dtors))
KEEP (*crtbegin?.o(.dtors))
KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .dtors))
KEEP (*(SORT(.dtors.*)))
KEEP (*(.dtors))
} >iram
.data : ALIGN(8)
{
*(.data .data.*)
*(.gnu.linkonce.d.*)
PROVIDE( _gp = . + 0x800 );
*(.srodata.cst16)
*(.srodata.cst8)
*(.srodata.cst4)
*(.srodata.cst2)
*(.srodata .srodata.*)
*(.sdata .sdata.*)
*(.gnu.linkonce.s.*)
. = ALIGN(4);
} >iram
PROVIDE( _data_load_addr = LOADADDR(.data) );
PROVIDE( _data_start = ADDR(.data) );
PROVIDE( _data_end = ADDR(.data) + SIZEOF(.data) );
.iram :
{
. = ALIGN(4);
PROVIDE( _iram_start = . );
*(.iram.entry*);
*(.iram*);
. = ALIGN(4);
PROVIDE( _iram_end = . );
} >iram
.bss : ALIGN(8)
{
*(.sbss*)
*(.gnu.linkonce.sb.*)
*(.bss .bss.*)
*(.gnu.linkonce.b.*)
*(COMMON)
} >iram
PROVIDE( _bss_start = ADDR(.bss) );
PROVIDE( _bss_end = ADDR(.bss) + SIZEOF(.bss) );
.heap :
{
. = ALIGN(4);
PROVIDE( _heap_start = . );
. = ORIGIN(iram) + LENGTH(iram) - __stack_size;
PROVIDE( _heap_end = . );
} >iram
PROVIDE( _heap_size = _heap_end - _heap_start );
.stack :
{
. += __stack_size;
PROVIDE( __stack_top = . );
} >iram
}
#if 0
ASSERT(_heap_size > 0x4000, "Heap size is too small...");
#endif