每一个嵌入式开发工程师,在工作越来越深入后,都会慢慢接触到链接脚本,那么什么是链接脚本,我们又该如何结合实际去做自己的链接脚本呢?

一、什么是链接脚本

链接脚本实质上是一个规则文件,程序员用来只会链接器工作的,当我们编写了多个C文件,调用了大量库,如何编译成一个可执行文件呢?这是链接脚本文件就会发挥他的作用了:将多个目标文件(xx.o)、库文件(xx.a)、动态库(.so)等等链接成一个可执行文件。

二、链接脚本规则

为了方便学习,可以将链接脚本规则分为三部分,后面再拿一个实例分析下。

2.1 链接配置

OUTPUT_FORMAT("elf32-little")
OUTPUT_ARCH("riscv")
ENTRY(_start)

其中,前两句指定了输出格式输出架构ENTRY指定整个程序入口,一般_start是startup.S的第一个lable。另外,要知道程序入口并不是说它位于存储介质的起始位置

2.2 内存分布定义

MEMORY
{sram (rxa!w)   : ORIGIN = 0x1c000000, LENGTH = 256Kilm (rxa!w)    : ORIGIN = 0x08000000, LENGTH = 64KFLASH (wxa!r)    : ORIGIN = 0x08010000, LENGTH = 64K
}

MEMORY表示对内存分布的定义,拿sram (rxa!w) : ORIGIN = 0x1c000000, LENGTH = 256K来说:

  • sram是内存块的名字
  • rxa!w是该内存的属性ATTR
  • ORIGIN 是区域的起始地址
  • LENGTH是内存区域的大小

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

  • R 只读section
  • W 读/写section
  • X 可执行section
  • A 可分配的section
  • I 初始化了的section
  • L 同I
  • ! 不满足该字符之后的任何一个属性的section

2.3 段列表定义

SECTIONS命令功能非常强大,可以通过对SECTIONS命令定义一些段(.text、.data、.bss等段)链接分布。

SECTIONS
{.text :{*(.text*)} > FLASH
}

其中:
.text段即代码段,* (.text*)指示将工程中所有目标文件的.text段,> FLASH 表示将.text链接到FLASH中。
更加通用模板如下:

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

其中contents和secname是必须的,
secname为段名字
contents表示将所有的目标的secname段链接到输出文件secname
start_ADDR:表示将某个段强制链接到的地址。
AT( LAM_ADDR ):实现存放地址和加载地址不一致的功能,AT表示在文件中存放的位置,而在内存里呢,按照普通方式存储。如果不用AT()关键字,那么可用AT>LMA_REGION表达式设置指定该section加载地址的范围
REGION 这个region就是前面说的MEMORY命令定义的位置信息。
TYPE每个输出section都有一个类型,如果没有指定TYPE类型,那么连接器根据输出section引用的输入section的类型设置该输出section的类型。

PS
一般情况下section的VMA == LMA;但在嵌入式系统中,为了节省MCU的面积,要减小片上SRAM的大小,会将加载地址和执行地址定义在不同地方:比如将文件加载烧录到开发板的flash中(由LMA指定),而在运行时将位于flash中的输出文件复制到SDRAM中去执行(由VMA指定)。
VMA(virtual memory address虚拟内存地址或程序地址空间地址)
LMA(load memory address加载内存地址或进程地址空间地址)

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

三、链接脚本关键字

上面在介绍sections时已经说了一些,这里介绍链接脚本常见一些修饰关键字如下:

  • ‘.’ 定位符号
    ‘.’ 表示当前地址,可以作为左值也可作为右值。
. = 0x08000000; /*当前地址设为0x08000000*/
RAM_START = .;     /*将当前地址赋值给RAM_START*/
  • KEEP
    连接命令行内使用了选项–gc-sections后,连接器可能将某些它认为没用的section过滤掉,要强制连接器保留一些特定的section

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

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

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

  • NOLOAD 就是上面我们提到的TYPE类型中的一类,表示该section在程序运行时,不被载入内存。
    另外,还有四种类型DSECT,COPY,INFO,OVERLAY,只是这些类型很少被使用,为了向后兼容才被保留下来。
  • 常见段含义
    .text 代码段 存放程序执行代码区域
    .data 初始化数据段 存放程序中已初始化的全局变量
    .bss 未初始化的数据段 存放程序中未初始化的全局变量
    .rodata 只读数据段 存放C中的字符串和#define定义的常量
    .heap 堆 存放进程运行中被动态分配的内存段(malloc)
    .stack 栈 存放程序临时创建的局部变量及函数调用压栈入栈(static 变量存放在数据段)
    NOTE:全局变量才算是程序的数据,局部变量不算程序的数据,只能算是函数的数据。局部变量没有初始化是随机的,全局变量没有初始化是 0
    这里很有感悟,前面接收了同事写的代码,main函数里用来配置寄存器的一个局部变量未初始化,后面调试总是各类问题,verdi仿真没问题硬件也没问题,调试了两天,后面fpga上板把所有寄存器初始化前后都打印出来才定位到这里。所以嘛。。。

