链接脚本
- 1.如何调用
- 2.链接脚本用处
- 3.链接参数
- 4.链接脚本常用关键词
- 5.示例
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)))} >iramPROVIDE (__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);} >iramPROVIDE( _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)} >iramPROVIDE( _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 = . );} >iramPROVIDE( _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