链接脚本理解

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.链接脚本常用关键词

  1. ALIGN:指定段的对齐方式。
    . = ALIGN(4);
  2. ASSERT:在链接脚本中添加断言,以确保链接脚本的一致性和正确性。
    ASSERT(_heap_size > 0x4000, “Heap size is too small…”);
  3. DEFINED:用于检查符号是否已经定义,通常用于条件编译。
    __stack_size = DEFINED(__stack_size) ? __stack_size : 4K;
  4. ENTRY:指定程序的入口点。
    ENTRY( _start )
    5.EXTERN:声明一个外部可见的符号。
  5. FILL:用于填充段以匹配特定长度。
  6. GROUP:定义符号组,以便一起加载和链接。
  7. INCLUDE:引入其他链接脚本文件。
  8. INCLUDE_PATH:指定链接脚本中的包含路径。
  9. KEEP:用于防止链接器删除未使用的段或符号。
    KEEP (*(SORT_NONE(.init)))
  10. LOAD:指定如何加载目标文件的段到内存中。
  11. MEMORY块:关键字用于定义不同的内存区域,包括其名称、起始地址(ORIGIN)、长度(LENGTH)等信息。这些内存区域通常表示硬件中的不同存储器,如ROM、RAM
属性部分是可选的,它主要有以下几个选项:
'R':只读段
'W':读写段
'X':可执行段
'A':需要分配内存的段
'I','L':初始化段
'!':和上述的属性合并使用,表示反转给出的属性

MEMORY
{
    iram(rw) : ORIGIN = DTOP_IRAM_START, LENGTH = DTOP_IRAM_LENGTH
}
  1. PROVIDE:为链接器提供一个符号,以便在链接过程中使用。
  2. 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")))
    {
        // 函数实现
    }
  1. SORT:定义段的排列顺序。
  2. 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

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

maosql

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值