链接脚本文件-Linker script file

连接器(Linker)会将一个或多个目标文件(.0 file)整合成一个可执行文件(.hex file)。

如单个源文件test.c经过编译后会生成一个目标文件test.o,test.o文件内容会细分为如下几个段:.bss段,.data段,.rodata,.text段。而整个项目有若干个.o文件,连接器会按照链接脚本中的定义,把所有的.o文件的各个段的内容选择出来然后放到输出文件的对应的段里。

编译整体过程:
在这里插入图片描述

链接过程:
在这里插入图片描述

链接文件的作用是:描述如果把目标文件中的段映射到可执行文件中去,并控制可执行文件中的内存布局。

1 链接文件特性基本概念

1.1 object file format

前面提到的目标文件.o跟可执行文件.hex 都使用相同的数据格式,这个数据格式为目标文件格式。

1.2 section

You can see the sections in an object file by using the objdump program with the ‘-h’ option

1.3 VMA and LMA

每个section同时拥有俩个地址,一个是Load Memory address,一个是Virtual Memory address。
一般情况下,VMA 等于LMA,如.text 。但有些段,如data段的内容,需要在上电初始化阶段,从Flash中拷贝到RAM中运行,这时候.data段的VMA不等于LMA,LMA是在可执行文件中的位置,VMA是软件运行时实际操作的内存位置。

1.3 Symbol

1.3.1 Symobl for function and variable

每个.o文件拥有一个symbol列表。
对于每一个函数,每一个全局的变量都有一个对应的symbol。

You can see the symbols in an object file by using the nm program, or by using the objdump program with the ‘-t’ option.

1.3.2 创建一个新的symbol

可以在连接脚本中创建新的symbol,用于运算或者保存数据。
/我是这样理解的,symbol就是一个全局变量,其的值可以是代表数值,也可以是代表地址,定义的symbol会体现在map文件/

定义一个symbol:
  symbol_name = 0x4000;

eg: in linkscript
SECTION
{
 .data:  {
    _data_start = .;
    (*.data)
    _data_end = .;
    _data_size = _data_end  - _data_start ;
}>RAM AT>FALSH
}
/*这里定义了俩个symbol,_data_start和_data_end,其代表可执行文件中.data有效数据的开始跟终止位置*/


我们可以在软件中直接使用这个symbol:
eg:in C source code
usage——1
external uint8 _data_start;/*得到一个地址*/
external uint8 _data_end;
external uint32 _data_size;/*得到一个数值*/
external uint8 _flash_data_start;
memcpy (& _flash_data_start, & _data_start,_data_size );

Usage--2
external uint8 _data_start[]/*得到一个地址*/
external uint8 _data_end[];
external uint32 _data_size;/*得到一个数值*/
external uint8 _flash_data_start[];

memcpy (_flash_data_start, _data_start,_data_size );

2 链接脚本文件语法基本概念

2.1 注释

注释格式与C语言注释符/**/一样。

2.2 表达式

许多命令会使用到算数表达式,连接脚本文件的表达式与C语言一样,有如下特性:

 a. 所有的表达式作为整形存在,long type or unsigned long type

 b. 所有的常量是整形

 c. 支持所有C语言的算术运算符

 d.可以参考,定义,创建全局变量

2.3 整形

 八进制:  _data_octal = 012345;

 十进制:    _data_decimal = 57005;

 十六进制: _data_hex = 0x1234;

 负数:     _data_neg = -1234;

此外 K ,M,G是固定常数,1kb = 1024bytes, 1M = 1024Kb.

2.4 The location counter

位置计数器用"."表示,其代表着当前输出的地址。

使用场景1: 把当前的位置计数器的值赋给一个symbol。

链接文件中:
SECTION
{
out_section_1
	{
	start_section1 = .
	*(.data)
	}

c语言中:
extern unsigned int start_section1;
/*可以通过start_section1的地址,就是section的启始地址*/
}

使用场景2:移动位置计数器的位置

