RISC-V链接脚本

1.基础概念
        链接脚本的主要作用是描述输入文件中的段应当如何映射到输出文件中,并控制输出文件的内存布局。但是,如果需要,链接脚本也可以使用下面所描述的命令指挥链接器进行很多其他操作。

       链接器通常使用一个链接脚本。如果没有为其提供一个,链接器将会使用默认的编译在链接器执行文件内部的脚本。可以使用命令’–verbose’显示默认的链接脚本。

为了描述链接脚本语言,我们需要定义一些基本概念和词汇。

       链接器将许多输入文件组合成一个输出文件。输出文件和每个输入文件都有一个特定的已知格式成为目标文件格式。每个文件都被称为目标文件。输出文件通常叫做可执行文件,但我们仍将其称为目标文件。每个目标文件在其他东西之间,都有一个段列表。有时把输入文件的段称作输入段,类似的,输出文件的段称作输出段。

      每个目标文件中的段都有名字和大小。多数段还有一个相关的数据块,称为 段内容。一个段可能被标记为可加载,表示当输出文件运行时,段内容需要先加载到内存中。一个没有内容的段可能是可分配段,即在内存中留出一段空间(有时还需要清零)。一个即不是加载又不是可分配的段,通常含有一些调试信息。

       每个加载或可分配输出段有两个地址。第一个地址为VMA,或者叫做虚地址。这是当输出文件运行时段所拥有的地址。第二个地址是LMA,或者叫加载内存地址。这是段将会被加载的地址。一个它们会产生区别的例子是,当一个数据段加载到ROM, 此后在程序启动时被复制到RAM中(这个技术通常被用来初始化全局变量)。此种情况下,ROM使用LMA地址,RAM使用VMA地址。

如果想查看目标文件中的段,可以用objdump程序的’-h’选项。
每个目标文件还有一个符号列表,称为符号列表。一个符号可能是被定义的或者未定义的。每个符号都有一个名字,且所有已定义的符号在其他信息中间都有一个地址。如果将一个c或者c++程序编译成目标文件,会将所有定义过的函数和全局变量以及静态变量作为已定义符号。所有输入文件引用的未定义的函数或者全局变量会成为未定义符号。

GCC 中的强符号、弱符号(-fno-common)
链接器中的全局符号可分为两种:强符号(Strong symbols),弱符号(Weak symbols)。GCC语法中使用__attribute__((weak))来声明这个符号是弱符号的,其语法手册中这样写道:

 The weak attribute causes the declaration to be emitted as a weak symbol rather than a global. This is primarily useful in defining library functions which can be overridden in user code, though it can also be used with non-function declarations. Weak symbols are supported for ELF targets, and also for a.out targets when using the GNU assembler and linker.
1
而对于全局变量来说,如果初始化了不为0的值,那么该全局变量则被保存在data段,如果初始化的值为0,那么将其保存在bss段,如果没有初始化,则将其保存在common段,等到链接时再将其放入到bss段。关于第三点不同编译器行为会不同,有的编译器会把没有初始化的全局变量直接放到bss段。

绝大多数情况下,函数和已初始化的变量是强符号,而未初始化的变量是弱符号。对于它们,下列三条规则适用:

1. 同名的强符号只能存在一个。
2. 一个强符号可以和多个同名的弱符号共存,但调用时会选择强符号的值。
3. 有多个弱符号时,链接器可以选择其中任意一个。

下面以一个简单的例子进行说明:

//test1.c
int a1 = 0;
void func1()
{
    printf("a1 = %d/n",a1);
}

//test2.c
int a1 = 1;
void func2()
{
    printf("a1 = %d/n",a1);
}

//main.c
void main()
{
  func1();
  func2();
}

此时编译main.c时编译器会报重复定义的错误。
而如果我们对以上代码进行小小的改动:

//test1.c
int a1;
void func1()
{
    printf("a1 = %d/n",a1);
}

//test2.c
int a1 = 1;
void func2()
{
    printf("a1 = %d/n",a1);
}

//main.c
void main()
{
  func1();
  func2();
}

则编译通过,且输出打印值为a1 = 1 a1 = 1。

这是因为test1.c中a1没有进行初始化,是弱符号,而test2.c中的a1则是强符号,而根据上述规则2,链接过程中编译器会使用强符号的值替代同名弱符号,所以不会报重复定义的错误也没有打印出变量期望值。

为了避免这种情况,在实际开发过程中,我们需要给GCC传入-fno-common参数,禁止将未初始化的全局变量放入到common段。这样就不会出现存在多个同名全局变量而编译时不报错的情况。

2.常用关键词与用法

ENTRY(symbol) 用来指定程序执行的入口点
MEMORY 内存分配命令
SECTIONS 段命令 描述输出文件的内存和布局
.text 程序代码段
.rodata 只读数据
.data 可读写且需要初始化的数据
.bss 可读写的清零初始化数据
ASSERT 断言
PROVIDE(symbol=expression) 定义一个符号
AT 后跟MEMORY定义的内存区域或者地址
ALIGN 字节对齐

ORIGIN(memory)返回名为memory的内存区域的起始地址。

LENGTH(memory)返回名为memory的内存的长度。

OUTPUT_ARCH( "riscv" )

ENTRY( _start )

