GCC链接脚本总结

本文深入探讨了链接脚本在构建可执行文件中的作用,包括内存空间分配、段的组织和符号赋值。通过ENTRY指定程序入口,MEMORY定义内存区域,SECTIONS布局内存段,并详细解释了段的属性、装载地址和虚拟地址。同时介绍了如何通过PROVIDE和PROVIDE_HIDDEN控制符号可见性,以及如何通过[>region][AT>lma_region]指定段的内存位置。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本文对链接脚本(Linker Scripts)常用知识进行总结,详细介绍可以通过查看GNU Linker手册了解。

基本概念

链接器的作用是将多个输入的目标文件(object file),一般后缀为.o,链接成一个可执行文件(executable file),在linux下为elf文件,而链接脚本的作用是告诉链接器如何将每个.o文件中的内容整合在一起,并给一些符号(symbol)赋值,用于系统的初始化等。
链接器以段(section)为单位对.o文件进行整合,一般来说,段中会存储程序执行必须的内容,如指令(.text段)数据(.data段)等,但是也有例外,所以依据中的内容,主要分为三类:

  • 可装载(loadable):段中的内容需要装载(load)到内存。
  • 可分配(allocatable):段中没有内容需要装载到内存中,但是在内存中需要预留该段的空间,通常这一部分空间被赋值为0。
  • 其他:除以上两种段以外的其他段,段中包含debug信息

对于可装载段可分配段,会存在两个地址,分别是虚拟地址(Virtual Memory Address,VMA)装载地址(Load Memory Address,LMA)虚拟地址是各段实际运行时的地址,而装载地址是各段装载到内存中的地址,一般来说,段的虚拟地址会用符号(symbol)进行记录,在程序中会依据这些符号进行段的移动等操作,段的装载地址即段在elf文件中的地址。例如,一个数据段会装载到ROM中,当程序启动时,需要将ROM中的数据段复制RAM中,该数据段在ROM中的地址为装载地址,而RAM中的地址为虚拟地址,且该虚拟地址会使用对应的符号(symbol)记录,在启动程序中,依据该符号进行数据的复制。
对于目标文件(.o文件)可以使用objdump程序的-h选项查看文件中的段(section)-t选项查看文件中的符号(symbol)

常用命令(command)

用户在链接脚本(Linker Script)中通过命令(command)对链接器进行控制。下面对常用的命令进行介绍。

ENTRY

ENTRY命令描述elf文件开始执行的位置。例如,ENTRY(_start),表示从符号(symbol)_start开始执行。另外可以通过命令选项-e指定开始执行的位置。

MEMORY

MEMORY命令描述elf文件内存空间的分配MEMORY命令的格式如下,

MEMORY
{
  name [(attr)] : ORIGIN = origin, LENGTH = len
  ...
}

name表示内存空间的名字,(attr)表示内存空间的读写执行属性,ORIGIN表示内存空间的起始地址,LENGTH表示内存空间的大小。具体例子如下所示,

MEMORY
{
  flash (wxa!ri) : ORIGIN = 0x00000000, LENGTH = 16K
  ram (wxa!ri) :   ORIGIN = 0x10000000, LENGTH = 16K
}

表示elf文件中对应两块内存空间,分别为flashram,其中flash的起始地址为0x00000000ram的起始地址为0x10000000,并且flashram的大小均为16KB。下面详细介绍(attr)的含义。
当需要链接的.o文件中的段,在链接脚本中,没有被显示安排在elf文件的某一段,那么.o文件中这些段将会按照.o文件中的段名单独成为elf文件中的一段,这些段所处的内存空间会按照原.o文件中这些段的属性和各个内存空间的属性进行匹配,匹配成功则会将该段放在对应的内存空间。其中MEMORY中的(attr)有以下几种,r表示只读,w表示可读可写,x表示可执行,a表示可分配,i表示初始化,!表示该段不具有后续的属性。上述例子中,(wxa!ri)的含义为,该内存空间中可以放置段属性为,可读写、可执行或可分配,且段属性不是只读和已经初始化的段。

SECTIONS

SECTIONS链接脚本中最重要的命令,用户可以用SECTIONS命令描述最后生成的elf文件内存布局(memory layout)。下面通过例子介绍SECTIONS命令的简单用法。

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