移动位置计数器的位置可能会造成内存空间上数据不是连续的。

SECTION
{
 output :
 {
 file1(.text)
 . = . + 1000;
 file2(.text)
 . += 1000;
 file3(.text)
 } = 0x1234;

/*file1 位于output section的开始位置,
然后有1000bytes的间隔,
然后放置file2,
然后再有1000bytes的间隔,
然后放置file3。
= 0x1234 表明,这个间隔里写的内容。*/
}

2.5 内嵌的算数功能

命令语言包括许多用于链接脚本表达式的内置函数。
1 ABSOLUTE(Experssion)
  返回一个表达式的绝对值。

eg:_cur_location_counter =  ABSOLUTE(.)
 /*把当前位置计数器的值赋给symbol _cur_location_counter */
2 ADDR(section) 
  返回一个section的绝对地址VMA。该section必须此前定义过。
3 LOADADDR(section)
  返回一个section的绝对加载地址LMA。
  一般情况下,一个section的VMA与LMA的值是一样的,
   除非定义section使用了AT。这时候VMA与LMA的值不一样
4 ALIGN(experssion)
 返回当前位置计数器与下一个expression边界对齐的位置的值。
 其结果 = (. + exp - 1) & ~(exp - 1)
 如果loadconter的值为1000,exp为4,计算的结果为1000
 如果loadconter的值为1002,exp为4,计算的结果为1004
 
 
eg1 :
SECTION
{
section ALIGN(4)
	{
	*(.data)
	. = ALIGN(4) ;
	}
}

第一个Align(4)的值作为section的start属性,即section的启始地址。
第二个 . = ALIGN(4),把当前加载计数器的值更新为4字节对齐后地址。
 
5 SIZEOF(section)
  返回一个section的空间内存大小;该section必须事先分配过。
6 
MAX(exp1,exp2)
MIN(exp1,exp2)
7 
BYTE(expression)    /*1 byte*/
SHORT(expression)   /*2 bytes*/
LONG(expression)    /*4 bytes*/
QUAD(expression)    /*8 unsigned bytes*/
SQUAD(expression)   /*8 signed bytes*/

以上数据会把表达式的值固定在当前section的某一位置上。