MEMORY

    /* Run in FLASH */ 
    flash (rxai!w) : ORIGIN = 0x08000000, LENGTH = 128k
    ram   (wxa!ri) : ORIGIN = 0x20000000, LENGTH = 32K 

    /* Run in RAM */ 
/*    flash (rxai!w) : ORIGIN = 0x20000000, LENGTH = 24k
    ram   (wxa!ri) : ORIGIN = 0x20006000, LENGTH = 8K
*/    
}


SECTIONS
{
  __stack_size = DEFINED(__stack_size) ? __stack_size : 2K;


  .init           :
  {
    KEEP (*(SORT_NONE(.init)))
  } >flash AT>flash 

  .ilalign         :
  {
    . = ALIGN(4);
    PROVIDE( _ilm_lma = . );
  } >flash AT>flash 

  .ialign         :
  {
    PROVIDE( _ilm = . );
  } >flash AT>flash 

  .text           :
  {
    *(.rodata .rodata.*)  
    *(.text.unlikely .text.unlikely.*)
    *(.text.startup .text.startup.*)
    *(.text .text.*)
    *(.gnu.linkonce.t.*)
  } >flash AT>flash 

  .fini           :
  {
    KEEP (*(SORT_NONE(.fini)))
  } >flash AT>flash 

  . = ALIGN(4);

  PROVIDE (__etext = .);
  PROVIDE (_etext = .);/*0x80022c8*/
  PROVIDE (etext = .);/*0x80022c8*/
  PROVIDE( _eilm = . );

  .preinit_array  :
  {
    PROVIDE_HIDDEN (__preinit_array_start = .);
    KEEP (*(.preinit_array))
    PROVIDE_HIDDEN (__preinit_array_end = .);
  } >flash AT>flash 

  .init_array     :
  {
    PROVIDE_HIDDEN (__init_array_start = .);
    KEEP (*(SORT_BY_INIT_PRIORITY(.init_array.*) SORT_BY_INIT_PRIORITY(.ctors.*)))
    KEEP (*(.init_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .ctors))
    PROVIDE_HIDDEN (__init_array_end = .);
  } >flash AT>flash 

  .fini_array     :
  {
    PROVIDE_HIDDEN (__fini_array_start = .);
    KEEP (*(SORT_BY_INIT_PRIORITY(.fini_array.*) SORT_BY_INIT_PRIORITY(.dtors.*)))
    KEEP (*(.fini_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .dtors))
    PROVIDE_HIDDEN (__fini_array_end = .);
  } >flash AT>flash 

  .ctors          :
  {
    /* gcc uses crtbegin.o to find the start of
       the constructors, so we make sure it is
       first.  Because this is a wildcard, it
       doesn't matter if the user does not
       actually link against crtbegin.o; the
       linker won't look for a file to match a
       wildcard.  The wildcard also means that it
       doesn't matter which directory crtbegin.o
       is in.  */
    KEEP (*crtbegin.o(.ctors))
    KEEP (*crtbegin?.o(.ctors))
    /* We don't want to include the .ctor section from
       the crtend.o file until after the sorted ctors.
       The .ctor section from the crtend file contains the
       end of ctors marker and it must be last */
    KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .ctors))
    KEEP (*(SORT(.ctors.*)))
    KEEP (*(.ctors))
  } >flash AT>flash 

  .dtors          :
  {
    KEEP (*crtbegin.o(.dtors))
    KEEP (*crtbegin?.o(.dtors))
    KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .dtors))
    KEEP (*(SORT(.dtors.*)))
    KEEP (*(.dtors))
  } >flash AT>flash 

    . = ALIGN(4);
    PROVIDE( _eilm = . );

  .lalign         :
  {
    . = ALIGN(4);
    PROVIDE( _data_lma = . );
  } >flash AT>flash 

  .dalign         :
  {
    . = ALIGN(4);
    PROVIDE( _data = . );
  } >ram AT>flash 
  
  
  .data          :
  {
    *(.rdata) 
   
    *(.gnu.linkonce.r.*)
    *(.data .data.*)
    *(.gnu.linkonce.d.*)
    . = ALIGN(8);
    PROVIDE( __global_pointer$ = . + 0x800); 
    *(.sdata .sdata.*)
    *(.gnu.linkonce.s.*)
    . = ALIGN(8);
    *(.srodata.cst16)
    *(.srodata.cst8)
    *(.srodata.cst4)
    *(.srodata.cst2)
    *(.srodata .srodata.*)
  } >ram AT>flash 

  . = ALIGN(4);
  PROVIDE( _edata = . );
  PROVIDE( edata = . );

  PROVIDE( _fbss = . ); /*0X200052A0  0X200002A0*/
  PROVIDE( __bss_start = . );
  .bss            :
  {
    *(.sbss*)
    *(.gnu.linkonce.sb.*)
    *(.bss .bss.*)
    *(.gnu.linkonce.b.*)
    *(COMMON)
    . = ALIGN(4);
  } >ram AT>ram 

  . = ALIGN(8);
  PROVIDE( _end = . ); /*0X2000,0340*/
  PROVIDE( end = . );

  .stack ORIGIN(ram) + LENGTH(ram) - __stack_size :  
  {
    PROVIDE( _heap_end = . ); 
    . = __stack_size;  
    PROVIDE( _sp = . ); 
  } >ram AT>ram 
}
 

浅谈RISC-V GCC之:链接脚本学习笔记(二)_MounRiver_Studio的博客-CSDN博客

  • 0
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值