链接脚本-1
链接脚本官方文档:
https://sourceware.org/binutils/docs-2.39/ld.html
链接脚本实例:(STM32F407VG,RT-Thread Studio生成的工程所含)
1 | /\* |
特别注意:
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定义地址空间的长度。
结构:
1 | MEMORY { |
NAME :存储区域的名字。(自己可以随意命名)
ATTR :定义该存储区域的属性。ATTR属性内可以出现以下7 个字符:
R 只读section
W 读/写section
X 可执行section
A 可分配的section
I 初始化了的section
L 同 I
! 不满足该字符之后的任何一个属性的section
ORIGIN :关键字,区域的开始地址,可简写成 org 或 o
LENGTH :关键字,区域的大小,可简写成 len 或 l
例子:
1 | MEMORY |
在链接文件中定义的变量(符号)可以在目标文件中使用
在链接文件中定义变量
1 | \_init\_start = .; |
在源文件中使用变量 _init_start
1 | #include <stdio.h> |
PROVIDE 关键字(感觉有无 PROVIDE 修饰都可以被输入文件引用,看上面的例子)
该关键字定义一个(输入文件内被引用但没定义)符号。相当于定义一个全局变量的符号表,其他C文件可以通过该符号来操作对应的存储内存。
1 | SECTIONS |
如上,在链接脚本中声明了_etext 符号。特别注意的是_etext 只是一个符号,没有存储内存,并不是一个变量,该符号对应(映射)的是一个地址,其地址为 .text section 之后的第一个地址。C文件中引用用法如下:
1 | int main() |
若在链接脚本中 “ _etext = 0x100; “,即表示符号_etext对应的地址为0x100, 此时 & _etext的值为 0x100, char a= *p;表示为 从0x100地址取存储的值赋值给变量a
在目标文件内定义的符号可以在链接脚本内被赋值
此时该符号被定义为全局的. 每个符号都对应了一个地址, 此处的赋值是更改这个符号对应的地址.
e.g. 通过下面的程序查看变量a的地址:
1 | /\* a.c \*/ |
文件a.lds
1 | /\* a.lds :注意格式有要求\*/ |
编译命令:$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
1 | .section .text.Reset\_Handler |
1 | .section .isr\_vector,"a",%progbits |
section 结构
1 | SECTIONS |
[ ]内的内容是可选选项
secname: 表示输出文件的 section 名,即输出文件中有哪些 section。而contents就是描述输出文件的这个 section 内容从哪些输入文件的哪些 section 里抽取而来。
输出section名字必须符合输出文件格式要求,比如:a.out格式的文件只允许存在.text、.data和.bss section名。而有的格式只允许存在数字名字,那么此时应该用引号将所有名字内的数字组合在一起;另外,还有一些格式允许任何序列的字符存在于 section名字内,此时如果名字内包含特殊字符(比如空格、逗号等),那么需要
用引号将其组合在一起。
如下,将输入文件的数据段存放在输出文件的数据段(section 名自己定义,section 名前后必须要有空格)
1 | SECTIONS |
其中 *(.data) 表示将所有的输入文件的 .data section 链接到输出文件 .data section 中, 特别注意的是,之前链接的就不会再链接,这样做的目的是可以将某些特殊的输入文件链接到地址前面。
start_addr :表示将某个段强制链接到的地址( VMA ),start_addr 改变定位符的值。
1 | SECTIONS |
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命令定义的位置信息,用于指定section在哪个memory执行,也就是VMA。如果不指定LMA,LMA = VMA。
VMA 和 LMA
section包含两个地址:VMA
(virtual memory address虚拟内存地址)和LMA
(load memory address加载内存地址)。通常VMA和LMA是相同的。
- VMA是执行输出文件时section所在的地址
- LMA是加载输出文件时section所在的地址
要作为运行地址,首先PC指针要能在这个地址空间内跑动,所以这段地址空间必须是可随机寻址的,也就是说可以访问想要访问的地址,如RAM,NorFlash(Norflash带有SRAM接口,有足够的地址引脚来寻址,可以很容易地存取其内部的每一个字节);如果是NandFlash,则不行,因为NandFlash只能通过一个块一个块的读/写,不能做到随机寻址。
VMA和LMA大多数情况下是相等的,但也可以不相等。通常,当LMA地址空间不支持随机寻址,或者是嫌弃LMA地址空间的访问速度比较慢时(比如NorFlash速度比SDRAM慢),则会将VMA设置到RAM中,这时,VMA与LMA就不相等了。
例:NandFlash因为不能随机访问想要访问的每个地址,不能作为运行地址,所以这里想要把在NandFlash的代码复制到SDRAM中。这里除了代码中要加入复制模块外,还要在链接脚本中使NandFlash部分的VMA设为SDRAM地址。大多数情况下,是在起始代码中初始化时就将需要复制的部分复制的到VMA地址空间中。
还有一种情况就是当嵌入式系统中先都将代码和数据加载到了ROM中,此时的地址就是LMA,但是当开始运行之后,需要将data数据部分拷贝到RAM中,此时数据的地址就是VMA,本文最开始的链接脚本,就是把.data的LMA设置在ROM,VMA设置在RAM。
KEEP 关键字
在链接命令行内使用了选项 -gc-sections 后,链接器可能将某些它认为没用的 section 过滤掉,此时就有必要强制让链接器保留一些特定的 section,可用 KEEP() 关键字达此目的。如 KEEP(* (.text)) 或 KEEP(SORT(*)(.text))。说的通俗易懂就是:防止被优化。
ALIGN 关键字
表示字节对齐, 如 “ . = ALIGN(4);”表示从该地址开始后面的存储进行4字节对齐。
实例详解:
1 | SECTIONS |
解释一下上诉的例子:
.= 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
本文转自 https://www.cnblogs.com/god-of-death/p/14879078.html,如有侵权,请联系删除。
- 本文作者: 季末影
- 本文链接: https://jimoying.github.io/2023/08/10/c/Linking-And-Libraries/链接脚本文件-1/
- 版权声明: 版权声明自定义文本