zoukankan      html  css  js  c++  java
  • 链接脚本文件(.ld .lds)详解

     链接脚本实例:(STM32F407VG,RT-Thread Studio生成的工程所含)

     * linker script for STM32F407ZG with GNU ld
     */
    
    /* Program Entry, set to mark it as "used" and avoid gc */
    MEMORY
    {
        ROM (rx) : ORIGIN = 0x08000000, LENGTH =  1024k /* 1024K flash */
        RAM (rw) : ORIGIN = 0x20000000, LENGTH =  128k /* 128K sram */
    }
    ENTRY(Reset_Handler)
    _system_stack_size = 0x400;
    
    SECTIONS
    {
        .text :
        {
            . = ALIGN(4);
            _stext = .;
            KEEP(*(.isr_vector))            /* Startup code */

          . = ALIGN(4);
          *(.text) /* remaining code */
          *(.text.*) /* remaining code */
          *(.rodata) /* read-only data (constants) */

    
            /* section information for utest */
            . = ALIGN(4);
            __rt_utest_tc_tab_start = .;
            KEEP(*(UtestTcTab))
            __rt_utest_tc_tab_end = .;
            . = ALIGN(4);
    
            PROVIDE(__ctors_start__ = .);
            KEEP (*(SORT(.init_array.*)))
            KEEP (*(.init_array))
            PROVIDE(__ctors_end__ = .);
    
            . = ALIGN(4);
    
            _etext = .;
        } > ROM = 0
        /* .data section which is used for initialized data */
    
        .stack : 
        {
            . = ALIGN(4);
            _sstack = .;
            . = . + _system_stack_size;
            . = ALIGN(4);
            _estack = .;
        } >RAM
    
        __bss_start = .;
        .bss :
        {
            . = ALIGN(4);
            /* This is used by the startup in order to initialize the .bss secion */
            _sbss = .;
    
            *(.bss)
            *(.bss.*)
            *(COMMON)
    
            . = ALIGN(4);
            /* This is used by the startup in order to initialize the .bss secion */
            _ebss = . ;
            
            *(.bss.init)
        } > RAM
        __bss_end = .;
    
        _end = .;
    
        /* Stabs debugging sections.  */
        .stab          0 : { *(.stab) }
        .stabstr       0 : { *(.stabstr) }
        .stab.excl     0 : { *(.stab.excl) }
        .stab.exclstr  0 : { *(.stab.exclstr) }
    }

     特别注意:

    1     .text  section :{}   .stack :{} 表示输出文件包含的 section

    2     {}里面的 section,是输入文件的 section,比如 *(.isr_vector)    *(.text)    *(.rodata) 这些 .isr_vector section   .text section   .rodata section,都有指定输入文件,*表示所有的输入文件;所以 *(.isr_vector) 表示从所有的输入文件中获取所有 .isr_vector section 放在一块连续的地址空间;main.o(.data) 表示从 main.o文件中获取所有的 .data section 放在一块连续的地址空间

    3     链接脚本从上往下,如果输入文件 A 已经被取出 .text section,此后输入文件 A 就没有 .text section,不能再被获取

    4     关于 section 的命名,名字前可以包含 . ,也可以不包含,大多取名会包含 .

    概述:

    链接器:把一个或多个输入文件合并成一个输出文件,输入文件是目标文件或者链接脚本文件,输出文件是目标文件(库文件)或者可执行文件,链接器从链接脚本读完一个 section 后,将定位器符号的值增加该 section 的大小

    链接脚本:控制输出文件内各部分在程序地址空间内的布局,地址空间包括 ROM 和 RAM

    -T 选项用于指定自己的链接脚本,否则使用默认的链接脚本

    语法:

    定位符 . 

    . 是定位器符号,可以对定位器符号赋值指定接下来内容的存储位置,如“ .= 0x10000”,也可以通过定位器符获取此位置的地址,比如 ”_stext = .;”,此后就可以用变量 _stext 表示此位置地址

    入口地址

    ENTRY(SYMBOL):将符号 SYMBOL 的值设置为入口地址,入口地址是进程执行的第一条指令在进程地址空间的地址(比如 ENTRY(Reset_Handler) 表示进程最开始从复位中断服务函数处执行

     有多种方法设置进程入口地址,以下编号越小,优先级越高

    1、ld 命令行的 -e 选项

    2、链接脚本的 ENTRY(SYMBOL) 命令

    3、在汇编程序中定义了 start 符号,使用 start 符号值

    4、如果存在 .text section,使用 .text section 首地址的值

    5、使用地址 0 的值

    内存布局

    脚本中以MEMORY命令定义了存储空间,其中以ORIGIN定义地址空间的起始地址,LENGTH定义地址空间的长度。

    结构:

    MEMORY {
    NAME1 [(ATTR)] : ORIGIN = ORIGIN1, LENGTH = LEN2
    NAME2 [(ATTR)] : ORIGIN = ORIGIN2, LENGTH = LEN2
    …
    }

    NAME :存储区域的名字。(自己可以随意命名)

    ATTR :定义该存储区域的属性。ATTR属性内可以出现以下7 个字符:

    R 只读section
    W 读/写section
    X 可执行section
    A 可分配的section
    I 初始化了的section
    L 同 I
    ! 不满足该字符之后的任何一个属性的section
    ORIGIN :关键字,区域的开始地址,可简写成 org 或 o

    LENGTH :关键字,区域的大小,可简写成 len 或 l

    例子:

    MEMORY
    {
        ROM (rx) : ORIGIN = 0x08000000, LENGTH =  1024k /* 1024K flash */
        RAM (rw) : ORIGIN = 0x20000000, LENGTH =  128k /* 128K sram */
    }

    在链接文件中定义的变量(符号)可以在目标文件中使用

    在链接文件中定义变量 

      _init_start = .;
      .application_init  : { *(.application_init) }
      _init_end = .;

     在源文件中使用变量 _init_start

    #include <stdio.h>
    #include <string.h>
    
    struct _s_application_init {
        int(*function)(void);
    };
    
    extern struct _s_application_init _init_start;//段".application_init"的起始地址,在*.lds文件中定义
    extern struct _s_application_init _init_end;//段".application_init"的末尾地址,在*.lds文件中定义
    
    #define __app_init_section __attribute__((section(".application_init")))
    #define __application_init(function) 
        struct _s_application_init _s_a_init_##function  __app_init_section = {function}
    
    static int application_init_a(void)
    {
        printf("execute funtion : %s
    ", __FUNCTION__);
        return 0;
    }
    __application_init(application_init_a);int main(int argc, char **argv)
    {
        /*
         * 从段的起始地址开始获取数据,直到末尾地址
         */
    
        struct _s_application_init *pf_init = &_init_start;
        do {
            printf("Load init function from address %p
    ", pf_init);
            pf_init->function();
            ++pf_init;
        } while (pf_init < &_init_end);
    
        return 0;
    }

    PROVIDE 关键字(感觉有无 PROVIDE 修饰都可以被输入文件引用,看上面的例子)

    该关键字定义一个(输入文件内被引用但没定义)符号。相当于定义一个全局变量的符号表,其他C文件可以通过该符号来操作对应的存储内存。

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

    如上,在链接脚本中声明了_etext 符号。特别注意的是_etext 只是一个符号,没有存储内存,并不是一个变量,该符号对应(映射)的是一个地址,其地址为 .text section 之后的第一个地址。C文件中引用用法如下:

    int main()
    {
        //引用该变量
        extern char  _etext;
        char *p = &_etext;
        //...
    }

    若在链接脚本中 " _etext = 0x100; ",即表示符号_etext对应的地址为0x100, 此时 & _etext的值为 0x100, char a= *p;表示为 从0x100地址取存储的值赋值给变量a

    在目标文件内定义的符号可以在链接脚本内被赋值

    此时该符号被定义为全局的. 每个符号都对应了一个地址, 此处的赋值是更改这个符号对应的地址.

    e.g. 通过下面的程序查看变量a的地址:

    /* a.c */  
    #include <stdio.h>
    #include <stdlib.h>
    
    int a = 100;  
    int main(void)  
    {  
        printf( “&a=0x%p“, &a );  
        return 0;  
    }

    文件a.lds

    /* a.lds :注意格式有要求*/  
    a = 3;  

    编译命令:$gcc -Wall -o a-without-lds a.c,执行./a-without-lds输出&a = 0×8049598.

    编译命令:$gcc -Wall -o a-with-lds a.c a.lds,执行a-with-lds,输出&a = 0×3

    对符号的赋值只对全局变量起作用! 一些简单的赋值语句,能使用任何c语言内的赋值

    除了可以在 C源文件中指定函数属于某个 section,汇编文件也可以,比如 startup.s

      .section  .text.Reset_Handler
      .weak  Reset_Handler
      .type  Reset_Handler, %function
    Reset_Handler:  
      ldr   sp, =_estack     /* set stack pointer */
      bl  entry
      bx  lr    
    .size  Reset_Handler, .-Reset_Handler
      .section  .isr_vector,"a",%progbits
      .type  g_pfnVectors, %object
      .size  g_pfnVectors, .-g_pfnVectors
        
    g_pfnVectors:
      .word  _estack
      .word  Reset_Handler

     section 结构

    SECTIONS
    {
           ...
          secname [start_ADDR] [(TYPE)] : [AT (LMA_ADDR)]
          { 
            contents 
          } [>REGION] [AT>LMA_REGION] [:PHDR HDR ...] [=FILLEXP]
          ...
    }

    [ ]内的内容是可选选项

    secname: 表示输出文件的 section 名,即输出文件中有哪些 section。而contents就是描述输出文件的这个 section 内容从哪些输入文件的哪些 section 里抽取而来。

    输出section名字必须符合输出文件格式要求,比如:a.out格式的文件只允许存在.text、.data和.bss section名。而有的格式只允许存在数字名字,那么此时应该用引号将所有名字内的数字组合在一起;另外,还有一些格式允许任何序列的字符存在于 section名字内,此时如果名字内包含特殊字符(比如空格、逗号等),那么需要

    用引号将其组合在一起。

    如下,将输入文件的数据段存放在输出文件的数据段(section 名自己定义,section 名前后必须要有空格)

    SECTIONS
    {
           ...
          .data :
          { 
            main.o(.data)
            *(.data)
          } 
          ...
    }

    其中 *(.data) 表示将所有的输入文件的 .data section 链接到输出文件 .data section 中, 特别注意的是,之前链接的就不会再链接,这样做的目的是可以将某些特殊的输入文件链接到地址前面。

    start_addr :表示将某个段强制链接到的地址( VMA ),start_addr 改变定位符的值。

    SECTIONS
    {
       .bss :
       {
          . = ALIGN(4);        /* Align the start of the section */
          _sbss = .;           /* Provide the name for the start of this section */
          
          *(.bss)
          *(.bss.*)
          . = ALIGN(512);
          USB_RAM_START = .;
        . += USB_RAM_GAP;
          
          . = ALIGN(4);        /* Align the end of the section */
       } > RAM
       _ebss = .;              /* Provide the name for the end of this section */
    
       m_usb_bdt USB_RAM_START (NOLOAD) :
       {
         *(m_usb_bdt)
         USB_RAM_BDT_END = .;
       }
    
       m_usb_global USB_RAM_BDT_END (NOLOAD) :
       {
         *(m_usb_global)
       }
    }

    TYPE:每个输出section都有一个类型,如果没有指定TYPE类型,那么连接器根据输出section引用的输入section的类型设置该输出section的类型。它可以为以下五种值

    • NOLOAD 该section在程序运行时,不被载入内存。
    • DSECT,COPY,INFO,OVERLAY :这些类型很少被使用,为了向后兼容才被保留下来。这种类型的section必须被标记为“不可加载的”,以便在程序运行不为它们分配内存。

    AT( LAM_ADDR ):输出 section 的 LMA,默认情况下 LMA 等于 VMA,但可以通过关键字 AT() 指定 LMA。

    用关键字 AT()指定,括号内包含表达式,表达式的值用于设置LMA。如果不用AT()关键字,那么可用AT>LMA_REGION表达式设置指定该section加载地址的范围。这个属性主要用于构件ROM境象。

    REGION:这个region就是前面说的MEMORY命令定义的位置信息。

    VMA 和 LMA

    section包含两个地址:VMA(virtual memory address虚拟内存地址或程序地址空间地址)和LMA(load memory address加载内存地址或进程地址空间地址)。通常VMA和LMA是相同的。

    • VMA是执行输出文件时section所在的地址
    • LMA是加载输出文件时section所在的地址

    一般而言, 某section的VMA == LMA. 但在嵌入式系统中, 经常存在加载地址和执行地址不同的情况: 比如将输出文件加载到开发板的flash中(由LMA指定), 而在运行时将位于flash中的输出文件复制到SDRAM中(由VMA指定)。

    KEEP 关键字

    在链接命令行内使用了选项 -gc-sections 后,链接器可能将某些它认为没用的 section 过滤掉,此时就有必要强制让链接器保留一些特定的 section,可用 KEEP() 关键字达此目的。如 KEEP(* (.text)) 或 KEEP(SORT(*)(.text))。说的通俗易懂就是:防止被优化。

    ALIGN 关键字

    表示字节对齐, 如 “ . = ALIGN(4);”表示从该地址开始后面的存储进行4字节对齐。

    实例详解:

     SECTIONS
     {
       .= 0x10000;
       .text : { *(.text) }
       .= 0×8000000;
       .data : { *(.data) }
       .bss : { *(.bss) }
     }

    解释一下上诉的例子:

    .= 0x10000:把定位器符号置为 0x10000(若不指定,则该符号的初始值为0)

    .text : { *(.text) }:*符号代表所有的输入文件,此句表示获取所有输入文件的 .text section放在一块连续的地址空间,首地址由上一句的定位器符号确定,即 0x10000

    .= 0x8000000:把定位器符号置为 0x8000000

    .data : { *(.data) }:获取所有输入文件的 .data section 放在一块连续的地址空间,该 section 的首地址为 0x8000000

    .bss : { *(.bss) }:获取所有输入文件的 .bss section 放在一块连续的地址空间,该 section 的首地址为 0x8000000 + .data section 的大小

     输出文件包含 .text section     .data section     .bss section

  • 相关阅读:
    linux下配置java环境及问题
    Chrome工具使用
    Ibatis的resultMap和查询数据的对应关系
    spring mvc接收数组
    PowderDesign的使用
    Android Runtime Stats
    [原创]ASM动态修改JAVA函数之函数字节码初探
    [原创]WB Android客户端架构总结:发WB工作队列设计
    [原创]Android Studio的Instant Run(即时安装)原理分析和源码浅析
    [原创]Android系统中常用JAVA类源码浅析之HashMap
  • 原文地址:https://www.cnblogs.com/god-of-death/p/14879078.html
Copyright © 2011-2022 走看看