另外链接脚本还可以在startup.S中定义,

四、 链接脚本实例

链接脚本里注释用 /* */(同C,但不可以用//哦)
额其实看了前三节的介绍,自己应该很容易看懂,就是最后堆栈那块,后面有时间解释下。

OUTPUT_FORMAT("elf32-little")
OUTPUT_ARCH( "riscv" )ENTRY( _start )__STACK_SIZE = 2K;
__HEAP_SIZE  = 2K;MEMORY
{sram (rxa!w)   : ORIGIN = 0x1c000000, LENGTH = 256Kilm (rxa!w)    : ORIGIN = 0x08000000, LENGTH = 64KFLASH (wxa!r)    : ORIGIN = 0x08010000, LENGTH = 64K
}SECTIONS
{/* To provide symbol __STACK_SIZE, __HEAP_SIZE and __SMP_CPU_CNT */PROVIDE(__STACK_SIZE = 2K);PROVIDE(__HEAP_SIZE = 2K);PROVIDE(__SMP_CPU_CNT = 1);__TOT_STACK_SIZE = __STACK_SIZE * __SMP_CPU_CNT;.init           :{*(.vtable)KEEP (*(SORT_NONE(.init)))} >ilm AT>ilm.ilalign         :{. = ALIGN(4);PROVIDE( _ilm_lma = . );} >ilm AT>ilm.ialign         :{. = ALIGN(4);PROVIDE( _ilm = . );} >ilm AT>ilm.text           :{*(.text.unlikely .text.unlikely.*)*(.text.startup .text.startup.*)*(.text .text.*)*(.gnu.linkonce.t.*)} >ilm AT>ilm.rodata : ALIGN(4){. = ALIGN(4);*(.rdata)*(.rodata .rodata.*)/* section information for initial. */. = ALIGN(4);__rt_init_start = .;KEEP(*(SORT(.rti_fn*)))__rt_init_end = .;/* section information for finsh shell */. = ALIGN(4);__fsymtab_start = .;KEEP(*(FSymTab))__fsymtab_end = .;. = ALIGN(4);__vsymtab_start = .;KEEP(*(VSymTab))__vsymtab_end = .;*(.gnu.linkonce.r.*)} >ram AT>ram.data          : ALIGN(8){KEEP(*(.data.ctest*))*(.data .data.*)*(.gnu.linkonce.d.*). = ALIGN(8);PROVIDE( __global_pointer$ = . + 0x800 );*(.sdata .sdata.* .sdata*)*(.gnu.linkonce.s.*). = ALIGN(8);*(.srodata.cst16)*(.srodata.cst8)*(.srodata.cst4)*(.srodata.cst2)*(.srodata .srodata.*)} >ram AT>ram.bss (NOLOAD)   : ALIGN(8){*(.sbss*)*(.gnu.linkonce.sb.*)*(.bss .bss.*)*(.gnu.linkonce.b.*)*(COMMON). = ALIGN(4);} >ram AT>ram/*  C Runtime Library requirements:* 1. heap need to be align at 16 bytes* 2. __heap_start and __heap_end symbol need to be defined* 3. reserved at least __HEAP_SIZE space for heap*/.heap (NOLOAD)   : ALIGN(16){. = ALIGN(16);PROVIDE( __heap_start = . );. += __HEAP_SIZE;. = ALIGN(16);PROVIDE( __heap_limit = . );} >ram AT>ram.stack ORIGIN(ram) + LENGTH(ram) - __TOT_STACK_SIZE (NOLOAD) :{. = ALIGN(16);PROVIDE( _heap_end = . );PROVIDE( __heap_end = . );PROVIDE( __StackLimit = . );PROVIDE( __StackBottom = . );. += __TOT_STACK_SIZE;. = ALIGN(16);PROVIDE( __StackTop = . );PROVIDE( _sp = . );} >ram AT>ram

链接脚本(Linker Script)解析相关推荐

  1. 链接脚本(Linker Scripts)语法和规则解析(翻译自官方手册)

    原链接:链接脚本(Linker Scripts)语法和规则解析(翻译自官方手册)_BSP-路人甲的博客-CSDN博客_链接脚本语法 为了便于与英文原文对照学习与理解(部分翻译可能不准确),本文中的每个 ...

  2. Realview MDK 链接脚本文件详细解析(一)–链接符号

    Realview MDK 链接脚本文件详细解析(一)–链接符号 Realview MDK 链接程序使用了两种方式控制程序的链接,即链接控制命令选项和链接脚本文件 链接脚本文件 链接脚本文件类型一般为: ...

  3. Linux下的lds链接脚本详解

    一. 概论 每一个链接过程都由链接脚本(linker script, 一般以lds作为文件的后缀名)控制. 链接脚本主要用于规定如何把输入文件内的section放入输出文件内, 并控制输出文件内各部分 ...

  4. 编译链接脚本lds文件

    转载自:http://linux.chinaunix.net/techdoc/beginner/2009/08/12/1129972.shtml 一. 概论 每一个链接过程都由链接脚本(linker ...

  5. linux下lds链接脚本详解

    转载自:http://linux.chinaunix.net/techdoc/beginner/2009/08/12/1129972.shtml 一. 概论 每一个链接过程都由链接脚本(linker ...

  6. linux 链接脚本,Linux下的lds链接脚本简介(一)

    每一个链接过程都由链接脚本(linker script, 一般以lds作为文件的后缀名)控制. 链接脚本主要用于规定如何把输入文件内的section放入输出文件内, 并控制输出文件内各部分在程序地址空 ...

  7. 链接脚本文件-Linker script file

    连接器(Linker)会将一个或多个目标文件(.0 file)整合成一个可执行文件(.hex file). 如单个源文件test.c经过编译后会生成一个目标文件test.o,test.o文件内容会细分 ...

  8. LD链接脚本解析-STM32F4xx

    本篇文章主要围绕项目 STM32_RTOS_GUN 的链接脚本 STM32F417IG_FLASH.ld 进行分析,同时对编写链接脚本的方法进行相应的讲解,尽可能地做到通过阅读这篇文章后能够学会编写简 ...

  9. linker 链接脚本

    linker 链接器主要有两个作用: 一是将若干输入文件(.o文件)根据一定规则合并为一个输出文件(例如ELF格式的可执行文件): 一是将符号与地址绑定(当然加载器也要完成这一部分工作). 关于链接器 ...

最新文章

  1. EM算法(Expectation Maximization)期望最大化算法
  2. 盘一盘 2021 年程序员们喜欢的网站数据
  3. C++ 优先级队列 priority_queue
  4. 15.看板方法——启动看板变革笔记
  5. 初学__Python——Python数据类型之列表和元组
  6. python在mac上_在Mac上Python多版本切换
  7. 如何实现动态加载删除android,关于android:融云IMKit-动态删除或添加plugin-的实现...
  8. IdentityServer4实战 - JWT Token Issuer 详解
  9. 算法设计与分析——回溯法——批处理作业调度
  10. AGC004E - Salvage Robots(dp,思维)
  11. [css] 举例说明与打印有关的属性有哪些?
  12. 【CodeForces - 202A】LLPS (思维,字符串)
  13. js学习笔记(新手向)
  14. arcpy:TIF/IMG上色后转KMZ文件
  15. java编程指南100本电子书
  16. fseek(f,0,SEEK_SET);
  17. linux ubantu最新版本,过去十年最佳的Ubuntu版本
  18. android 找不到手机,找不到 Android 开发者选项,难道我的手机系统没有?| 有轻功 #290...
  19. iOS ☞ SDWebimage 内存暴增问题
  20. 反垃圾邮件的一些相关链接

热门文章

  1. 从选题、创作、编辑、推广到优化,23个必用的内容营销工具
  2. FZU 2240 Daxia Suneast's problem
  3. python中sgd模型_python实现随机梯度下降(SGD)
  4. (附源码)计算机毕业设计SSM自习室管理系统
  5. Acute TravelLogic使用教程
  6. docker数据管理与网络通信
  7. 中国著名个人网站站长名单
  8. 计算机中单位ms什么意思,电荷的灵敏度单位(pc/ms^2)是什么意思啊?
  9. Windows7笔记本配置wifi热点
  10. php闹钟功能实现,基于Alarmmanager实现简单闹钟功能