```c
8 FILL(experssion)
把因为操作位置计数器导致的间隔进行填充,填充的数据为experssion的低8位。

``

3 command集合

3.1 ENTRY 命令

上电后执行的第一个函数被称为entery point,ENTEY命令用于设置工程的entery point。

命令格式为:
ENTRY(entry_point)

eg
ENTRY(Reset_handler)
/*其中Reset_handler是程序执行的第一个函数,reset_handler这里是一个symbol,其值代表reset_handler的地址*/

3.2 ASSERT

assert命令可以进行简单的逻辑判断,如果判断为FALSE,则打印对应的messgae。

其命令格式为“
ASSERT(exp, message)

常用于判断当前分配内存是否使用溢出。

eg:
.stack :
  {
    PROVIDE (__stack = .);
    PROVIDE (__stack_size = 0x100);
    ASSERT ((__stack > (_end + __stack_size)), "Error: No room left for the stack");
  }

3.3 MEMORY命令

该命令描述目标内存快的位置及大小.

命令格式为:
MEMORY
{
...
name (attr) :ORIGIN = origin, LENGTH = len
name1 (attr) :ORIGIN = origin1, LENGTH = len1
...
}

--name
  内存块的名称
  
--(attr)
  可选项目,代表当前块的属性的列表。
  R /*Read only sections*/
  W/*Read/Write sections*/
  X/*Section containg executeable code*/
  A/*Alllocated sections*/
  I/*initialized sections*/
eg: 
 BOARD_FLASH (rx) : ORIGIN = 0x10000000, LENGTH = 0x10000 


--ORIGIN: 内存块的启始地址

--LENGTH:内存块的空间大小

可以通过该命令描述链接器可以使用那些内存,以及避开使用那些内存区域。

一旦定义一个具体的内存区域,其名称为“flash_memory_region”,就可以通过SECTION命令中以‘>flash_memory_region’,将特定的输出section定向导入这个内存区域。

3.4 SECTION命令

SECTION命令精准控制输入section放置到输出section的位置,输入section在输出文件的顺序以及输入section被分配到那些输出section,指明输出文档的内存分布。

在脚本文件中最多可以使用一个SECTION命令。

Section定义的完整语法如下,包含所有的可选项:

SECTIONS
{
...
secname start BLOCK(align) (NOLOAD) : AT (ldadr)
{contents}
 >region : phdr = fill
...
其中section,content是必须的,其余的参数为可选的。

参数解析:
0 ----contents 输入section的信息;
其语法格式为 file_name.o( input_section[]);由一个文件名跟一个输入section的列表组合而成。

 这个实际使用是比较灵活的:
 
 ---指出所有文件的.data 段
 .data: { *(.data) }

 ---指出所以文件的多个段
 .data :{ *(.data .text )}
 .data :{*(.data) *(.text)}
 
 ---指出单个文件的单个段
 .data :( adc.o(.data) ) 
 
 ---指出多个文件的单个段或斗个段
 .data :{
 adc.o(.rodata)
 adc.o(.text)
 uart.o(.rodata)
 uart.o(.text)

---指出不需要包含某些文件的某个段
.data :{
*(EXCLUDE_FILE (*uart.o *spi.o *i2c.o) .data)}
 }
 .DATRA:{*(.data)}
/*会把除uart,spi,i2c这个3个模块的输入.data 放入.data段中*、
/*uart,spi,i2c这个3个模块的输入.data 放入.DATA段中*/
  

1 ----start
该参数使用时,强制把输出的段加载到start参数指定的位置

2 ---- BLOCK(align)
   你可以包含BLOCK(align),把位置计数器(Location counter .)推进到满足align对齐方式的位置。align 是一个表达式,指定了对齐的方式。
 
3 ----(NOLOAD)
   NOLOAD表明:
   链接器会正常处理这个section,但程序加载器不会把这个section加载到内存中。
eg:  
SECTION
{
 section_rom 0 (NOLOAD)
 	{content}
}
/*section_rom位于内存为0的位置,但不会加载内存中当程序运行的时候*/
}

4----- AT (lmd_adr)
   指出当前section的实际加载地址LMA。
eg:
   SECTION
{
.text 0x1000
	{
	*(.text )
	_etext = .;
	}
.mdata 0x2000
	{
	_data = .;
	*(.data)
	_edata = .;
	}> RAM AT>FALSH
}
5-----  >region
   把section分配给MEMORY定义的某一个内存块(region)中去。
 
6 -----f: phdr
   把当前输出section分配给一个程序头描述的段。
 
7 ---- =fill
   指定该输出section未指定的区域指定填充内容,与FILL功能一样。

4 C语言内存分配

segment名称comment
bss段Blocked start by symbol存放没有初始化或者初始化为0的全局变量
data段数据段存放初始化非0的非const的全局变量
rodata只读数据段存放const限定的全局变量
text段代码段存放程序执行代码
heap存放动态分配的内存
stack堆栈1 存放程序临时创建的非静态的局部变量;2 函数传参;3 函数返回值

实际使用的一些总结

1 如要求把特定数据放到特定内存位置上,如要求把中断向量表放到位置为0的地址;

链接文件中定义:
 SECTIONS {
   .vector_table : {
      *(.INTVECT_COREEXCEPTIONS)
   } >FALSH

source code中定义
__attribute__ ((section(".INTVECT_COREEXCEPTIONS")))
void (* const vector_table[])(void) = {
&func_address_list };

attribute section会在当前文件中新建一个名为.INTVECT_COREEXCEPTIONS的section,并把vector_table函数指针数组放入这个section。

Reference:
1 https://ftp.gnu.org/old-gnu/Manuals/ld-2.9.1/html_chapter/ld_toc.html#TOC6

  • 5
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值