SECTIONS命令由关键字SECTIONS和一对大括号{}组成,大括号内部由描述内存布局的一系列符号(symbol)组成。
在上述例子中,SECTIONS中首先设置特殊符号.的值,.表示位置计数器(location counter)位置计数器在初始值为0,可以通过设置位置计数器来指定后续内容在输出的elf文件中地址,上例中. = 0x10000表示位置计数器的值设置为0x10000,即后续的内容从elf文件的地址0x10000开始排列。
SECTIONS第二个符号.text定义了输出的elf文件中的.text段,而在输出elf文件中会存在符号.text,该符号的值为.text段的起始地址,在此例中为0x10000.text后的大括号{}中的内容是为了说明,所有需要链接的.o文件中的哪些段,需要被放置到输出elf文件.text段中,上例中,*(.text)的通配符(wildcard)*,表示所有.o文件中的.text段,都需要被放置到elf文件.text段内。
后续. = 0x8000000将位置计数器设置为0x8000000,所以elf文件.data段的起始地址为0x8000000,在.data段链接完成后,位置计数器的值为0x8000000加上.data段的大小,而当前的位置计数器的值也就是elf文件.bss段的起始地址。
通过上述例子,可以总结在SECTIONS命令中可以包含的内容

  • 符号的赋值,例如,. = 0x10000. = 0x8000000,给特殊符号.赋值。
  • 输出elf文件中段的描述,例如,.text : { ... }.data : { ... }.bss = { ... }

现在对上述两类内容进行详细介绍。

符号的赋值

SECTIONS命令中,不仅可以给特殊符号.赋值,还可以给定义的任意符号赋值,一般形式为symbol_name = xxx,表示symbol_name的值为xxx。较为常用的是symbol_name = .,表示symbol_name当前位置计数器的值,即当前的虚拟地址(VMA)的值,也即位置计数器.前一个段链接完成后,对应的虚拟地址的值,关于位置计数器值的问题,会在段的描述章节中详细介绍。下面介绍符号赋值时常用的命令。

PROVIDE

PROVIDE用来定义在编程中可以使用符号。例如,PROVIDE( etext = . ),使用户编程时可以使用etext变量,而且变量etext的值为位置计数器.对应的地址。

PROVIDE_HIDDEN

PROVIDE相反,PROVIDE_HIDDEN用来隐藏符号。例如,PROVIDE( etext = . ),表示etext在编程时不可见。

段的描述

SECTIONS命令中,可以对链接完成后,输出的elf文件的段进行描述。具体格式如下,

SECTIONS
{
  ...
  section : {
    ...
    output-section-command
    output-section-command
    ...
  } [>region] [AT>lma_region]
  ...
}

下面对SECTIONS命令中的段描述各部分进行说明。

段的名字

上述例子中,section为输出elf文件中段的名字,段名会记录在符号表(symbol table)中,并且段名对应符号的值为该段的起始虚拟地址。一般elf文件中常见的段有,.text.data.bss等。

输入的段

上述例子中,output-section-command可以用来说明输出elf文件中的段,由被链接的.o文件中的哪些段构成。例如,output-section-command对应的命令为*( .text ),表示所有被链接的.o文件.text段,都会链接到elf文件的当前段中。另外,如果在链接时对链接器使用--gc-sections命令(该命令可以让链接器去除没有被使用到的段,以减小最后elf文件的大小),那么可以使用KEEP命令,即KEEP(*( .text )),表示所有输入.o文件中的.text段均不会被优化掉。

符号赋值

上述例子中,output-section-command可以对符号进行赋值,具体的规则和段的描述之外的符号赋值相同。值得注意的是,段的描述内的位置计数器.的值,均对应该段的虚拟地址。

[>region]

[>region]用来说明段的虚拟地址位于哪块内存空间,例如,>ram表示当前段的虚拟地址在内存空间ram中。段的虚拟地址所处的内存空间,对该段附近的位置计数器的值也存在影响。如下例所示,

SECTIONS
{
  section1 : {
    section1_start = .;
  } >ram AT>flash

  section1_end = .;
  
  section2 : {
    section2_start = .;
  } >flash AT>flash
  
  section2_end = .;
}

section1的虚拟地址在内存空间ram中,section1对应的大括号{}内以及大括号{}后的位置计数器也对应内存空间ram中的地址,所以符号section1_startsection2_end对应内存空间ram中的地址。section2的虚拟地址在内存空间flash中,section2对应的大括号{}内以及大括号{}的位置计数器对应内存空间flash中的地址,所以符号section2_startsection2_end对应内存空间flash中的地址。

[AT>lma_region]

[AT>lma_region]用来说明段的装载地址位于哪块内存空间,例如,AT>flash表示当前段的装载地址在内存空间flash中。装载地址对应该段在elf文件中的地址。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值